@burdenoff/microfe-adaptercloud 2026.614.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/adaptercloud/AdapterCloudRoot.js +177 -0
- package/dist/adaptercloud/AdapterCloudRoot.js.map +1 -0
- package/dist/adaptercloud/AdapterCloudRoutes.js +215 -0
- package/dist/adaptercloud/AdapterCloudRoutes.js.map +1 -0
- package/dist/adaptercloud/components/EmptyState.js +28 -0
- package/dist/adaptercloud/components/EmptyState.js.map +1 -0
- package/dist/adaptercloud/components/InvestmentCard.js +96 -0
- package/dist/adaptercloud/components/InvestmentCard.js.map +1 -0
- package/dist/adaptercloud/components/MetricsCard.js +46 -0
- package/dist/adaptercloud/components/MetricsCard.js.map +1 -0
- package/dist/adaptercloud/components/PageLayout.js +26 -0
- package/dist/adaptercloud/components/PageLayout.js.map +1 -0
- package/dist/adaptercloud/components/RatingDisplay.js +35 -0
- package/dist/adaptercloud/components/RatingDisplay.js.map +1 -0
- package/dist/adaptercloud/components/StatusBadge.js +45 -0
- package/dist/adaptercloud/components/StatusBadge.js.map +1 -0
- package/dist/adaptercloud/context/AdapterCloudContext.js +93 -0
- package/dist/adaptercloud/context/AdapterCloudContext.js.map +1 -0
- package/dist/adaptercloud/context/index.js +1 -0
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js +223 -0
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js.map +1 -0
- package/dist/adaptercloud/index.js +7 -0
- package/dist/adaptercloud/mocks/data.js +1517 -0
- package/dist/adaptercloud/mocks/data.js.map +1 -0
- package/dist/adaptercloud/mocks/users.js +34 -0
- package/dist/adaptercloud/mocks/users.js.map +1 -0
- package/dist/adaptercloud/pages/HomePage.js +921 -0
- package/dist/adaptercloud/pages/HomePage.js.map +1 -0
- package/dist/adaptercloud/pages/admin/AdminAnalyticsPage.js +645 -0
- package/dist/adaptercloud/pages/admin/AdminAnalyticsPage.js.map +1 -0
- package/dist/adaptercloud/pages/admin/AdminDashboard.js +639 -0
- package/dist/adaptercloud/pages/admin/AdminDashboard.js.map +1 -0
- package/dist/adaptercloud/pages/admin/AdminFinancialPage.js +421 -0
- package/dist/adaptercloud/pages/admin/AdminFinancialPage.js.map +1 -0
- package/dist/adaptercloud/pages/admin/AdminOrdersPage.js +660 -0
- package/dist/adaptercloud/pages/admin/AdminOrdersPage.js.map +1 -0
- package/dist/adaptercloud/pages/admin/AdminProfilePage.js +259 -0
- package/dist/adaptercloud/pages/admin/AdminProfilePage.js.map +1 -0
- package/dist/adaptercloud/pages/admin/AdminSettingsPage.js +649 -0
- package/dist/adaptercloud/pages/admin/AdminSettingsPage.js.map +1 -0
- package/dist/adaptercloud/pages/admin/CartManagementPage.js +869 -0
- package/dist/adaptercloud/pages/admin/CartManagementPage.js.map +1 -0
- package/dist/adaptercloud/pages/admin/UserManagementPage.js +893 -0
- package/dist/adaptercloud/pages/admin/UserManagementPage.js.map +1 -0
- package/dist/adaptercloud/pages/cart-owner/AnalyticsDashboard.js +834 -0
- package/dist/adaptercloud/pages/cart-owner/AnalyticsDashboard.js.map +1 -0
- package/dist/adaptercloud/pages/cart-owner/BusinessSettingsPage.js +700 -0
- package/dist/adaptercloud/pages/cart-owner/BusinessSettingsPage.js.map +1 -0
- package/dist/adaptercloud/pages/cart-owner/CartOwnerDashboard.js +760 -0
- package/dist/adaptercloud/pages/cart-owner/CartOwnerDashboard.js.map +1 -0
- package/dist/adaptercloud/pages/cart-owner/CartOwnerProfilePage.js +886 -0
- package/dist/adaptercloud/pages/cart-owner/CartOwnerProfilePage.js.map +1 -0
- package/dist/adaptercloud/pages/cart-owner/CartServiceConfigPage.js +631 -0
- package/dist/adaptercloud/pages/cart-owner/CartServiceConfigPage.js.map +1 -0
- package/dist/adaptercloud/pages/cart-owner/InventoryManagementPage.js +913 -0
- package/dist/adaptercloud/pages/cart-owner/InventoryManagementPage.js.map +1 -0
- package/dist/adaptercloud/pages/cart-owner/OrderManagementPage.js +881 -0
- package/dist/adaptercloud/pages/cart-owner/OrderManagementPage.js.map +1 -0
- package/dist/adaptercloud/pages/cart-owner/StaffManagementPage.js +833 -0
- package/dist/adaptercloud/pages/cart-owner/StaffManagementPage.js.map +1 -0
- package/dist/adaptercloud/pages/common/HelpSupportPage.js +1325 -0
- package/dist/adaptercloud/pages/common/HelpSupportPage.js.map +1 -0
- package/dist/adaptercloud/pages/common/NotificationCenterPage.js +665 -0
- package/dist/adaptercloud/pages/common/NotificationCenterPage.js.map +1 -0
- package/dist/adaptercloud/pages/common/SettingsPage.js +964 -0
- package/dist/adaptercloud/pages/common/SettingsPage.js.map +1 -0
- package/dist/adaptercloud/pages/customer/BookingDetailsPage.js +593 -0
- package/dist/adaptercloud/pages/customer/BookingDetailsPage.js.map +1 -0
- package/dist/adaptercloud/pages/customer/CartDetailPage.js +620 -0
- package/dist/adaptercloud/pages/customer/CartDetailPage.js.map +1 -0
- package/dist/adaptercloud/pages/customer/CartDiscoveryPage.js +547 -0
- package/dist/adaptercloud/pages/customer/CartDiscoveryPage.js.map +1 -0
- package/dist/adaptercloud/pages/customer/CheckoutPage.js +532 -0
- package/dist/adaptercloud/pages/customer/CheckoutPage.js.map +1 -0
- package/dist/adaptercloud/pages/customer/CustomerDashboard.js +496 -0
- package/dist/adaptercloud/pages/customer/CustomerDashboard.js.map +1 -0
- package/dist/adaptercloud/pages/customer/CustomerProfilePage.js +621 -0
- package/dist/adaptercloud/pages/customer/CustomerProfilePage.js.map +1 -0
- package/dist/adaptercloud/pages/customer/DirectServiceBookingPage.js +623 -0
- package/dist/adaptercloud/pages/customer/DirectServiceBookingPage.js.map +1 -0
- package/dist/adaptercloud/pages/customer/FavoritesPage.js +477 -0
- package/dist/adaptercloud/pages/customer/FavoritesPage.js.map +1 -0
- package/dist/adaptercloud/pages/customer/OrderHistoryPage.js +438 -0
- package/dist/adaptercloud/pages/customer/OrderHistoryPage.js.map +1 -0
- package/dist/adaptercloud/pages/customer/OrderTrackingPage.js +388 -0
- package/dist/adaptercloud/pages/customer/OrderTrackingPage.js.map +1 -0
- package/dist/adaptercloud/pages/customer/PaymentMethodsPage.js +488 -0
- package/dist/adaptercloud/pages/customer/PaymentMethodsPage.js.map +1 -0
- package/dist/adaptercloud/pages/customer/ReviewsPage.js +606 -0
- package/dist/adaptercloud/pages/customer/ReviewsPage.js.map +1 -0
- package/dist/adaptercloud/pages/customer/SavedAddressesPage.js +443 -0
- package/dist/adaptercloud/pages/customer/SavedAddressesPage.js.map +1 -0
- package/dist/adaptercloud/pages/investor/InvestmentDetailsPage.js +849 -0
- package/dist/adaptercloud/pages/investor/InvestmentDetailsPage.js.map +1 -0
- package/dist/adaptercloud/pages/investor/InvestmentOpportunitiesPage.js +939 -0
- package/dist/adaptercloud/pages/investor/InvestmentOpportunitiesPage.js.map +1 -0
- package/dist/adaptercloud/pages/investor/InvestorAnalyticsPage.js +681 -0
- package/dist/adaptercloud/pages/investor/InvestorAnalyticsPage.js.map +1 -0
- package/dist/adaptercloud/pages/investor/InvestorDashboard.js +419 -0
- package/dist/adaptercloud/pages/investor/InvestorDashboard.js.map +1 -0
- package/dist/adaptercloud/pages/investor/InvestorInvestmentsPage.js +399 -0
- package/dist/adaptercloud/pages/investor/InvestorInvestmentsPage.js.map +1 -0
- package/dist/adaptercloud/pages/investor/InvestorPortfolioPage.js +569 -0
- package/dist/adaptercloud/pages/investor/InvestorPortfolioPage.js.map +1 -0
- package/dist/adaptercloud/pages/investor/InvestorProfilePage.js +1065 -0
- package/dist/adaptercloud/pages/investor/InvestorProfilePage.js.map +1 -0
- package/dist/adaptercloud/pages/worker/TaskDetailsPage.js +496 -0
- package/dist/adaptercloud/pages/worker/TaskDetailsPage.js.map +1 -0
- package/dist/adaptercloud/pages/worker/WorkerDashboard.js +634 -0
- package/dist/adaptercloud/pages/worker/WorkerDashboard.js.map +1 -0
- package/dist/adaptercloud/pages/worker/WorkerOrdersPage.js +675 -0
- package/dist/adaptercloud/pages/worker/WorkerOrdersPage.js.map +1 -0
- package/dist/adaptercloud/pages/worker/WorkerProfilePage.js +687 -0
- package/dist/adaptercloud/pages/worker/WorkerProfilePage.js.map +1 -0
- package/dist/adaptercloud/pages/worker/WorkerSchedulePage.js +578 -0
- package/dist/adaptercloud/pages/worker/WorkerSchedulePage.js.map +1 -0
- package/dist/adaptercloud/store/adaptercloudStore.js +38 -0
- package/dist/adaptercloud/store/adaptercloudStore.js.map +1 -0
- package/dist/adaptercloud/store/index.js +1 -0
- package/dist/adaptercloud/utils/cn.js +10 -0
- package/dist/adaptercloud/utils/cn.js.map +1 -0
- package/dist/adaptercloud/utils/formatters.js +51 -0
- package/dist/adaptercloud/utils/formatters.js.map +1 -0
- package/dist/adaptercloud/utils/navigation.js +9 -0
- package/dist/adaptercloud/utils/navigation.js.map +1 -0
- package/dist/index.js +7 -0
- package/dist/node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.js +18 -0
- package/dist/node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/node_modules/.bun/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.js +1674 -0
- package/dist/node_modules/.bun/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/package.json +106 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReviewsPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/customer/ReviewsPage.tsx"],"sourcesContent":["/**\n * Reviews Page\n * @module adaptercloud/pages/customer\n *\n * Full-featured reviews: Write Review modal (interactive star rating with\n * text labels, title, comment, guidelines), Owner Response callout,\n * review images, verified badge, Mark as Helpful toggle, 4 filters\n * (search, type, rating, sort), Your Review label, 5 mock reviews.\n */\n\nimport { useState, useCallback } from 'react';\nimport {\n Star,\n MessageSquare,\n ThumbsUp,\n PenSquare,\n X,\n CheckCircle2,\n Image,\n MessageCircle,\n Info,\n Search,\n} from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { RatingDisplay } from '../../components/RatingDisplay';\nimport { EmptyState } from '../../components/EmptyState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { cn } from '../../utils/cn';\nimport { formatDate } from '../../utils/formatters';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface Review {\n id: string;\n userId: string;\n userName: string;\n userAvatar: string;\n itemId: string;\n itemName: string;\n itemType: 'cart' | 'product';\n cartId: string;\n cartName: string;\n orderId: string;\n rating: number;\n title: string;\n comment: string;\n images?: string[];\n helpfulCount: number;\n isHelpful: boolean;\n isVerified: boolean;\n createdAt: string;\n response?: {\n message: string;\n respondedBy: string;\n respondedAt: string;\n };\n}\n\ninterface ReviewFormData {\n rating: number;\n title: string;\n comment: string;\n}\n\ninterface ReviewFilters {\n searchQuery: string;\n type: 'all' | 'cart' | 'product' | 'my-reviews';\n rating: string;\n sortBy: 'recent' | 'helpful' | 'rating-high' | 'rating-low';\n}\n\nconst CURRENT_USER_ID = 'user-001';\n\nconst ratingLabels: Record<number, string> = {\n 1: 'Terrible',\n 2: 'Poor',\n 3: 'Average',\n 4: 'Good',\n 5: 'Excellent',\n};\n\n// ============================================================================\n// Mock Data\n// ============================================================================\n\nconst mockReviews: Review[] = [\n {\n id: 'review-001',\n userId: CURRENT_USER_ID,\n userName: 'Rahul Sharma',\n userAvatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100',\n itemId: 'FOOD-001',\n itemName: 'Dosa Express',\n itemType: 'cart',\n cartId: 'FOOD-001',\n cartName: 'Dosa Express',\n orderId: 'ORD-2024-001',\n rating: 5,\n title: 'Excellent breakfast spot!',\n comment:\n 'Amazing dosas and filter coffee. The masala dosa was perfectly crispy and the potato filling was delicious. Service was quick and friendly. Highly recommend for authentic South Indian breakfast!',\n helpfulCount: 12,\n isHelpful: true,\n isVerified: true,\n createdAt: '2024-06-20T09:30:00Z',\n response: {\n message:\n \"Thank you so much for your wonderful review! We're thrilled you enjoyed the dosas. We look forward to serving you again soon!\",\n respondedBy: 'Dosa Express Team',\n respondedAt: '2024-06-20T15:45:00Z',\n },\n },\n {\n id: 'review-002',\n userId: 'user-002',\n userName: 'Priya Patel',\n userAvatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100',\n itemId: 'prod-002',\n itemName: 'Pani Puri',\n itemType: 'product',\n cartId: 'FOOD-002',\n cartName: 'Chaat Corner',\n orderId: 'ORD-2024-002',\n rating: 4,\n title: 'Good but could be spicier',\n comment:\n 'The pani puri was good overall. The puris were crispy and fresh. However, the pani could have been more spicy and tangy. The portions were generous though.',\n helpfulCount: 8,\n isHelpful: false,\n isVerified: true,\n createdAt: '2024-06-18T19:15:00Z',\n },\n {\n id: 'review-003',\n userId: 'user-003',\n userName: 'Amit Kumar',\n userAvatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100',\n itemId: 'FOOD-002',\n itemName: 'Chaat Corner',\n itemType: 'cart',\n cartId: 'FOOD-002',\n cartName: 'Chaat Corner',\n orderId: 'ORD-2024-003',\n rating: 5,\n title: 'Authentic Mumbai street food!',\n comment:\n 'This place brings back memories of Mumbai streets. Every item I ordered was authentic and delicious. The bhel puri and vada pav were exceptional. Will definitely order again!',\n images: ['https://images.unsplash.com/photo-1601050690597-df0568f70950?w=400'],\n helpfulCount: 15,\n isHelpful: false,\n isVerified: true,\n createdAt: '2024-06-17T20:30:00Z',\n },\n {\n id: 'review-004',\n userId: CURRENT_USER_ID,\n userName: 'Rahul Sharma',\n userAvatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100',\n itemId: 'prod-001',\n itemName: 'Masala Dosa',\n itemType: 'product',\n cartId: 'FOOD-001',\n cartName: 'Dosa Express',\n orderId: 'ORD-2024-004',\n rating: 5,\n title: 'Perfect masala dosa!',\n comment:\n 'The masala dosa here is absolutely perfect. Crispy, golden brown, and the potato masala inside is perfectly spiced. Comes with great chutneys and sambar.',\n helpfulCount: 6,\n isHelpful: false,\n isVerified: true,\n createdAt: '2024-06-15T08:45:00Z',\n response: {\n message: 'Glad you loved it, Rahul! Our chef takes great care with every dosa. See you soon!',\n respondedBy: 'Dosa Express Team',\n respondedAt: '2024-06-15T14:00:00Z',\n },\n },\n {\n id: 'review-005',\n userId: 'user-004',\n userName: 'Sneha Reddy',\n userAvatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100',\n itemId: 'FOOD-001',\n itemName: 'Dosa Express',\n itemType: 'cart',\n cartId: 'FOOD-001',\n cartName: 'Dosa Express',\n orderId: 'ORD-2024-005',\n rating: 3,\n title: 'Average experience',\n comment:\n 'The food was okay but delivery took longer than expected. The taste was good but not exceptional. The packaging could be better to keep food warm.',\n helpfulCount: 4,\n isHelpful: false,\n isVerified: true,\n createdAt: '2024-06-12T12:20:00Z',\n },\n];\n\n// ============================================================================\n// Star Rating Interactive Component\n// ============================================================================\n\nfunction InteractiveStarRating({\n value,\n onChange,\n}: {\n value: number;\n onChange: (v: number) => void;\n}) {\n const [hoverValue, setHoverValue] = useState(0);\n const displayValue = hoverValue || value;\n\n return (\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center gap-0.5\">\n {[1, 2, 3, 4, 5].map((star) => (\n <button\n key={star}\n type=\"button\"\n onClick={() => onChange(star)}\n onMouseEnter={() => setHoverValue(star)}\n onMouseLeave={() => setHoverValue(0)}\n className=\"p-0.5 transition-transform hover:scale-110\"\n >\n <Star\n className={cn(\n 'h-6 w-6',\n star <= displayValue\n ? 'fill-yellow-400 text-yellow-400'\n : 'fill-none text-muted-foreground/40'\n )}\n />\n </button>\n ))}\n </div>\n <span className=\"text-sm font-medium text-muted-foreground\">\n {ratingLabels[displayValue] ?? ''}\n </span>\n </div>\n );\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function ReviewsPage() {\n const { navigateTo } = useAdapterCloud();\n const [reviews, setReviews] = useState<Review[]>(mockReviews);\n const [showWriteReview, setShowWriteReview] = useState(false);\n const [reviewForm, setReviewForm] = useState<ReviewFormData>({\n rating: 5,\n title: '',\n comment: '',\n });\n const [filters, setFilters] = useState<ReviewFilters>({\n searchQuery: '',\n type: 'all',\n rating: '',\n sortBy: 'recent',\n });\n const [statusMessage, setStatusMessage] = useState<string | null>(null);\n\n const showStatus = (msg: string) => {\n setStatusMessage(msg);\n setTimeout(() => setStatusMessage(null), 3000);\n };\n\n // --- Filtered reviews ---\n const filteredReviews = useCallback(() => {\n let result = [...reviews];\n\n // Type filter\n if (filters.type === 'my-reviews') {\n result = result.filter((r) => r.userId === CURRENT_USER_ID);\n } else if (filters.type !== 'all') {\n result = result.filter((r) => r.itemType === filters.type);\n }\n\n // Rating filter\n if (filters.rating) {\n const ratingVal = parseInt(filters.rating, 10);\n result = result.filter((r) => r.rating === ratingVal);\n }\n\n // Search\n if (filters.searchQuery) {\n const q = filters.searchQuery.toLowerCase();\n result = result.filter(\n (r) =>\n r.itemName.toLowerCase().includes(q) ||\n r.title.toLowerCase().includes(q) ||\n r.comment.toLowerCase().includes(q) ||\n r.cartName.toLowerCase().includes(q)\n );\n }\n\n // Sort\n switch (filters.sortBy) {\n case 'recent':\n result.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());\n break;\n case 'helpful':\n result.sort((a, b) => b.helpfulCount - a.helpfulCount);\n break;\n case 'rating-high':\n result.sort((a, b) => b.rating - a.rating);\n break;\n case 'rating-low':\n result.sort((a, b) => a.rating - b.rating);\n break;\n }\n\n return result;\n }, [reviews, filters])();\n\n // --- Stats ---\n const myReviewCount = reviews.filter((r) => r.userId === CURRENT_USER_ID).length;\n const avgRating =\n reviews.length > 0\n ? (reviews.reduce((s, r) => s + r.rating, 0) / reviews.length).toFixed(1)\n : '0';\n\n // --- Handlers ---\n const handleSubmitReview = () => {\n if (!reviewForm.title.trim() || !reviewForm.comment.trim()) {\n alert('Please fill in the Review Title and Comment.');\n return;\n }\n\n const newReview: Review = {\n id: `review-${Date.now()}`,\n userId: CURRENT_USER_ID,\n userName: 'Rahul Sharma',\n userAvatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100',\n itemId: 'FOOD-001',\n itemName: 'Dosa Express',\n itemType: 'cart',\n cartId: 'FOOD-001',\n cartName: 'Dosa Express',\n orderId: `ORD-2024-${Date.now()}`,\n rating: reviewForm.rating,\n title: reviewForm.title,\n comment: reviewForm.comment,\n helpfulCount: 0,\n isHelpful: false,\n isVerified: true,\n createdAt: new Date().toISOString(),\n };\n\n setReviews((prev) => [newReview, ...prev]);\n setReviewForm({ rating: 5, title: '', comment: '' });\n setShowWriteReview(false);\n showStatus('Review submitted successfully!');\n };\n\n const handleMarkHelpful = (reviewId: string) => {\n setReviews((prev) =>\n prev.map((r) =>\n r.id === reviewId\n ? {\n ...r,\n isHelpful: !r.isHelpful,\n helpfulCount: r.isHelpful ? r.helpfulCount - 1 : r.helpfulCount + 1,\n }\n : r\n )\n );\n };\n\n return (\n <PageLayout\n title=\"Reviews & Ratings\"\n description={`${filteredReviews.length} reviews found`}\n actions={\n <button\n type=\"button\"\n onClick={() => setShowWriteReview(true)}\n className={cn(\n 'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-opacity',\n 'bg-foreground text-background hover:opacity-90'\n )}\n >\n <PenSquare className=\"h-4 w-4\" />\n Write Review\n </button>\n }\n >\n {/* Status message */}\n {statusMessage && (\n <div className=\"mb-4 rounded-lg border border-green-500/30 bg-green-500/10 p-3 flex items-center gap-2\">\n <CheckCircle2 className=\"h-4 w-4 text-green-600 dark:text-green-400 shrink-0\" />\n <p className=\"text-sm text-green-600 dark:text-green-400\">{statusMessage}</p>\n </div>\n )}\n\n {/* Stats */}\n <div className=\"flex flex-wrap items-center gap-4 text-sm text-muted-foreground mb-6\">\n <span>{reviews.length} total reviews</span>\n <span>{myReviewCount} your reviews</span>\n <span className=\"flex items-center gap-1\">\n Average: {avgRating}\n <Star className=\"h-3.5 w-3.5 fill-yellow-400 text-yellow-400\" />\n </span>\n </div>\n\n {/* Filters */}\n <div className=\"rounded-lg border border-border bg-card p-4 mb-6\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3\">\n {/* Search */}\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground\" />\n <input\n type=\"text\"\n placeholder=\"Search reviews...\"\n value={filters.searchQuery}\n onChange={(e) => setFilters((p) => ({ ...p, searchQuery: e.target.value }))}\n className=\"w-full rounded-lg border border-border bg-card py-2 pl-9 pr-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n />\n </div>\n\n {/* Type */}\n <select\n value={filters.type}\n onChange={(e) =>\n setFilters((p) => ({ ...p, type: e.target.value as ReviewFilters['type'] }))\n }\n className=\"w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n >\n <option value=\"all\">All Reviews</option>\n <option value=\"cart\">Cart Reviews</option>\n <option value=\"product\">Product Reviews</option>\n <option value=\"my-reviews\">My Reviews</option>\n </select>\n\n {/* Rating */}\n <select\n value={filters.rating}\n onChange={(e) => setFilters((p) => ({ ...p, rating: e.target.value }))}\n className=\"w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n >\n <option value=\"\">All Ratings</option>\n <option value=\"5\">5 Stars</option>\n <option value=\"4\">4 Stars</option>\n <option value=\"3\">3 Stars</option>\n <option value=\"2\">2 Stars</option>\n <option value=\"1\">1 Star</option>\n </select>\n\n {/* Sort */}\n <select\n value={filters.sortBy}\n onChange={(e) =>\n setFilters((p) => ({ ...p, sortBy: e.target.value as ReviewFilters['sortBy'] }))\n }\n className=\"w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n >\n <option value=\"recent\">Most Recent</option>\n <option value=\"helpful\">Most Helpful</option>\n <option value=\"rating-high\">Highest Rating</option>\n <option value=\"rating-low\">Lowest Rating</option>\n </select>\n </div>\n </div>\n\n {/* Reviews List */}\n {filteredReviews.length === 0 ? (\n <EmptyState\n icon={<MessageSquare className=\"h-12 w-12\" />}\n title=\"No reviews found\"\n description=\"Be the first to write a review!\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowWriteReview(true)}\n className=\"rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:opacity-90 transition-opacity\"\n >\n Write a Review\n </button>\n }\n />\n ) : (\n <div className=\"space-y-4\">\n {filteredReviews.map((review) => {\n const isMyReview = review.userId === CURRENT_USER_ID;\n return (\n <div\n key={review.id}\n className={cn(\n 'rounded-lg border bg-card p-5',\n isMyReview ? 'border-foreground/20' : 'border-border'\n )}\n >\n {/* Header */}\n <div className=\"flex items-start gap-3 mb-3\">\n <img\n src={review.userAvatar}\n alt={review.userName}\n className=\"h-10 w-10 rounded-full object-cover shrink-0\"\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <span className=\"text-sm font-semibold text-foreground\">\n {review.userName}\n </span>\n {review.isVerified && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-green-500/10 px-2 py-0.5 text-[10px] font-medium text-green-600 dark:text-green-400\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Verified\n </span>\n )}\n {isMyReview && (\n <span className=\"inline-flex items-center rounded-full bg-blue-500/10 px-2 py-0.5 text-[10px] font-medium text-blue-600 dark:text-blue-400\">\n Your Review\n </span>\n )}\n </div>\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-muted-foreground mt-0.5\">\n <span>\n {review.itemType === 'cart' ? (\n <MessageSquare className=\"inline h-3 w-3 mr-0.5\" />\n ) : (\n <Star className=\"inline h-3 w-3 mr-0.5\" />\n )}\n {review.itemName}\n </span>\n <span>\n {formatDate(review.createdAt, {\n hour: undefined,\n minute: undefined,\n hour12: undefined,\n })}\n </span>\n </div>\n </div>\n <div className=\"shrink-0\">\n <RatingDisplay value={review.rating} size=\"sm\" />\n <p className=\"text-xs text-muted-foreground mt-0.5 text-right\">\n From: {review.cartName}\n </p>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"mb-3\">\n <h4 className=\"text-sm font-semibold text-foreground mb-1\">{review.title}</h4>\n <p className=\"text-sm text-foreground leading-relaxed\">{review.comment}</p>\n </div>\n\n {/* Review Images */}\n {review.images && review.images.length > 0 && (\n <div className=\"mb-3 flex gap-2 overflow-x-auto pb-1\">\n {review.images.map((img, idx) => (\n <img\n key={idx}\n src={img}\n alt={`Review image ${idx + 1}`}\n className=\"h-20 w-20 rounded-lg object-cover shrink-0\"\n />\n ))}\n </div>\n )}\n\n {/* Owner Response */}\n {review.response && (\n <div className=\"mb-3 rounded-lg border border-blue-500/20 bg-blue-500/5 p-3\">\n <div className=\"flex flex-wrap items-center gap-2 mb-1.5\">\n <MessageCircle className=\"h-3.5 w-3.5 text-blue-600 dark:text-blue-400\" />\n <span className=\"text-xs font-medium text-blue-600 dark:text-blue-400\">\n Response from {review.response.respondedBy}\n </span>\n <span className=\"text-[10px] text-blue-500/70\">\n {formatDate(review.response.respondedAt, {\n hour: undefined,\n minute: undefined,\n hour12: undefined,\n })}\n </span>\n </div>\n <p className=\"text-xs text-blue-700 dark:text-blue-300 leading-relaxed\">\n {review.response.message}\n </p>\n </div>\n )}\n\n {/* Footer */}\n <div className=\"flex items-center justify-between pt-3 border-t border-border\">\n <button\n type=\"button\"\n onClick={() => handleMarkHelpful(review.id)}\n className={cn(\n 'flex items-center gap-1.5 text-xs transition-colors',\n review.isHelpful\n ? 'text-blue-600 dark:text-blue-400 font-medium'\n : 'text-muted-foreground hover:text-foreground'\n )}\n >\n <ThumbsUp className={cn('h-3.5 w-3.5', review.isHelpful && 'fill-current')} />\n Helpful ({review.helpfulCount})\n </button>\n\n <div className=\"flex items-center gap-3 text-xs text-muted-foreground\">\n <span>Order: {review.orderId}</span>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/customer/carts/${review.cartId}`)}\n className=\"font-medium hover:text-foreground transition-colors\"\n >\n View Cart\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n {/* Write Review Modal */}\n {showWriteReview && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\">\n <div className=\"w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-lg border border-border bg-card shadow-lg\">\n <div className=\"p-5\">\n {/* Header */}\n <div className=\"flex items-center justify-between mb-5\">\n <h3 className=\"text-lg font-semibold text-foreground\">Write a Review</h3>\n <button\n type=\"button\"\n onClick={() => setShowWriteReview(false)}\n className=\"rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-foreground transition-colors\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n\n <div className=\"space-y-4\">\n {/* Rating */}\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-2\">\n Rating <span className=\"text-red-500\">*</span>\n </label>\n <InteractiveStarRating\n value={reviewForm.rating}\n onChange={(v) => setReviewForm((p) => ({ ...p, rating: v }))}\n />\n </div>\n\n {/* Title */}\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1.5\">\n Review Title <span className=\"text-red-500\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"Summarize your experience\"\n value={reviewForm.title}\n onChange={(e) => setReviewForm((p) => ({ ...p, title: e.target.value }))}\n className=\"w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n />\n </div>\n\n {/* Comment */}\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1.5\">\n Your Review <span className=\"text-red-500\">*</span>\n </label>\n <textarea\n rows={5}\n placeholder=\"Share your experience with others...\"\n value={reviewForm.comment}\n onChange={(e) => setReviewForm((p) => ({ ...p, comment: e.target.value }))}\n className=\"w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring resize-none\"\n />\n </div>\n\n {/* Guidelines */}\n <div className=\"rounded-lg bg-muted p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Info className=\"h-4 w-4 text-muted-foreground\" />\n <h4 className=\"text-sm font-medium text-foreground\">Review Guidelines</h4>\n </div>\n <ul className=\"text-xs text-muted-foreground space-y-1\">\n <li>- Be honest and helpful to other customers</li>\n <li>- Focus on your experience with the food and service</li>\n <li>- Avoid personal information or inappropriate content</li>\n <li>- Reviews are public and cannot be deleted once submitted</li>\n </ul>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-5\">\n <button\n type=\"button\"\n onClick={handleSubmitReview}\n className=\"rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:opacity-90 transition-opacity\"\n >\n Submit Review\n </button>\n <button\n type=\"button\"\n onClick={() => setShowWriteReview(false)}\n className=\"rounded-lg border border-border px-4 py-2 text-sm font-medium text-foreground hover:bg-accent transition-colors\"\n >\n Cancel\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import { useAdapterCloud as e } from "../../context/AdapterCloudContext.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
+
import { EmptyState as r } from "../../components/EmptyState.js";
|
|
5
|
+
import { formatDate as i } from "../../utils/formatters.js";
|
|
6
|
+
import { useState as a } from "react";
|
|
7
|
+
import { Briefcase as o, CheckCircle2 as s, Clock as c, Hash as l, Home as u, MapPin as d, Pencil as f, Plus as p, Star as m, Trash2 as h, X as g } from "lucide-react";
|
|
8
|
+
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
9
|
+
//#region src/adaptercloud/pages/customer/SavedAddressesPage.tsx
|
|
10
|
+
var y = {
|
|
11
|
+
type: "home",
|
|
12
|
+
nickname: "",
|
|
13
|
+
addressLine1: "",
|
|
14
|
+
addressLine2: "",
|
|
15
|
+
city: "",
|
|
16
|
+
state: "",
|
|
17
|
+
zipCode: "",
|
|
18
|
+
isDefault: !1
|
|
19
|
+
}, b = [
|
|
20
|
+
{
|
|
21
|
+
id: "addr-001",
|
|
22
|
+
type: "home",
|
|
23
|
+
nickname: "Home Sweet Home",
|
|
24
|
+
addressLine1: "123, MG Road",
|
|
25
|
+
addressLine2: "Near Central Mall",
|
|
26
|
+
city: "Bangalore",
|
|
27
|
+
state: "Karnataka",
|
|
28
|
+
zipCode: "560001",
|
|
29
|
+
isDefault: !0,
|
|
30
|
+
usageCount: 45,
|
|
31
|
+
lastUsed: "2024-06-24T08:30:00Z"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "addr-002",
|
|
35
|
+
type: "office",
|
|
36
|
+
nickname: "Office",
|
|
37
|
+
addressLine1: "456, Brigade Road",
|
|
38
|
+
addressLine2: "Floor 3, Tech Tower",
|
|
39
|
+
city: "Bangalore",
|
|
40
|
+
state: "Karnataka",
|
|
41
|
+
zipCode: "560025",
|
|
42
|
+
isDefault: !1,
|
|
43
|
+
usageCount: 28,
|
|
44
|
+
lastUsed: "2024-06-23T13:15:00Z"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "addr-003",
|
|
48
|
+
type: "other",
|
|
49
|
+
nickname: "Friend's Place",
|
|
50
|
+
addressLine1: "789, Koramangala",
|
|
51
|
+
addressLine2: "Near Forum Mall",
|
|
52
|
+
city: "Bangalore",
|
|
53
|
+
state: "Karnataka",
|
|
54
|
+
zipCode: "560034",
|
|
55
|
+
isDefault: !1,
|
|
56
|
+
usageCount: 12,
|
|
57
|
+
lastUsed: "2024-06-20T19:45:00Z"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "addr-004",
|
|
61
|
+
type: "home",
|
|
62
|
+
nickname: "Weekend Place",
|
|
63
|
+
addressLine1: "321, Indiranagar",
|
|
64
|
+
addressLine2: "Near Metro Station",
|
|
65
|
+
city: "Bangalore",
|
|
66
|
+
state: "Karnataka",
|
|
67
|
+
zipCode: "560038",
|
|
68
|
+
isDefault: !1,
|
|
69
|
+
usageCount: 8,
|
|
70
|
+
lastUsed: "2024-06-18T11:30:00Z"
|
|
71
|
+
}
|
|
72
|
+
], x = {
|
|
73
|
+
home: u,
|
|
74
|
+
office: o,
|
|
75
|
+
other: d
|
|
76
|
+
}, S = {
|
|
77
|
+
home: "bg-green-500/10 text-green-600 dark:text-green-400",
|
|
78
|
+
office: "bg-blue-500/10 text-blue-600 dark:text-blue-400",
|
|
79
|
+
other: "bg-purple-500/10 text-purple-600 dark:text-purple-400"
|
|
80
|
+
};
|
|
81
|
+
function C({ data: e, onChange: t, onSubmit: n, onCancel: r, submitLabel: i }) {
|
|
82
|
+
return /* @__PURE__ */ v("div", {
|
|
83
|
+
className: "space-y-4",
|
|
84
|
+
children: [
|
|
85
|
+
/* @__PURE__ */ v("div", {
|
|
86
|
+
className: "grid grid-cols-1 sm:grid-cols-2 gap-4",
|
|
87
|
+
children: [/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ v("label", {
|
|
88
|
+
className: "block text-sm font-medium text-foreground mb-1.5",
|
|
89
|
+
children: ["Address Type ", /* @__PURE__ */ _("span", {
|
|
90
|
+
className: "text-red-500",
|
|
91
|
+
children: "*"
|
|
92
|
+
})]
|
|
93
|
+
}), /* @__PURE__ */ v("select", {
|
|
94
|
+
value: e.type,
|
|
95
|
+
onChange: (e) => t("type", e.target.value),
|
|
96
|
+
className: "w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring",
|
|
97
|
+
children: [
|
|
98
|
+
/* @__PURE__ */ _("option", {
|
|
99
|
+
value: "home",
|
|
100
|
+
children: "Home"
|
|
101
|
+
}),
|
|
102
|
+
/* @__PURE__ */ _("option", {
|
|
103
|
+
value: "office",
|
|
104
|
+
children: "Office"
|
|
105
|
+
}),
|
|
106
|
+
/* @__PURE__ */ _("option", {
|
|
107
|
+
value: "other",
|
|
108
|
+
children: "Other"
|
|
109
|
+
})
|
|
110
|
+
]
|
|
111
|
+
})] }), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
112
|
+
className: "block text-sm font-medium text-foreground mb-1.5",
|
|
113
|
+
children: "Nickname"
|
|
114
|
+
}), /* @__PURE__ */ _("input", {
|
|
115
|
+
type: "text",
|
|
116
|
+
placeholder: "e.g., Home Sweet Home",
|
|
117
|
+
value: e.nickname,
|
|
118
|
+
onChange: (e) => t("nickname", e.target.value),
|
|
119
|
+
className: "w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
|
120
|
+
})] })]
|
|
121
|
+
}),
|
|
122
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ v("label", {
|
|
123
|
+
className: "block text-sm font-medium text-foreground mb-1.5",
|
|
124
|
+
children: ["Address Line 1 ", /* @__PURE__ */ _("span", {
|
|
125
|
+
className: "text-red-500",
|
|
126
|
+
children: "*"
|
|
127
|
+
})]
|
|
128
|
+
}), /* @__PURE__ */ _("input", {
|
|
129
|
+
type: "text",
|
|
130
|
+
placeholder: "House/Flat number, Street name",
|
|
131
|
+
value: e.addressLine1,
|
|
132
|
+
onChange: (e) => t("addressLine1", e.target.value),
|
|
133
|
+
className: "w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
|
134
|
+
})] }),
|
|
135
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
136
|
+
className: "block text-sm font-medium text-foreground mb-1.5",
|
|
137
|
+
children: "Address Line 2"
|
|
138
|
+
}), /* @__PURE__ */ _("input", {
|
|
139
|
+
type: "text",
|
|
140
|
+
placeholder: "Area, Landmark",
|
|
141
|
+
value: e.addressLine2,
|
|
142
|
+
onChange: (e) => t("addressLine2", e.target.value),
|
|
143
|
+
className: "w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
|
144
|
+
})] }),
|
|
145
|
+
/* @__PURE__ */ v("div", {
|
|
146
|
+
className: "grid grid-cols-1 sm:grid-cols-3 gap-4",
|
|
147
|
+
children: [
|
|
148
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ v("label", {
|
|
149
|
+
className: "block text-sm font-medium text-foreground mb-1.5",
|
|
150
|
+
children: ["City ", /* @__PURE__ */ _("span", {
|
|
151
|
+
className: "text-red-500",
|
|
152
|
+
children: "*"
|
|
153
|
+
})]
|
|
154
|
+
}), /* @__PURE__ */ _("input", {
|
|
155
|
+
type: "text",
|
|
156
|
+
placeholder: "City",
|
|
157
|
+
value: e.city,
|
|
158
|
+
onChange: (e) => t("city", e.target.value),
|
|
159
|
+
className: "w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
|
160
|
+
})] }),
|
|
161
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ v("label", {
|
|
162
|
+
className: "block text-sm font-medium text-foreground mb-1.5",
|
|
163
|
+
children: ["State ", /* @__PURE__ */ _("span", {
|
|
164
|
+
className: "text-red-500",
|
|
165
|
+
children: "*"
|
|
166
|
+
})]
|
|
167
|
+
}), /* @__PURE__ */ _("input", {
|
|
168
|
+
type: "text",
|
|
169
|
+
placeholder: "State",
|
|
170
|
+
value: e.state,
|
|
171
|
+
onChange: (e) => t("state", e.target.value),
|
|
172
|
+
className: "w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
|
173
|
+
})] }),
|
|
174
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ v("label", {
|
|
175
|
+
className: "block text-sm font-medium text-foreground mb-1.5",
|
|
176
|
+
children: ["ZIP Code ", /* @__PURE__ */ _("span", {
|
|
177
|
+
className: "text-red-500",
|
|
178
|
+
children: "*"
|
|
179
|
+
})]
|
|
180
|
+
}), /* @__PURE__ */ _("input", {
|
|
181
|
+
type: "text",
|
|
182
|
+
placeholder: "560001",
|
|
183
|
+
value: e.zipCode,
|
|
184
|
+
onChange: (e) => t("zipCode", e.target.value),
|
|
185
|
+
className: "w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
|
186
|
+
})] })
|
|
187
|
+
]
|
|
188
|
+
}),
|
|
189
|
+
/* @__PURE__ */ v("label", {
|
|
190
|
+
className: "flex items-center gap-2 cursor-pointer",
|
|
191
|
+
children: [/* @__PURE__ */ _("input", {
|
|
192
|
+
type: "checkbox",
|
|
193
|
+
checked: e.isDefault,
|
|
194
|
+
onChange: (e) => t("isDefault", e.target.checked),
|
|
195
|
+
className: "h-4 w-4 rounded border-border text-foreground"
|
|
196
|
+
}), /* @__PURE__ */ _("span", {
|
|
197
|
+
className: "text-sm text-muted-foreground",
|
|
198
|
+
children: "Set as default address"
|
|
199
|
+
})]
|
|
200
|
+
}),
|
|
201
|
+
/* @__PURE__ */ v("div", {
|
|
202
|
+
className: "flex items-center gap-3 pt-2",
|
|
203
|
+
children: [/* @__PURE__ */ _("button", {
|
|
204
|
+
type: "button",
|
|
205
|
+
onClick: n,
|
|
206
|
+
className: "rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:opacity-90 transition-opacity",
|
|
207
|
+
children: i
|
|
208
|
+
}), /* @__PURE__ */ _("button", {
|
|
209
|
+
type: "button",
|
|
210
|
+
onClick: r,
|
|
211
|
+
className: "rounded-lg border border-border px-4 py-2 text-sm font-medium text-foreground hover:bg-accent transition-colors",
|
|
212
|
+
children: "Cancel"
|
|
213
|
+
})]
|
|
214
|
+
})
|
|
215
|
+
]
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function w() {
|
|
219
|
+
let { navigateTo: o } = e(), [u, w] = a(b), [T, E] = a(!1), [D, O] = a(null), [k, A] = a({ ...y }), [j, M] = a(null), N = (e) => {
|
|
220
|
+
M(e), setTimeout(() => M(null), 3e3);
|
|
221
|
+
}, P = (e) => !e.addressLine1.trim() || !e.city.trim() || !e.state.trim() || !e.zipCode.trim() ? (alert("Please fill in all required fields (Address Line 1, City, State, ZIP Code)."), !1) : !0, F = () => {
|
|
222
|
+
if (!P(k)) return;
|
|
223
|
+
let e = {
|
|
224
|
+
id: `addr-${Date.now()}`,
|
|
225
|
+
type: k.type,
|
|
226
|
+
nickname: k.nickname,
|
|
227
|
+
addressLine1: k.addressLine1,
|
|
228
|
+
addressLine2: k.addressLine2,
|
|
229
|
+
city: k.city,
|
|
230
|
+
state: k.state,
|
|
231
|
+
zipCode: k.zipCode,
|
|
232
|
+
isDefault: k.isDefault,
|
|
233
|
+
usageCount: 0
|
|
234
|
+
};
|
|
235
|
+
w((t) => {
|
|
236
|
+
let n = [...t];
|
|
237
|
+
return e.isDefault && (n = n.map((e) => ({
|
|
238
|
+
...e,
|
|
239
|
+
isDefault: !1
|
|
240
|
+
}))), [...n, e];
|
|
241
|
+
}), A({ ...y }), E(!1), N("Address added successfully");
|
|
242
|
+
}, I = () => {
|
|
243
|
+
D && P(D) && (w((e) => e.map((e) => e.id === D.id ? D : D.isDefault ? {
|
|
244
|
+
...e,
|
|
245
|
+
isDefault: !1
|
|
246
|
+
} : e)), O(null), N("Address updated successfully"));
|
|
247
|
+
}, L = (e) => {
|
|
248
|
+
if (u.find((t) => t.id === e)?.isDefault) {
|
|
249
|
+
alert("Cannot delete default address. Set another address as default first.");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
w((t) => t.filter((t) => t.id !== e)), N("Address deleted successfully");
|
|
253
|
+
}, R = (e) => {
|
|
254
|
+
w((t) => t.map((t) => ({
|
|
255
|
+
...t,
|
|
256
|
+
isDefault: t.id === e
|
|
257
|
+
}))), N("Default address updated");
|
|
258
|
+
}, z = (e, t, n) => {
|
|
259
|
+
e((e) => ({
|
|
260
|
+
...e,
|
|
261
|
+
[t]: n
|
|
262
|
+
}));
|
|
263
|
+
};
|
|
264
|
+
return /* @__PURE__ */ v(n, {
|
|
265
|
+
title: "Saved Addresses",
|
|
266
|
+
description: "Manage your delivery addresses for faster checkout",
|
|
267
|
+
actions: T ? void 0 : /* @__PURE__ */ v("button", {
|
|
268
|
+
type: "button",
|
|
269
|
+
onClick: () => E(!0),
|
|
270
|
+
className: t("flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-opacity", "bg-foreground text-background hover:opacity-90"),
|
|
271
|
+
children: [/* @__PURE__ */ _(p, { className: "h-4 w-4" }), "Add New Address"]
|
|
272
|
+
}),
|
|
273
|
+
children: [
|
|
274
|
+
j && /* @__PURE__ */ v("div", {
|
|
275
|
+
className: "mb-4 rounded-lg border border-green-500/30 bg-green-500/10 p-3 flex items-center gap-2",
|
|
276
|
+
children: [/* @__PURE__ */ _(s, { className: "h-4 w-4 text-green-600 dark:text-green-400 shrink-0" }), /* @__PURE__ */ _("p", {
|
|
277
|
+
className: "text-sm text-green-600 dark:text-green-400",
|
|
278
|
+
children: j
|
|
279
|
+
})]
|
|
280
|
+
}),
|
|
281
|
+
T && /* @__PURE__ */ v("div", {
|
|
282
|
+
className: "rounded-lg border border-border bg-card p-5 mb-6",
|
|
283
|
+
children: [/* @__PURE__ */ _("h3", {
|
|
284
|
+
className: "text-base font-semibold text-foreground mb-4",
|
|
285
|
+
children: "Add New Address"
|
|
286
|
+
}), /* @__PURE__ */ _(C, {
|
|
287
|
+
data: k,
|
|
288
|
+
onChange: (e, t) => z(A, e, t),
|
|
289
|
+
onSubmit: F,
|
|
290
|
+
onCancel: () => {
|
|
291
|
+
E(!1), A({ ...y });
|
|
292
|
+
},
|
|
293
|
+
submitLabel: "Save Address"
|
|
294
|
+
})]
|
|
295
|
+
}),
|
|
296
|
+
u.length > 0 ? /* @__PURE__ */ _("div", {
|
|
297
|
+
className: "space-y-3",
|
|
298
|
+
children: u.map((e) => {
|
|
299
|
+
let n = x[e.type];
|
|
300
|
+
return /* @__PURE__ */ _("div", {
|
|
301
|
+
className: t("rounded-lg border bg-card p-4", e.isDefault ? "border-foreground/30" : "border-border"),
|
|
302
|
+
children: /* @__PURE__ */ v("div", {
|
|
303
|
+
className: "flex items-start justify-between gap-3",
|
|
304
|
+
children: [/* @__PURE__ */ v("div", {
|
|
305
|
+
className: "flex items-start gap-3 flex-1 min-w-0",
|
|
306
|
+
children: [/* @__PURE__ */ _("div", {
|
|
307
|
+
className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-muted",
|
|
308
|
+
children: /* @__PURE__ */ _(n, { className: "h-5 w-5 text-muted-foreground" })
|
|
309
|
+
}), /* @__PURE__ */ v("div", {
|
|
310
|
+
className: "min-w-0 flex-1",
|
|
311
|
+
children: [
|
|
312
|
+
/* @__PURE__ */ v("div", {
|
|
313
|
+
className: "flex flex-wrap items-center gap-2",
|
|
314
|
+
children: [/* @__PURE__ */ _("span", {
|
|
315
|
+
className: t("inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium capitalize", S[e.type]),
|
|
316
|
+
children: e.type
|
|
317
|
+
}), e.isDefault && /* @__PURE__ */ v("span", {
|
|
318
|
+
className: "inline-flex items-center gap-1 rounded-full bg-green-500/10 px-2 py-0.5 text-[10px] font-medium text-green-600 dark:text-green-400",
|
|
319
|
+
children: [/* @__PURE__ */ _(s, { className: "h-3 w-3" }), "Default"]
|
|
320
|
+
})]
|
|
321
|
+
}),
|
|
322
|
+
e.nickname && /* @__PURE__ */ _("p", {
|
|
323
|
+
className: "mt-1 text-sm font-semibold text-foreground",
|
|
324
|
+
children: e.nickname
|
|
325
|
+
}),
|
|
326
|
+
/* @__PURE__ */ _("p", {
|
|
327
|
+
className: "mt-0.5 text-sm text-foreground",
|
|
328
|
+
children: e.addressLine1
|
|
329
|
+
}),
|
|
330
|
+
e.addressLine2 && /* @__PURE__ */ _("p", {
|
|
331
|
+
className: "text-sm text-muted-foreground",
|
|
332
|
+
children: e.addressLine2
|
|
333
|
+
}),
|
|
334
|
+
/* @__PURE__ */ v("p", {
|
|
335
|
+
className: "text-sm text-muted-foreground",
|
|
336
|
+
children: [
|
|
337
|
+
e.city,
|
|
338
|
+
", ",
|
|
339
|
+
e.state,
|
|
340
|
+
" - ",
|
|
341
|
+
e.zipCode
|
|
342
|
+
]
|
|
343
|
+
}),
|
|
344
|
+
/* @__PURE__ */ v("div", {
|
|
345
|
+
className: "mt-2 flex flex-wrap items-center gap-3 text-xs text-muted-foreground",
|
|
346
|
+
children: [/* @__PURE__ */ v("span", {
|
|
347
|
+
className: "flex items-center gap-1",
|
|
348
|
+
children: [
|
|
349
|
+
/* @__PURE__ */ _(l, { className: "h-3 w-3" }),
|
|
350
|
+
"Used ",
|
|
351
|
+
e.usageCount,
|
|
352
|
+
" times"
|
|
353
|
+
]
|
|
354
|
+
}), e.lastUsed && /* @__PURE__ */ v("span", {
|
|
355
|
+
className: "flex items-center gap-1",
|
|
356
|
+
children: [
|
|
357
|
+
/* @__PURE__ */ _(c, { className: "h-3 w-3" }),
|
|
358
|
+
"Last used:",
|
|
359
|
+
" ",
|
|
360
|
+
i(e.lastUsed, {
|
|
361
|
+
hour: void 0,
|
|
362
|
+
minute: void 0,
|
|
363
|
+
hour12: void 0
|
|
364
|
+
})
|
|
365
|
+
]
|
|
366
|
+
})]
|
|
367
|
+
})
|
|
368
|
+
]
|
|
369
|
+
})]
|
|
370
|
+
}), /* @__PURE__ */ v("div", {
|
|
371
|
+
className: "flex items-center gap-1 shrink-0",
|
|
372
|
+
children: [
|
|
373
|
+
!e.isDefault && /* @__PURE__ */ _("button", {
|
|
374
|
+
type: "button",
|
|
375
|
+
onClick: () => R(e.id),
|
|
376
|
+
title: "Set as default",
|
|
377
|
+
className: "rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-foreground transition-colors",
|
|
378
|
+
children: /* @__PURE__ */ _(m, { className: "h-4 w-4" })
|
|
379
|
+
}),
|
|
380
|
+
/* @__PURE__ */ _("button", {
|
|
381
|
+
type: "button",
|
|
382
|
+
onClick: () => O({ ...e }),
|
|
383
|
+
title: "Edit address",
|
|
384
|
+
className: "rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-foreground transition-colors",
|
|
385
|
+
children: /* @__PURE__ */ _(f, { className: "h-4 w-4" })
|
|
386
|
+
}),
|
|
387
|
+
/* @__PURE__ */ _("button", {
|
|
388
|
+
type: "button",
|
|
389
|
+
onClick: () => L(e.id),
|
|
390
|
+
title: "Delete address",
|
|
391
|
+
className: "rounded-md p-2 text-muted-foreground hover:bg-destructive/10 hover:text-destructive transition-colors",
|
|
392
|
+
children: /* @__PURE__ */ _(h, { className: "h-4 w-4" })
|
|
393
|
+
})
|
|
394
|
+
]
|
|
395
|
+
})]
|
|
396
|
+
})
|
|
397
|
+
}, e.id);
|
|
398
|
+
})
|
|
399
|
+
}) : /* @__PURE__ */ _(r, {
|
|
400
|
+
icon: /* @__PURE__ */ _(d, { className: "h-12 w-12" }),
|
|
401
|
+
title: "No saved addresses",
|
|
402
|
+
description: "Add a delivery address to get started.",
|
|
403
|
+
action: /* @__PURE__ */ _("button", {
|
|
404
|
+
type: "button",
|
|
405
|
+
onClick: () => E(!0),
|
|
406
|
+
className: "rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:opacity-90 transition-opacity",
|
|
407
|
+
children: "Add Address"
|
|
408
|
+
})
|
|
409
|
+
}),
|
|
410
|
+
D && /* @__PURE__ */ _("div", {
|
|
411
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4",
|
|
412
|
+
children: /* @__PURE__ */ _("div", {
|
|
413
|
+
className: "w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-lg border border-border bg-card shadow-lg",
|
|
414
|
+
children: /* @__PURE__ */ v("div", {
|
|
415
|
+
className: "p-5",
|
|
416
|
+
children: [/* @__PURE__ */ v("div", {
|
|
417
|
+
className: "flex items-center justify-between mb-4",
|
|
418
|
+
children: [/* @__PURE__ */ _("h3", {
|
|
419
|
+
className: "text-lg font-semibold text-foreground",
|
|
420
|
+
children: "Edit Address"
|
|
421
|
+
}), /* @__PURE__ */ _("button", {
|
|
422
|
+
type: "button",
|
|
423
|
+
onClick: () => O(null),
|
|
424
|
+
className: "rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-foreground transition-colors",
|
|
425
|
+
children: /* @__PURE__ */ _(g, { className: "h-4 w-4" })
|
|
426
|
+
})]
|
|
427
|
+
}), /* @__PURE__ */ _(C, {
|
|
428
|
+
data: D,
|
|
429
|
+
onChange: (e, t) => z(O, e, t),
|
|
430
|
+
onSubmit: I,
|
|
431
|
+
onCancel: () => O(null),
|
|
432
|
+
submitLabel: "Save Changes"
|
|
433
|
+
})]
|
|
434
|
+
})
|
|
435
|
+
})
|
|
436
|
+
})
|
|
437
|
+
]
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
//#endregion
|
|
441
|
+
export { w as SavedAddressesPage };
|
|
442
|
+
|
|
443
|
+
//# sourceMappingURL=SavedAddressesPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SavedAddressesPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/customer/SavedAddressesPage.tsx"],"sourcesContent":["/**\n * Saved Addresses Page\n * @module adaptercloud/pages/customer\n *\n * Manage delivery addresses: list, add (inline expandable), edit (modal),\n * delete (with default protection), set default, usage count, last used date,\n * address nickname support. 4 mock addresses.\n */\n\nimport { useState } from 'react';\nimport {\n MapPin,\n Plus,\n Pencil,\n Trash2,\n CheckCircle2,\n Home,\n Briefcase,\n Star,\n X,\n Hash,\n Clock,\n} from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { cn } from '../../utils/cn';\nimport { formatDate } from '../../utils/formatters';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface SavedAddress {\n id: string;\n type: 'home' | 'office' | 'other';\n nickname: string;\n addressLine1: string;\n addressLine2: string;\n city: string;\n state: string;\n zipCode: string;\n isDefault: boolean;\n usageCount: number;\n lastUsed?: string;\n}\n\ntype AddressFormData = Omit<SavedAddress, 'id' | 'usageCount' | 'lastUsed'>;\n\nconst emptyForm: AddressFormData = {\n type: 'home',\n nickname: '',\n addressLine1: '',\n addressLine2: '',\n city: '',\n state: '',\n zipCode: '',\n isDefault: false,\n};\n\n// ============================================================================\n// Mock Data\n// ============================================================================\n\nconst initialAddresses: SavedAddress[] = [\n {\n id: 'addr-001',\n type: 'home',\n nickname: 'Home Sweet Home',\n addressLine1: '123, MG Road',\n addressLine2: 'Near Central Mall',\n city: 'Bangalore',\n state: 'Karnataka',\n zipCode: '560001',\n isDefault: true,\n usageCount: 45,\n lastUsed: '2024-06-24T08:30:00Z',\n },\n {\n id: 'addr-002',\n type: 'office',\n nickname: 'Office',\n addressLine1: '456, Brigade Road',\n addressLine2: 'Floor 3, Tech Tower',\n city: 'Bangalore',\n state: 'Karnataka',\n zipCode: '560025',\n isDefault: false,\n usageCount: 28,\n lastUsed: '2024-06-23T13:15:00Z',\n },\n {\n id: 'addr-003',\n type: 'other',\n nickname: \"Friend's Place\",\n addressLine1: '789, Koramangala',\n addressLine2: 'Near Forum Mall',\n city: 'Bangalore',\n state: 'Karnataka',\n zipCode: '560034',\n isDefault: false,\n usageCount: 12,\n lastUsed: '2024-06-20T19:45:00Z',\n },\n {\n id: 'addr-004',\n type: 'home',\n nickname: 'Weekend Place',\n addressLine1: '321, Indiranagar',\n addressLine2: 'Near Metro Station',\n city: 'Bangalore',\n state: 'Karnataka',\n zipCode: '560038',\n isDefault: false,\n usageCount: 8,\n lastUsed: '2024-06-18T11:30:00Z',\n },\n];\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nconst iconMap = {\n home: Home,\n office: Briefcase,\n other: MapPin,\n};\n\nconst typeBadgeStyles: Record<string, string> = {\n home: 'bg-green-500/10 text-green-600 dark:text-green-400',\n office: 'bg-blue-500/10 text-blue-600 dark:text-blue-400',\n other: 'bg-purple-500/10 text-purple-600 dark:text-purple-400',\n};\n\n// ============================================================================\n// Address Form Component\n// ============================================================================\n\nfunction AddressForm({\n data,\n onChange,\n onSubmit,\n onCancel,\n submitLabel,\n}: {\n data: AddressFormData;\n onChange: (field: string, value: string | boolean) => void;\n onSubmit: () => void;\n onCancel: () => void;\n submitLabel: string;\n}) {\n return (\n <div className=\"space-y-4\">\n {/* Type + Nickname row */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1.5\">\n Address Type <span className=\"text-red-500\">*</span>\n </label>\n <select\n value={data.type}\n onChange={(e) => onChange('type', e.target.value)}\n className=\"w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n >\n <option value=\"home\">Home</option>\n <option value=\"office\">Office</option>\n <option value=\"other\">Other</option>\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1.5\">Nickname</label>\n <input\n type=\"text\"\n placeholder=\"e.g., Home Sweet Home\"\n value={data.nickname}\n onChange={(e) => onChange('nickname', e.target.value)}\n className=\"w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n />\n </div>\n </div>\n\n {/* Address Line 1 */}\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1.5\">\n Address Line 1 <span className=\"text-red-500\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"House/Flat number, Street name\"\n value={data.addressLine1}\n onChange={(e) => onChange('addressLine1', e.target.value)}\n className=\"w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n />\n </div>\n\n {/* Address Line 2 */}\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1.5\">Address Line 2</label>\n <input\n type=\"text\"\n placeholder=\"Area, Landmark\"\n value={data.addressLine2}\n onChange={(e) => onChange('addressLine2', e.target.value)}\n className=\"w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n />\n </div>\n\n {/* City / State / ZIP */}\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1.5\">\n City <span className=\"text-red-500\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"City\"\n value={data.city}\n onChange={(e) => onChange('city', e.target.value)}\n className=\"w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1.5\">\n State <span className=\"text-red-500\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"State\"\n value={data.state}\n onChange={(e) => onChange('state', e.target.value)}\n className=\"w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1.5\">\n ZIP Code <span className=\"text-red-500\">*</span>\n </label>\n <input\n type=\"text\"\n placeholder=\"560001\"\n value={data.zipCode}\n onChange={(e) => onChange('zipCode', e.target.value)}\n className=\"w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n />\n </div>\n </div>\n\n {/* Set Default checkbox */}\n <label className=\"flex items-center gap-2 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={data.isDefault}\n onChange={(e) => onChange('isDefault', e.target.checked)}\n className=\"h-4 w-4 rounded border-border text-foreground\"\n />\n <span className=\"text-sm text-muted-foreground\">Set as default address</span>\n </label>\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={onSubmit}\n className=\"rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:opacity-90 transition-opacity\"\n >\n {submitLabel}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"rounded-lg border border-border px-4 py-2 text-sm font-medium text-foreground hover:bg-accent transition-colors\"\n >\n Cancel\n </button>\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\nexport function SavedAddressesPage() {\n const { navigateTo } = useAdapterCloud();\n const [addresses, setAddresses] = useState<SavedAddress[]>(initialAddresses);\n const [showAddForm, setShowAddForm] = useState(false);\n const [editingAddress, setEditingAddress] = useState<SavedAddress | null>(null);\n const [newAddress, setNewAddress] = useState<AddressFormData>({ ...emptyForm });\n const [statusMessage, setStatusMessage] = useState<string | null>(null);\n\n // --- Helpers ---\n const showStatus = (msg: string) => {\n setStatusMessage(msg);\n setTimeout(() => setStatusMessage(null), 3000);\n };\n\n const validateForm = (data: AddressFormData): boolean => {\n if (\n !data.addressLine1.trim() ||\n !data.city.trim() ||\n !data.state.trim() ||\n !data.zipCode.trim()\n ) {\n alert('Please fill in all required fields (Address Line 1, City, State, ZIP Code).');\n return false;\n }\n return true;\n };\n\n // --- Handlers ---\n const handleAddAddress = () => {\n if (!validateForm(newAddress)) return;\n\n const addressToAdd: SavedAddress = {\n id: `addr-${Date.now()}`,\n type: newAddress.type,\n nickname: newAddress.nickname,\n addressLine1: newAddress.addressLine1,\n addressLine2: newAddress.addressLine2,\n city: newAddress.city,\n state: newAddress.state,\n zipCode: newAddress.zipCode,\n isDefault: newAddress.isDefault,\n usageCount: 0,\n };\n\n setAddresses((prev) => {\n let updated = [...prev];\n if (addressToAdd.isDefault) {\n updated = updated.map((a) => ({ ...a, isDefault: false }));\n }\n return [...updated, addressToAdd];\n });\n\n setNewAddress({ ...emptyForm });\n setShowAddForm(false);\n showStatus('Address added successfully');\n };\n\n const handleEditSave = () => {\n if (!editingAddress) return;\n if (!validateForm(editingAddress)) return;\n\n setAddresses((prev) =>\n prev.map((a) => {\n if (a.id === editingAddress.id) return editingAddress;\n if (editingAddress.isDefault) return { ...a, isDefault: false };\n return a;\n })\n );\n setEditingAddress(null);\n showStatus('Address updated successfully');\n };\n\n const handleDelete = (id: string) => {\n const addr = addresses.find((a) => a.id === id);\n if (addr?.isDefault) {\n alert('Cannot delete default address. Set another address as default first.');\n return;\n }\n setAddresses((prev) => prev.filter((a) => a.id !== id));\n showStatus('Address deleted successfully');\n };\n\n const handleSetDefault = (id: string) => {\n setAddresses((prev) => prev.map((a) => ({ ...a, isDefault: a.id === id })));\n showStatus('Default address updated');\n };\n\n const handleFormChange = (\n setter: React.Dispatch<React.SetStateAction<any>>,\n field: string,\n value: string | boolean\n ) => {\n setter((prev: any) => ({ ...prev, [field]: value }));\n };\n\n return (\n <PageLayout\n title=\"Saved Addresses\"\n description=\"Manage your delivery addresses for faster checkout\"\n actions={\n !showAddForm ? (\n <button\n type=\"button\"\n onClick={() => setShowAddForm(true)}\n className={cn(\n 'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-opacity',\n 'bg-foreground text-background hover:opacity-90'\n )}\n >\n <Plus className=\"h-4 w-4\" />\n Add New Address\n </button>\n ) : undefined\n }\n >\n {/* Status message */}\n {statusMessage && (\n <div className=\"mb-4 rounded-lg border border-green-500/30 bg-green-500/10 p-3 flex items-center gap-2\">\n <CheckCircle2 className=\"h-4 w-4 text-green-600 dark:text-green-400 shrink-0\" />\n <p className=\"text-sm text-green-600 dark:text-green-400\">{statusMessage}</p>\n </div>\n )}\n\n {/* Inline Add Form */}\n {showAddForm && (\n <div className=\"rounded-lg border border-border bg-card p-5 mb-6\">\n <h3 className=\"text-base font-semibold text-foreground mb-4\">Add New Address</h3>\n <AddressForm\n data={newAddress}\n onChange={(field, value) => handleFormChange(setNewAddress, field, value)}\n onSubmit={handleAddAddress}\n onCancel={() => {\n setShowAddForm(false);\n setNewAddress({ ...emptyForm });\n }}\n submitLabel=\"Save Address\"\n />\n </div>\n )}\n\n {/* Address List */}\n {addresses.length > 0 ? (\n <div className=\"space-y-3\">\n {addresses.map((address) => {\n const Icon = iconMap[address.type];\n return (\n <div\n key={address.id}\n className={cn(\n 'rounded-lg border bg-card p-4',\n address.isDefault ? 'border-foreground/30' : 'border-border'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n {/* Left: icon + details */}\n <div className=\"flex items-start gap-3 flex-1 min-w-0\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-muted\">\n <Icon className=\"h-5 w-5 text-muted-foreground\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n {/* Badges row */}\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium capitalize',\n typeBadgeStyles[address.type]\n )}\n >\n {address.type}\n </span>\n {address.isDefault && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-green-500/10 px-2 py-0.5 text-[10px] font-medium text-green-600 dark:text-green-400\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Default\n </span>\n )}\n </div>\n\n {/* Nickname */}\n {address.nickname && (\n <p className=\"mt-1 text-sm font-semibold text-foreground\">\n {address.nickname}\n </p>\n )}\n\n {/* Address lines */}\n <p className=\"mt-0.5 text-sm text-foreground\">{address.addressLine1}</p>\n {address.addressLine2 && (\n <p className=\"text-sm text-muted-foreground\">{address.addressLine2}</p>\n )}\n <p className=\"text-sm text-muted-foreground\">\n {address.city}, {address.state} - {address.zipCode}\n </p>\n\n {/* Usage stats */}\n <div className=\"mt-2 flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n <span className=\"flex items-center gap-1\">\n <Hash className=\"h-3 w-3\" />\n Used {address.usageCount} times\n </span>\n {address.lastUsed && (\n <span className=\"flex items-center gap-1\">\n <Clock className=\"h-3 w-3\" />\n Last used:{' '}\n {formatDate(address.lastUsed, {\n hour: undefined,\n minute: undefined,\n hour12: undefined,\n })}\n </span>\n )}\n </div>\n </div>\n </div>\n\n {/* Right: actions */}\n <div className=\"flex items-center gap-1 shrink-0\">\n {!address.isDefault && (\n <button\n type=\"button\"\n onClick={() => handleSetDefault(address.id)}\n title=\"Set as default\"\n className=\"rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-foreground transition-colors\"\n >\n <Star className=\"h-4 w-4\" />\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => setEditingAddress({ ...address })}\n title=\"Edit address\"\n className=\"rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-foreground transition-colors\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleDelete(address.id)}\n title=\"Delete address\"\n className=\"rounded-md p-2 text-muted-foreground hover:bg-destructive/10 hover:text-destructive transition-colors\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n ) : (\n <EmptyState\n icon={<MapPin className=\"h-12 w-12\" />}\n title=\"No saved addresses\"\n description=\"Add a delivery address to get started.\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddForm(true)}\n className=\"rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:opacity-90 transition-opacity\"\n >\n Add Address\n </button>\n }\n />\n )}\n\n {/* Edit Address Modal */}\n {editingAddress && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\">\n <div className=\"w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-lg border border-border bg-card shadow-lg\">\n <div className=\"p-5\">\n {/* Modal header */}\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"text-lg font-semibold text-foreground\">Edit Address</h3>\n <button\n type=\"button\"\n onClick={() => setEditingAddress(null)}\n className=\"rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-foreground transition-colors\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n\n <AddressForm\n data={editingAddress}\n onChange={(field, value) => handleFormChange(setEditingAddress, field, value)}\n onSubmit={handleEditSave}\n onCancel={() => setEditingAddress(null)}\n submitLabel=\"Save Changes\"\n />\n </div>\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|