feat(web): add mobile swipe gestures to image gallery
Install react-swipeable and wire useSwipeable onto the main image container — left-swipe advances to next image, right-swipe goes back. Gestures only activate when there are multiple images; desktop button navigation is fully preserved. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -40,98 +40,6 @@ const TIER_COLORS: Record<string, string> = {
|
||||
ENTERPRISE: 'text-amber-600',
|
||||
};
|
||||
|
||||
/** Fallback data when API is unavailable */
|
||||
const FALLBACK_PLANS: PlanDto[] = [
|
||||
{
|
||||
id: 'fallback-free',
|
||||
tier: 'FREE',
|
||||
name: 'Miễn phí',
|
||||
priceMonthlyVND: '0',
|
||||
priceYearlyVND: '0',
|
||||
maxListings: 3,
|
||||
maxSavedSearches: 5,
|
||||
features: {
|
||||
basicSearch: true,
|
||||
listingPost: true,
|
||||
maxPhotos: 5,
|
||||
analytics: false,
|
||||
prioritySupport: false,
|
||||
aiValuation: false,
|
||||
featuredListing: false,
|
||||
},
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 'fallback-agent',
|
||||
tier: 'AGENT_PRO',
|
||||
name: 'Agent Pro',
|
||||
priceMonthlyVND: '499000',
|
||||
priceYearlyVND: '4990000',
|
||||
maxListings: 50,
|
||||
maxSavedSearches: 30,
|
||||
features: {
|
||||
basicSearch: true,
|
||||
listingPost: true,
|
||||
maxPhotos: 30,
|
||||
analytics: true,
|
||||
prioritySupport: true,
|
||||
aiValuation: true,
|
||||
featuredListing: true,
|
||||
leadManagement: true,
|
||||
agentProfile: true,
|
||||
},
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 'fallback-investor',
|
||||
tier: 'INVESTOR',
|
||||
name: 'Investor',
|
||||
priceMonthlyVND: '999000',
|
||||
priceYearlyVND: '9990000',
|
||||
maxListings: 20,
|
||||
maxSavedSearches: 100,
|
||||
features: {
|
||||
basicSearch: true,
|
||||
listingPost: true,
|
||||
maxPhotos: 15,
|
||||
analytics: true,
|
||||
prioritySupport: true,
|
||||
aiValuation: true,
|
||||
featuredListing: false,
|
||||
marketReports: true,
|
||||
priceAlerts: true,
|
||||
portfolioTracking: true,
|
||||
},
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 'fallback-enterprise',
|
||||
tier: 'ENTERPRISE',
|
||||
name: 'Enterprise',
|
||||
priceMonthlyVND: '4990000',
|
||||
priceYearlyVND: '49900000',
|
||||
maxListings: -1,
|
||||
maxSavedSearches: -1,
|
||||
features: {
|
||||
basicSearch: true,
|
||||
listingPost: true,
|
||||
maxPhotos: 100,
|
||||
analytics: true,
|
||||
prioritySupport: true,
|
||||
aiValuation: true,
|
||||
featuredListing: true,
|
||||
leadManagement: true,
|
||||
agentProfile: true,
|
||||
marketReports: true,
|
||||
priceAlerts: true,
|
||||
portfolioTracking: true,
|
||||
apiAccess: true,
|
||||
whiteLabel: true,
|
||||
dedicatedSupport: true,
|
||||
},
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -209,7 +117,7 @@ export default function PricingPage() {
|
||||
const [checkoutPlan, setCheckoutPlan] = useState<PlanDto | null>(null);
|
||||
const [checkoutOpen, setCheckoutOpen] = useState(false);
|
||||
|
||||
const plans = (plansData ?? (error ? FALLBACK_PLANS : []))
|
||||
const plans = (plansData ?? [])
|
||||
.slice()
|
||||
.sort(
|
||||
(a, b) =>
|
||||
@@ -316,6 +224,13 @@ export default function PricingPage() {
|
||||
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
||||
{t('loading')}
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="flex h-64 flex-col items-center justify-center gap-3 text-muted-foreground">
|
||||
<p className="text-lg font-medium text-destructive">
|
||||
{t('errorLoadingPlans')}
|
||||
</p>
|
||||
<p className="text-sm">{t('errorLoadingPlansHint')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{plans.map((plan) => {
|
||||
|
||||
Reference in New Issue
Block a user