@burdenoff/microfe-kadaikodi 2026.704.3 → 2026.704.5
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/dist/kadaikodi/KadaikodiRoutes.js +34 -26
- package/dist/kadaikodi/KadaikodiRoutes.js.map +1 -1
- package/dist/kadaikodi/components/viz/AllocationBar.js +68 -0
- package/dist/kadaikodi/components/viz/AllocationBar.js.map +1 -0
- package/dist/kadaikodi/components/viz/AreaChart.js +102 -0
- package/dist/kadaikodi/components/viz/AreaChart.js.map +1 -0
- package/dist/kadaikodi/components/viz/AvatarStack.js +36 -0
- package/dist/kadaikodi/components/viz/AvatarStack.js.map +1 -0
- package/dist/kadaikodi/components/viz/BarChart.js +83 -0
- package/dist/kadaikodi/components/viz/BarChart.js.map +1 -0
- package/dist/kadaikodi/components/viz/CertificateCard.js +4 -0
- package/dist/kadaikodi/components/viz/DonutChart.js +109 -0
- package/dist/kadaikodi/components/viz/DonutChart.js.map +1 -0
- package/dist/kadaikodi/components/viz/HBarFunnel.js +54 -0
- package/dist/kadaikodi/components/viz/HBarFunnel.js.map +1 -0
- package/dist/kadaikodi/components/viz/Heatmap.js +3 -0
- package/dist/kadaikodi/components/viz/KanbanBoard.js +50 -0
- package/dist/kadaikodi/components/viz/KanbanBoard.js.map +1 -0
- package/dist/kadaikodi/components/viz/LineChart.js +4 -0
- package/dist/kadaikodi/components/viz/OrgTree.js +2 -0
- package/dist/kadaikodi/components/viz/PathSteps.js +3 -0
- package/dist/kadaikodi/components/viz/ProgressRing.js +68 -0
- package/dist/kadaikodi/components/viz/ProgressRing.js.map +1 -0
- package/dist/kadaikodi/components/viz/RatingStars.js +51 -0
- package/dist/kadaikodi/components/viz/RatingStars.js.map +1 -0
- package/dist/kadaikodi/components/viz/SegmentedTabs.js +69 -0
- package/dist/kadaikodi/components/viz/SegmentedTabs.js.map +1 -0
- package/dist/kadaikodi/components/viz/SkillBar.js +3 -0
- package/dist/kadaikodi/components/viz/Sparkline.js +74 -0
- package/dist/kadaikodi/components/viz/Sparkline.js.map +1 -0
- package/dist/kadaikodi/components/viz/StatCard.js +60 -0
- package/dist/kadaikodi/components/viz/StatCard.js.map +1 -0
- package/dist/kadaikodi/components/viz/StepProgress.js +64 -0
- package/dist/kadaikodi/components/viz/StepProgress.js.map +1 -0
- package/dist/kadaikodi/components/viz/Timeline.js +66 -0
- package/dist/kadaikodi/components/viz/Timeline.js.map +1 -0
- package/dist/kadaikodi/components/viz/index.js +23 -0
- package/dist/kadaikodi/components/viz/lib/Avatar.js +30 -0
- package/dist/kadaikodi/components/viz/lib/Avatar.js.map +1 -0
- package/dist/kadaikodi/components/viz/lib/CartesianFrame.js +119 -0
- package/dist/kadaikodi/components/viz/lib/CartesianFrame.js.map +1 -0
- package/dist/kadaikodi/components/viz/lib/ResponsiveSvg.js +35 -0
- package/dist/kadaikodi/components/viz/lib/ResponsiveSvg.js.map +1 -0
- package/dist/kadaikodi/components/viz/lib/format.js +21 -0
- package/dist/kadaikodi/components/viz/lib/format.js.map +1 -0
- package/dist/kadaikodi/components/viz/lib/geometry.js +61 -0
- package/dist/kadaikodi/components/viz/lib/geometry.js.map +1 -0
- package/dist/kadaikodi/components/viz/lib/hooks.js +39 -0
- package/dist/kadaikodi/components/viz/lib/hooks.js.map +1 -0
- package/dist/kadaikodi/components/viz/lib/palette.js +27 -0
- package/dist/kadaikodi/components/viz/lib/palette.js.map +1 -0
- package/dist/kadaikodi/hooks/useKadaikodiApi.js +84 -6
- package/dist/kadaikodi/hooks/useKadaikodiApi.js.map +1 -1
- package/dist/kadaikodi/pages/HomePage.js +373 -631
- package/dist/kadaikodi/pages/HomePage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminAnalyticsPage.js +304 -169
- package/dist/kadaikodi/pages/admin/AdminAnalyticsPage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminDashboard.js +255 -271
- package/dist/kadaikodi/pages/admin/AdminDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminFinancialPage.js +185 -125
- package/dist/kadaikodi/pages/admin/AdminFinancialPage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminOrdersPage.js +141 -102
- package/dist/kadaikodi/pages/admin/AdminOrdersPage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminProfilePage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminSettingsPage.js +332 -276
- package/dist/kadaikodi/pages/admin/AdminSettingsPage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/CartManagementPage.js +564 -729
- package/dist/kadaikodi/pages/admin/CartManagementPage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/UserManagementPage.js +310 -366
- package/dist/kadaikodi/pages/admin/UserManagementPage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/AnalyticsDashboard.js +201 -120
- package/dist/kadaikodi/pages/cart-owner/AnalyticsDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/BusinessSettingsPage.js +290 -257
- package/dist/kadaikodi/pages/cart-owner/BusinessSettingsPage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/CartOwnerDashboard.js +173 -194
- package/dist/kadaikodi/pages/cart-owner/CartOwnerDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/CartOwnerProfilePage.js +270 -228
- package/dist/kadaikodi/pages/cart-owner/CartOwnerProfilePage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/CartServiceConfigPage.js +194 -173
- package/dist/kadaikodi/pages/cart-owner/CartServiceConfigPage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/InventoryManagementPage.js +428 -347
- package/dist/kadaikodi/pages/cart-owner/InventoryManagementPage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/InvestmentListingPage.js +332 -0
- package/dist/kadaikodi/pages/cart-owner/InvestmentListingPage.js.map +1 -0
- package/dist/kadaikodi/pages/cart-owner/MerchantOnboardingPage.js +373 -0
- package/dist/kadaikodi/pages/cart-owner/MerchantOnboardingPage.js.map +1 -0
- package/dist/kadaikodi/pages/cart-owner/OrderManagementPage.js +294 -162
- package/dist/kadaikodi/pages/cart-owner/OrderManagementPage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/StaffManagementPage.js +180 -181
- package/dist/kadaikodi/pages/cart-owner/StaffManagementPage.js.map +1 -1
- package/dist/kadaikodi/pages/common/HelpSupportPage.js +310 -317
- package/dist/kadaikodi/pages/common/HelpSupportPage.js.map +1 -1
- package/dist/kadaikodi/pages/common/NotificationCenterPage.js.map +1 -1
- package/dist/kadaikodi/pages/common/SettingsPage.js +502 -586
- package/dist/kadaikodi/pages/common/SettingsPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/BookingDetailsPage.js +239 -156
- package/dist/kadaikodi/pages/customer/BookingDetailsPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CartDetailPage.js +405 -248
- package/dist/kadaikodi/pages/customer/CartDetailPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CartDiscoveryPage.js +258 -232
- package/dist/kadaikodi/pages/customer/CartDiscoveryPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CheckoutPage.js +254 -205
- package/dist/kadaikodi/pages/customer/CheckoutPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CustomerDashboard.js +5 -5
- package/dist/kadaikodi/pages/customer/CustomerDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CustomerProfilePage.js +189 -199
- package/dist/kadaikodi/pages/customer/CustomerProfilePage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/DirectServiceBookingPage.js +272 -230
- package/dist/kadaikodi/pages/customer/DirectServiceBookingPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/FavoritesPage.js +150 -413
- package/dist/kadaikodi/pages/customer/FavoritesPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/OrderHistoryPage.js +173 -134
- package/dist/kadaikodi/pages/customer/OrderHistoryPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/OrderTrackingPage.js +411 -305
- package/dist/kadaikodi/pages/customer/OrderTrackingPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/ReviewsPage.js +310 -406
- package/dist/kadaikodi/pages/customer/ReviewsPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/SavedAddressesPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/shared.js +181 -0
- package/dist/kadaikodi/pages/customer/shared.js.map +1 -0
- package/dist/kadaikodi/pages/investor/InvestmentDetailsPage.js +438 -311
- package/dist/kadaikodi/pages/investor/InvestmentDetailsPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestmentOpportunitiesPage.js +320 -293
- package/dist/kadaikodi/pages/investor/InvestmentOpportunitiesPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestorAnalyticsPage.js +159 -91
- package/dist/kadaikodi/pages/investor/InvestorAnalyticsPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestorDashboard.js +137 -107
- package/dist/kadaikodi/pages/investor/InvestorDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestorInvestmentsPage.js +157 -135
- package/dist/kadaikodi/pages/investor/InvestorInvestmentsPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestorPortfolioPage.js +280 -206
- package/dist/kadaikodi/pages/investor/InvestorPortfolioPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestorProfilePage.js +441 -514
- package/dist/kadaikodi/pages/investor/InvestorProfilePage.js.map +1 -1
- package/dist/kadaikodi/pages/worker/TaskDetailsPage.js +428 -431
- package/dist/kadaikodi/pages/worker/TaskDetailsPage.js.map +1 -1
- package/dist/kadaikodi/pages/worker/WorkerDashboard.js +328 -445
- package/dist/kadaikodi/pages/worker/WorkerDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/worker/WorkerOrdersPage.js +141 -164
- package/dist/kadaikodi/pages/worker/WorkerOrdersPage.js.map +1 -1
- package/dist/kadaikodi/pages/worker/WorkerProfilePage.js +360 -288
- package/dist/kadaikodi/pages/worker/WorkerProfilePage.js.map +1 -1
- package/dist/kadaikodi/pages/worker/WorkerSchedulePage.js +303 -281
- package/dist/kadaikodi/pages/worker/WorkerSchedulePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReviewsPage.js","names":[],"sources":["../../../../src/kadaikodi/pages/customer/ReviewsPage.tsx"],"sourcesContent":["/**\n * Reviews Page\n * @module kadaikodi/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 { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { RatingDisplay } from '../../components/RatingDisplay';\nimport { EmptyState } from '../../components/EmptyState';\nimport { useKadaikodi } from '../../context/KadaikodiContext';\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 ratingLabels: Record<number, string> = {\n 1: 'Terrible',\n 2: 'Poor',\n 3: 'Average',\n 4: 'Good',\n 5: 'Excellent',\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\"\n >\n <Star\n className={cn(\n 'h-6 w-6',\n star <= displayValue\n ? 'fill-status-warning-text text-status-warning-text'\n : 'fill-none text-text-secondary/40'\n )}\n />\n </button>\n ))}\n </div>\n <span className=\"text-sm font-medium text-text-secondary\">\n {ratingLabels[displayValue] ?? ''}\n </span>\n </div>\n );\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function ReviewsPage() {\n const { navigateTo, currentUser } = useKadaikodi();\n // There is no cross-cart \"my reviews\" endpoint; reviews are listed per provider\n // via useReviews(providerId). This customer-level view has no provider context,\n // so we never fabricate reviews — the list stays empty until a real source exists.\n const currentUserId = currentUser?.id ?? '';\n const [reviews, setReviews] = useState<Review[]>([]);\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 === currentUserId);\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 === currentUserId).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 // Reviews attach to a specific cart + order. They are created from a delivered\n // order (createReview), not from this cross-cart list — so we don't fabricate a\n // review row here. Guide the customer to the right place instead.\n setReviewForm({ rating: 5, title: '', comment: '' });\n setShowWriteReview(false);\n showStatus('To post a review, open a delivered order and rate it from there.');\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-status-success-text/30 bg-status-success-bg-subtle p-3 flex items-center gap-2\">\n <CheckCircle2 className=\"h-4 w-4 text-status-success-text shrink-0\" />\n <p className=\"text-sm text-status-success-text\">{statusMessage}</p>\n </div>\n )}\n\n <PagePurpose className=\"mb-6\">\n Your voice on the carts you've tried — write honest reviews to help other customers and give\n vendors feedback they can act on. Browse what others said, then add your own rating for a\n recent order.\n </PagePurpose>\n\n {/* Stats — the one emphasis zone */}\n <EmphasisPanel className=\"mb-6\">\n <div className=\"flex flex-wrap items-center gap-4 text-sm text-text-secondary\">\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-status-warning-text text-status-warning-text\" />\n </span>\n </div>\n </EmphasisPanel>\n\n {/* Filters */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface 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-text-secondary\" />\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-subtle bg-bg-surface py-2 pl-9 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-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 === currentUserId;\n return (\n <div\n key={review.id}\n className={cn(\n 'rounded-lg border bg-bg-surface p-5',\n isMyReview ? 'border-foreground/20' : 'border-border-subtle'\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-text-primary\">\n {review.userName}\n </span>\n {review.isVerified && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-success-text\">\n <CheckCircle2 className=\"h-3 w-3\" />\n Verified\n </span>\n )}\n {isMyReview && (\n <span className=\"inline-flex items-center rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-[10px] font-medium text-text-primary\">\n Your Review\n </span>\n )}\n </div>\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-text-secondary 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-text-secondary 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-text-primary mb-1\">{review.title}</h4>\n <p className=\"text-sm text-text-primary 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-border-focus/20 bg-[var(--color-accent-soft)] 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-text-primary\" />\n <span className=\"text-xs font-medium text-text-primary\">\n Response from {review.response.respondedBy}\n </span>\n <span className=\"text-[10px] text-text-primary/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-text-primary 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-subtle\">\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-text-primary font-medium'\n : 'text-text-secondary hover:text-text-primary'\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-text-secondary\">\n <span>Order: {review.orderId}</span>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/customer/carts/${review.cartId}`)}\n className=\"font-medium hover:text-text-primary 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-subtle bg-bg-surface 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-text-primary\">Write a Review</h3>\n <button\n type=\"button\"\n onClick={() => setShowWriteReview(false)}\n className=\"rounded-md p-2 text-text-secondary hover:bg-accent hover:text-text-primary 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-text-primary mb-2\">\n Rating <span className=\"text-status-error-text\">*</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-text-primary mb-1.5\">\n Review Title <span className=\"text-status-error-text\">*</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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* Comment */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n Your Review <span className=\"text-status-error-text\">*</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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n\n {/* Guidelines */}\n <div className=\"rounded-lg bg-bg-sunken p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Info className=\"h-4 w-4 text-text-secondary\" />\n <h4 className=\"text-sm font-medium text-text-primary\">Review Guidelines</h4>\n </div>\n <ul className=\"text-xs text-text-secondary 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-subtle px-4 py-2 text-sm font-medium text-text-primary 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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"}
|
|
1
|
+
{"version":3,"file":"ReviewsPage.js","names":[],"sources":["../../../../src/kadaikodi/pages/customer/ReviewsPage.tsx"],"sourcesContent":["/**\n * Reviews Page\n * @module kadaikodi/pages/customer\n *\n * Per-cart reviews on REAL data: pick a cart (defaults to one you've ordered\n * from), see its aggregate rating + a 5→1 rating-distribution, read every\n * review with the owner's public response, and write your own review for a\n * delivered order (persisted via useCreateReview). Nothing here is fabricated —\n * when a cart has no reviews yet, an honest empty state is shown.\n */\n\nimport { useMemo, useState } from 'react';\nimport {\n Star,\n MessageSquare,\n MessageCircle,\n Search,\n Loader2,\n CheckCircle2,\n PenSquare,\n} from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { RatingStars } from '../../components/viz';\nimport { useKadaikodi } from '../../context/KadaikodiContext';\nimport {\n useProviders,\n useOrders,\n useReviews,\n useCreateReview,\n} from '../../hooks/useKadaikodiApi';\nimport { cn } from '../../utils/cn';\nimport { formatRelativeTime } from '../../utils/formatters';\nimport { ProviderImage } from './shared';\n\nconst ratingLabels: Record<number, string> = {\n 1: 'Terrible',\n 2: 'Poor',\n 3: 'Average',\n 4: 'Good',\n 5: 'Excellent',\n};\n\ntype SortBy = 'recent' | 'rating-high' | 'rating-low';\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 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 aria-label={`${star} star${star > 1 ? 's' : ''}`}\n >\n <Star\n className={cn(\n 'h-7 w-7',\n star <= displayValue\n ? 'fill-status-warning-text text-status-warning-text'\n : 'fill-none text-text-secondary/40'\n )}\n />\n </button>\n ))}\n </div>\n <span className=\"text-sm font-medium text-text-secondary\">\n {ratingLabels[displayValue] ?? ''}\n </span>\n </div>\n );\n}\n\nexport function ReviewsPage() {\n const { navigateTo, currentUser } = useKadaikodi();\n const { data: providers = [], isLoading: providersLoading } = useProviders();\n const { data: myOrders = [] } = useOrders({ customerId: currentUser?.id });\n const createReview = useCreateReview();\n\n // Delivered orders are the only reviewable ones. The providers behind them\n // are the carts the customer can write about.\n const reviewableOrders = useMemo(\n () => myOrders.filter((o) => o.status === 'delivered'),\n [myOrders]\n );\n const reviewableProviderIds = useMemo(\n () => new Set(reviewableOrders.map((o) => o.cartId)),\n [reviewableOrders]\n );\n\n const [selectedProviderId, setSelectedProviderId] = useState('');\n const effectiveProviderId =\n selectedProviderId ||\n reviewableOrders[0]?.cartId ||\n providers[0]?.id ||\n '';\n\n const provider = useMemo(\n () => providers.find((p) => p.id === effectiveProviderId),\n [providers, effectiveProviderId]\n );\n\n const { data: reviews = [], isLoading: reviewsLoading } = useReviews(effectiveProviderId);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [ratingFilter, setRatingFilter] = useState('');\n const [sortBy, setSortBy] = useState<SortBy>('recent');\n\n const [formRating, setFormRating] = useState(5);\n const [formComment, setFormComment] = useState('');\n const [statusMessage, setStatusMessage] = useState<string | null>(null);\n\n const showStatus = (msg: string): void => {\n setStatusMessage(msg);\n setTimeout(() => setStatusMessage(null), 3000);\n };\n\n // Rating distribution 5→1 from the real review list.\n const distribution = useMemo(() => {\n const counts: Record<number, number> = { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 };\n for (const r of reviews) {\n const bucket = Math.min(5, Math.max(1, Math.round(r.rating)));\n counts[bucket] += 1;\n }\n return counts;\n }, [reviews]);\n\n const filteredReviews = useMemo(() => {\n let list = [...reviews];\n if (ratingFilter) {\n const val = parseInt(ratingFilter, 10);\n list = list.filter((r) => Math.round(r.rating) === val);\n }\n if (searchQuery.trim()) {\n const q = searchQuery.toLowerCase();\n list = list.filter(\n (r) => (r.review ?? '').toLowerCase().includes(q) || (r.response ?? '').toLowerCase().includes(q)\n );\n }\n switch (sortBy) {\n case 'recent':\n list.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());\n break;\n case 'rating-high':\n list.sort((a, b) => b.rating - a.rating);\n break;\n case 'rating-low':\n list.sort((a, b) => a.rating - b.rating);\n break;\n }\n return list;\n }, [reviews, ratingFilter, searchQuery, sortBy]);\n\n // The delivered order for this provider that anchors a new review.\n const orderForReview = useMemo(\n () => reviewableOrders.find((o) => o.cartId === effectiveProviderId),\n [reviewableOrders, effectiveProviderId]\n );\n const canWrite = Boolean(orderForReview && currentUser?.id);\n\n const handleSubmit = (): void => {\n if (!orderForReview || !currentUser?.id) {\n showStatus('Order from this cart and wait for delivery to leave a review.');\n return;\n }\n createReview.mutate(\n {\n providerId: effectiveProviderId,\n orderId: orderForReview.id,\n customerId: currentUser.id,\n rating: formRating,\n review: formComment.trim() || undefined,\n },\n {\n onSuccess: () => {\n setFormComment('');\n setFormRating(5);\n showStatus('Thanks! Your review has been posted.');\n },\n onError: () => showStatus('Could not post your review. Please try again.'),\n }\n );\n };\n\n if (providersLoading) {\n return (\n <PageLayout title=\"Reviews & Ratings\">\n <div className=\"flex h-[60vh] items-center justify-center\">\n <Loader2 className=\"h-8 w-8 animate-spin text-accent-primary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (!provider) {\n return (\n <PageLayout title=\"Reviews & Ratings\">\n <EmptyState\n icon={<MessageSquare className=\"h-12 w-12\" />}\n title=\"No carts to review yet\"\n description=\"Browse carts and place an order — once it's delivered you can rate and review it.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/customer/discover')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-semibold text-action-primary-text hover:bg-action-primary-bg-hover transition-colors\"\n >\n Discover Carts\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout title=\"Reviews & Ratings\" description={`${reviews.length} reviews`}>\n {statusMessage && (\n <div className=\"mb-4 flex items-center gap-2 rounded-lg border border-status-success-text/30 bg-status-success-bg-subtle p-3\">\n <CheckCircle2 className=\"h-4 w-4 text-status-success-text shrink-0\" />\n <p className=\"text-sm text-status-success-text\">{statusMessage}</p>\n </div>\n )}\n\n <PagePurpose className=\"mb-6\">\n Honest ratings for the carts on Kadaikodi — pick a cart to see its aggregate score and every\n review, then add your own for an order you've had delivered.\n </PagePurpose>\n\n {/* Cart picker */}\n <div className=\"mb-6\">\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Viewing reviews for\n </label>\n <select\n value={effectiveProviderId}\n onChange={(e) => setSelectedProviderId(e.target.value)}\n className=\"w-full max-w-sm rounded-lg border border-border-subtle bg-bg-surface px-3 py-2.5 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {providers.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n {reviewableProviderIds.has(p.id) ? ' • ordered' : ''}\n </option>\n ))}\n </select>\n </div>\n\n {/* Aggregate — the one emphasis zone */}\n <EmphasisPanel className=\"mb-6\">\n <div className=\"flex flex-col gap-5 sm:flex-row sm:items-center\">\n <div className=\"flex items-center gap-4\">\n <div className=\"h-16 w-16 shrink-0 overflow-hidden rounded-xl\">\n <ProviderImage src={provider.image} alt={provider.name} seed={provider.name} />\n </div>\n <div>\n <p className=\"text-4xl font-bold leading-none text-text-primary\">\n {provider.rating.toFixed(1)}\n </p>\n <div className=\"mt-1.5\">\n <RatingStars value={provider.rating} size={16} />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">{provider.totalRatings} ratings</p>\n </div>\n </div>\n\n {/* Distribution 5→1 */}\n <div className=\"flex-1 space-y-1.5\">\n {[5, 4, 3, 2, 1].map((star) => {\n const count = distribution[star];\n const pct = reviews.length ? (count / reviews.length) * 100 : 0;\n return (\n <div key={star} className=\"flex items-center gap-2 text-xs\">\n <span className=\"flex w-7 items-center gap-0.5 text-text-secondary\">\n {star}\n <Star className=\"h-3 w-3 fill-status-warning-text text-status-warning-text\" />\n </span>\n <div className=\"h-2 flex-1 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-accent-primary transition-all\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"w-6 text-right tabular-nums text-text-muted\">{count}</span>\n </div>\n );\n })}\n </div>\n </div>\n </EmphasisPanel>\n\n {/* Write a review */}\n <div className=\"mb-6 rounded-xl border border-border-subtle bg-bg-surface p-5\">\n <h2 className=\"mb-3 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <PenSquare className=\"h-4 w-4 text-text-secondary\" />\n Write a review\n </h2>\n {canWrite ? (\n <div className=\"space-y-3\">\n <InteractiveStarRating value={formRating} onChange={setFormRating} />\n <textarea\n rows={3}\n value={formComment}\n onChange={(e) => setFormComment(e.target.value)}\n placeholder=\"Share your experience…\"\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2.5 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={createReview.isPending}\n className={cn(\n 'inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-semibold text-action-primary-text transition-colors',\n createReview.isPending\n ? 'opacity-60 cursor-not-allowed'\n : 'hover:bg-action-primary-bg-hover'\n )}\n >\n {createReview.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n Submit Review\n </button>\n </div>\n </div>\n ) : (\n <p className=\"rounded-lg bg-bg-sunken p-3 text-xs text-text-secondary\">\n You can review <span className=\"font-medium text-text-primary\">{provider.name}</span>{' '}\n once one of your orders from it has been delivered.{' '}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/customer/cart/${provider.id}`)}\n className=\"font-medium text-accent-primary hover:underline\"\n >\n Order now\n </button>\n </p>\n )}\n </div>\n\n {/* Filters */}\n <div className=\"mb-6 grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <div className=\"relative sm:col-span-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"text\"\n placeholder=\"Search reviews…\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2 pl-9 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <select\n value={ratingFilter}\n onChange={(e) => setRatingFilter(e.target.value)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-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 <select\n value={sortBy}\n onChange={(e) => setSortBy(e.target.value as SortBy)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"recent\">Most recent</option>\n <option value=\"rating-high\">Highest rating</option>\n <option value=\"rating-low\">Lowest rating</option>\n </select>\n </div>\n\n {/* Review list */}\n {reviewsLoading ? (\n <div className=\"flex h-40 items-center justify-center\">\n <Loader2 className=\"h-6 w-6 animate-spin text-accent-primary\" />\n </div>\n ) : filteredReviews.length === 0 ? (\n <EmptyState\n icon={<MessageSquare className=\"h-12 w-12\" />}\n title=\"No reviews yet\"\n description={`Be the first to review ${provider.name}.`}\n />\n ) : (\n <div className=\"space-y-3\">\n {filteredReviews.map((review) => {\n const isMine = review.customerId === currentUser?.id;\n return (\n <div\n key={review.id}\n className={cn(\n 'rounded-xl border bg-bg-surface p-4',\n isMine ? 'border-action-primary-bg/40' : 'border-border-subtle'\n )}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <RatingStars value={review.rating} size={14} />\n {isMine && (\n <span className=\"rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-[10px] font-medium text-accent-primary\">\n Your review\n </span>\n )}\n </div>\n <span className=\"text-xs text-text-muted\">\n {formatRelativeTime(review.createdAt)}\n </span>\n </div>\n {review.review && (\n <p className=\"mt-2 text-sm leading-relaxed text-text-primary\">{review.review}</p>\n )}\n {review.response && (\n <div className=\"mt-3 rounded-lg border-l-2 border-accent-primary bg-[var(--color-accent-soft)] p-3\">\n <p className=\"mb-1 flex items-center gap-1.5 text-xs font-medium text-text-primary\">\n <MessageCircle className=\"h-3.5 w-3.5\" />\n Owner response\n </p>\n <p className=\"text-xs leading-relaxed text-text-primary\">{review.response}</p>\n </div>\n )}\n </div>\n );\n })}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SavedAddressesPage.js","names":[],"sources":["../../../../src/kadaikodi/pages/customer/SavedAddressesPage.tsx"],"sourcesContent":["/**\n * Saved Addresses Page\n * @module kadaikodi/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} from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { useKadaikodi } from '../../context/KadaikodiContext';\nimport { cn } from '../../utils/cn';\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// Helpers\n// ============================================================================\n\nconst iconMap = {\n home: Home,\n office: Briefcase,\n other: MapPin,\n};\n\nconst typeBadgeStyles: Record<string, string> = {\n home: 'bg-status-success-bg-subtle text-status-success-text',\n office: 'bg-[var(--color-accent-soft)] text-text-primary',\n other: 'bg-accent-purple-subtle text-text-primary',\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-text-primary mb-1.5\">\n Address Type <span className=\"text-status-error-text\">*</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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-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-text-primary 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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Address Line 1 */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n Address Line 1 <span className=\"text-status-error-text\">*</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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* Address Line 2 */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary 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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-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-text-primary mb-1.5\">\n City <span className=\"text-status-error-text\">*</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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n State <span className=\"text-status-error-text\">*</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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n ZIP Code <span className=\"text-status-error-text\">*</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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-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-subtle text-text-primary\"\n />\n <span className=\"text-sm text-text-secondary\">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-subtle px-4 py-2 text-sm font-medium text-text-primary 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 } = useKadaikodi();\n const [addresses, setAddresses] = useState<SavedAddress[]>([]);\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 <PagePurpose className=\"mb-6\">\n Save the places you order to most — home, work, a friend's flat — so checkout is one tap and\n your courier always knows exactly where to go. Set a default to make it the automatic pick\n at checkout.\n </PagePurpose>\n\n {/* Summary — the one emphasis zone */}\n {addresses.length > 0 && (\n <EmphasisPanel className=\"mb-6\">\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <MapPin className=\"h-4 w-4\" />\n <span>\n {addresses.length} saved {addresses.length === 1 ? 'address' : 'addresses'}\n {addresses.some((a) => a.isDefault)\n ? ` · default set to ${addresses.find((a) => a.isDefault)?.nickname ?? 'one address'}`\n : ' · no default set yet'}\n </span>\n </div>\n </EmphasisPanel>\n )}\n\n {/* Status message */}\n {statusMessage && (\n <div className=\"mb-4 rounded-lg border border-status-success-text/30 bg-status-success-bg-subtle p-3 flex items-center gap-2\">\n <CheckCircle2 className=\"h-4 w-4 text-status-success-text shrink-0\" />\n <p className=\"text-sm text-status-success-text\">{statusMessage}</p>\n </div>\n )}\n\n {/* Inline Add Form */}\n {showAddForm && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-5 mb-6\">\n <h3 className=\"text-base font-semibold text-text-primary 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-bg-surface p-4',\n address.isDefault ? 'border-foreground/30' : 'border-border-subtle'\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-bg-sunken\">\n <Icon className=\"h-5 w-5 text-text-secondary\" />\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-status-success-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-success-text\">\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-text-primary\">\n {address.nickname}\n </p>\n )}\n\n {/* Address lines */}\n <p className=\"mt-0.5 text-sm text-text-primary\">{address.addressLine1}</p>\n {address.addressLine2 && (\n <p className=\"text-sm text-text-secondary\">{address.addressLine2}</p>\n )}\n <p className=\"text-sm text-text-secondary\">\n {address.city}, {address.state} - {address.zipCode}\n </p>\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-text-secondary hover:bg-accent hover:text-text-primary 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-text-secondary hover:bg-accent hover:text-text-primary 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-text-secondary hover:bg-status-error-bg/10 hover:text-status-error-text 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 illustration=\"empty-files\"\n icon={<MapPin className=\"h-12 w-12\" />}\n title=\"No saved addresses\"\n description=\"Add a delivery address to get started. Address book sync is coming soon — for now, entries are kept for this session.\"\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-subtle bg-bg-surface 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-text-primary\">Edit Address</h3>\n <button\n type=\"button\"\n onClick={() => setEditingAddress(null)}\n className=\"rounded-md p-2 text-text-secondary hover:bg-accent hover:text-text-primary 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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"}
|
|
1
|
+
{"version":3,"file":"SavedAddressesPage.js","names":[],"sources":["../../../../src/kadaikodi/pages/customer/SavedAddressesPage.tsx"],"sourcesContent":["/**\n * Saved Addresses Page\n * @module kadaikodi/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 { MapPin, Plus, Pencil, Trash2, CheckCircle2, Home, Briefcase, Star, X } from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { useKadaikodi } from '../../context/KadaikodiContext';\nimport { cn } from '../../utils/cn';\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// Helpers\n// ============================================================================\n\nconst iconMap = {\n home: Home,\n office: Briefcase,\n other: MapPin,\n};\n\nconst typeBadgeStyles: Record<string, string> = {\n home: 'bg-status-success-bg-subtle text-status-success-text',\n office: 'bg-[var(--color-accent-soft)] text-text-primary',\n other: 'bg-accent-purple-subtle text-text-primary',\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-text-primary mb-1.5\">\n Address Type <span className=\"text-status-error-text\">*</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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-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-text-primary 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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Address Line 1 */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n Address Line 1 <span className=\"text-status-error-text\">*</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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* Address Line 2 */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary 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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-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-text-primary mb-1.5\">\n City <span className=\"text-status-error-text\">*</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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n State <span className=\"text-status-error-text\">*</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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n ZIP Code <span className=\"text-status-error-text\">*</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-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-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-subtle text-text-primary\"\n />\n <span className=\"text-sm text-text-secondary\">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-subtle px-4 py-2 text-sm font-medium text-text-primary 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 } = useKadaikodi();\n const [addresses, setAddresses] = useState<SavedAddress[]>([]);\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 <PagePurpose className=\"mb-6\">\n Save the places you order to most — home, work, a friend's flat — so checkout is one tap and\n your courier always knows exactly where to go. Set a default to make it the automatic pick\n at checkout.\n </PagePurpose>\n\n {/* Summary — the one emphasis zone */}\n {addresses.length > 0 && (\n <EmphasisPanel className=\"mb-6\">\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <MapPin className=\"h-4 w-4\" />\n <span>\n {addresses.length} saved {addresses.length === 1 ? 'address' : 'addresses'}\n {addresses.some((a) => a.isDefault)\n ? ` · default set to ${addresses.find((a) => a.isDefault)?.nickname ?? 'one address'}`\n : ' · no default set yet'}\n </span>\n </div>\n </EmphasisPanel>\n )}\n\n {/* Status message */}\n {statusMessage && (\n <div className=\"mb-4 rounded-lg border border-status-success-text/30 bg-status-success-bg-subtle p-3 flex items-center gap-2\">\n <CheckCircle2 className=\"h-4 w-4 text-status-success-text shrink-0\" />\n <p className=\"text-sm text-status-success-text\">{statusMessage}</p>\n </div>\n )}\n\n {/* Inline Add Form */}\n {showAddForm && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-5 mb-6\">\n <h3 className=\"text-base font-semibold text-text-primary 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-bg-surface p-4',\n address.isDefault ? 'border-foreground/30' : 'border-border-subtle'\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-bg-sunken\">\n <Icon className=\"h-5 w-5 text-text-secondary\" />\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-status-success-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-success-text\">\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-text-primary\">\n {address.nickname}\n </p>\n )}\n\n {/* Address lines */}\n <p className=\"mt-0.5 text-sm text-text-primary\">{address.addressLine1}</p>\n {address.addressLine2 && (\n <p className=\"text-sm text-text-secondary\">{address.addressLine2}</p>\n )}\n <p className=\"text-sm text-text-secondary\">\n {address.city}, {address.state} - {address.zipCode}\n </p>\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-text-secondary hover:bg-accent hover:text-text-primary 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-text-secondary hover:bg-accent hover:text-text-primary 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-text-secondary hover:bg-status-error-bg/10 hover:text-status-error-text 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 illustration=\"empty-files\"\n icon={<MapPin className=\"h-12 w-12\" />}\n title=\"No saved addresses\"\n description=\"Add a delivery address to get started. Address book sync is coming soon — for now, entries are kept for this session.\"\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-subtle bg-bg-surface 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-text-primary\">Edit Address</h3>\n <button\n type=\"button\"\n onClick={() => setEditingAddress(null)}\n className=\"rounded-md p-2 text-text-secondary hover:bg-accent hover:text-text-primary 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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"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { useCallback as t, useMemo as n, useState as r, useSyncExternalStore as i } from "react";
|
|
3
|
+
import { Home as a, MapPin as o, Navigation as s, Store as c } from "lucide-react";
|
|
4
|
+
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
5
|
+
//#region src/kadaikodi/pages/customer/shared.tsx
|
|
6
|
+
function d({ src: t, alt: n, seed: i, className: a, icon: o }) {
|
|
7
|
+
let [s, d] = r(!1);
|
|
8
|
+
if (t && !s) return /* @__PURE__ */ l("img", {
|
|
9
|
+
src: t,
|
|
10
|
+
alt: n,
|
|
11
|
+
loading: "lazy",
|
|
12
|
+
onError: () => d(!0),
|
|
13
|
+
className: e("h-full w-full object-cover", a)
|
|
14
|
+
});
|
|
15
|
+
let f = (i ?? n).trim().charAt(0).toUpperCase() || "?";
|
|
16
|
+
return /* @__PURE__ */ l("div", {
|
|
17
|
+
"aria-label": n,
|
|
18
|
+
role: "img",
|
|
19
|
+
className: e("flex h-full w-full items-center justify-center bg-gradient-to-br from-accent-primary/25 via-accent-primary/10 to-bg-sunken", a),
|
|
20
|
+
children: /* @__PURE__ */ u("div", {
|
|
21
|
+
className: "flex flex-col items-center gap-1 text-accent-primary/70",
|
|
22
|
+
children: [o ?? /* @__PURE__ */ l(c, {
|
|
23
|
+
className: "h-6 w-6",
|
|
24
|
+
"aria-hidden": "true"
|
|
25
|
+
}), /* @__PURE__ */ l("span", {
|
|
26
|
+
className: "text-lg font-bold leading-none",
|
|
27
|
+
children: f
|
|
28
|
+
})]
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function f(e) {
|
|
33
|
+
return /* @__PURE__ */ l(d, {
|
|
34
|
+
...e,
|
|
35
|
+
icon: /* @__PURE__ */ l(c, {
|
|
36
|
+
className: "h-6 w-6",
|
|
37
|
+
"aria-hidden": "true"
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
var p = "kadaikodi:favorites:providers", m = /* @__PURE__ */ new Set();
|
|
42
|
+
function h() {
|
|
43
|
+
return typeof localStorage > "u" ? "[]" : localStorage.getItem(p) ?? "[]";
|
|
44
|
+
}
|
|
45
|
+
function g(e) {
|
|
46
|
+
try {
|
|
47
|
+
let t = JSON.parse(e);
|
|
48
|
+
return Array.isArray(t) ? t.filter((e) => typeof e == "string") : [];
|
|
49
|
+
} catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function _(e) {
|
|
54
|
+
if (typeof localStorage > "u") return;
|
|
55
|
+
let t = g(h()), n = t.includes(e) ? t.filter((t) => t !== e) : [...t, e];
|
|
56
|
+
localStorage.setItem(p, JSON.stringify(n)), m.forEach((e) => e());
|
|
57
|
+
}
|
|
58
|
+
var v = (e) => {
|
|
59
|
+
m.add(e);
|
|
60
|
+
let t = (t) => {
|
|
61
|
+
t.key === p && e();
|
|
62
|
+
};
|
|
63
|
+
return typeof window < "u" && window.addEventListener("storage", t), () => {
|
|
64
|
+
m.delete(e), typeof window < "u" && window.removeEventListener("storage", t);
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
function y() {
|
|
68
|
+
let e = i(v, h, () => "[]"), r = n(() => g(e), [e]);
|
|
69
|
+
return {
|
|
70
|
+
ids: r,
|
|
71
|
+
has: t((e) => r.includes(e), [r]),
|
|
72
|
+
toggle: _
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function b({ progress: t = .5, originLabel: n = "Cart", destinationLabel: r = "You", className: i }) {
|
|
76
|
+
let c = Math.min(1, Math.max(0, t)), d = {
|
|
77
|
+
x: 60,
|
|
78
|
+
y: 168
|
|
79
|
+
}, f = {
|
|
80
|
+
x: 340,
|
|
81
|
+
y: 56
|
|
82
|
+
}, p = {
|
|
83
|
+
x: 150,
|
|
84
|
+
y: 60
|
|
85
|
+
}, m = {
|
|
86
|
+
x: 250,
|
|
87
|
+
y: 200
|
|
88
|
+
}, h = c, g = 1 - h, _ = g * g * g * d.x + 3 * g * g * h * p.x + 3 * g * h * h * m.x + h * h * h * f.x, v = g * g * g * d.y + 3 * g * g * h * p.y + 3 * g * h * h * m.y + h * h * h * f.y;
|
|
89
|
+
return /* @__PURE__ */ u("div", {
|
|
90
|
+
className: e("relative overflow-hidden rounded-xl border border-border-subtle bg-bg-sunken", i),
|
|
91
|
+
children: [
|
|
92
|
+
/* @__PURE__ */ u("svg", {
|
|
93
|
+
viewBox: "0 0 400 220",
|
|
94
|
+
className: "h-full w-full",
|
|
95
|
+
role: "img",
|
|
96
|
+
"aria-label": "Illustrative delivery route",
|
|
97
|
+
children: [
|
|
98
|
+
/* @__PURE__ */ u("g", {
|
|
99
|
+
stroke: "var(--color-border-subtle)",
|
|
100
|
+
strokeWidth: "1",
|
|
101
|
+
opacity: "0.6",
|
|
102
|
+
children: [/* @__PURE__ */ l("path", { d: "M0 40 H400 M0 90 H400 M0 140 H400 M0 190 H400" }), /* @__PURE__ */ l("path", { d: "M70 0 V220 M150 0 V220 M230 0 V220 M310 0 V220" })]
|
|
103
|
+
}),
|
|
104
|
+
/* @__PURE__ */ l("path", {
|
|
105
|
+
d: `M${d.x} ${d.y} C ${p.x} ${p.y}, ${m.x} ${m.y}, ${f.x} ${f.y}`,
|
|
106
|
+
fill: "none",
|
|
107
|
+
stroke: "var(--color-accent-primary)",
|
|
108
|
+
strokeWidth: "3",
|
|
109
|
+
strokeLinecap: "round",
|
|
110
|
+
strokeDasharray: "2 8",
|
|
111
|
+
opacity: "0.9"
|
|
112
|
+
}),
|
|
113
|
+
/* @__PURE__ */ l("circle", {
|
|
114
|
+
cx: d.x,
|
|
115
|
+
cy: d.y,
|
|
116
|
+
r: "16",
|
|
117
|
+
fill: "var(--color-accent-primary)",
|
|
118
|
+
opacity: "0.12"
|
|
119
|
+
}),
|
|
120
|
+
/* @__PURE__ */ l("circle", {
|
|
121
|
+
cx: d.x,
|
|
122
|
+
cy: d.y,
|
|
123
|
+
r: "6",
|
|
124
|
+
fill: "var(--color-accent-primary)"
|
|
125
|
+
}),
|
|
126
|
+
/* @__PURE__ */ l("circle", {
|
|
127
|
+
cx: f.x,
|
|
128
|
+
cy: f.y,
|
|
129
|
+
r: "9",
|
|
130
|
+
fill: "var(--color-status-success-text)"
|
|
131
|
+
}),
|
|
132
|
+
/* @__PURE__ */ l("circle", {
|
|
133
|
+
cx: _,
|
|
134
|
+
cy: v,
|
|
135
|
+
r: "12",
|
|
136
|
+
fill: "var(--color-accent-primary)",
|
|
137
|
+
opacity: "0.18",
|
|
138
|
+
children: /* @__PURE__ */ l("animate", {
|
|
139
|
+
attributeName: "r",
|
|
140
|
+
values: "10;16;10",
|
|
141
|
+
dur: "2.4s",
|
|
142
|
+
repeatCount: "indefinite"
|
|
143
|
+
})
|
|
144
|
+
}),
|
|
145
|
+
/* @__PURE__ */ l("circle", {
|
|
146
|
+
cx: _,
|
|
147
|
+
cy: v,
|
|
148
|
+
r: "6",
|
|
149
|
+
fill: "var(--color-accent-primary)",
|
|
150
|
+
stroke: "var(--color-bg-surface)",
|
|
151
|
+
strokeWidth: "2"
|
|
152
|
+
})
|
|
153
|
+
]
|
|
154
|
+
}),
|
|
155
|
+
/* @__PURE__ */ u("span", {
|
|
156
|
+
className: "absolute bottom-2 left-2 inline-flex items-center gap-1 rounded-full bg-bg-surface/80 px-2 py-0.5 text-[10px] font-medium text-text-secondary backdrop-blur-sm",
|
|
157
|
+
children: [
|
|
158
|
+
/* @__PURE__ */ l(o, { className: "h-3 w-3 text-accent-primary" }),
|
|
159
|
+
" ",
|
|
160
|
+
n
|
|
161
|
+
]
|
|
162
|
+
}),
|
|
163
|
+
/* @__PURE__ */ u("span", {
|
|
164
|
+
className: "absolute right-2 top-2 inline-flex items-center gap-1 rounded-full bg-bg-surface/80 px-2 py-0.5 text-[10px] font-medium text-text-secondary backdrop-blur-sm",
|
|
165
|
+
children: [
|
|
166
|
+
/* @__PURE__ */ l(a, { className: "h-3 w-3 text-status-success-text" }),
|
|
167
|
+
" ",
|
|
168
|
+
r
|
|
169
|
+
]
|
|
170
|
+
}),
|
|
171
|
+
/* @__PURE__ */ u("span", {
|
|
172
|
+
className: "absolute right-2 bottom-2 inline-flex items-center gap-1 rounded-full bg-bg-surface/70 px-2 py-0.5 text-[10px] text-text-muted backdrop-blur-sm",
|
|
173
|
+
children: [/* @__PURE__ */ l(s, { className: "h-3 w-3" }), " Illustrative route"]
|
|
174
|
+
})
|
|
175
|
+
]
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
export { f as ProviderImage, b as StylizedRouteMap, y as useFavorites };
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","names":[],"sources":["../../../../src/kadaikodi/pages/customer/shared.tsx"],"sourcesContent":["/**\n * Shared presentational helpers for the customer pages.\n * @module kadaikodi/pages/customer\n *\n * - `ProviderImage` / `OfferingImage`: render a real media image when the\n * backend supplies one, otherwise a tokenized, themeable placeholder (never a\n * broken <img> and never a fabricated stock photo).\n * - `useFavorites`: a real, user-owned favorites store persisted in\n * localStorage and shared reactively across the customer pages (Discover adds,\n * Favorites reads). This is genuine user data — nothing is fabricated.\n * - `StylizedRouteMap`: an honest, clearly-labelled illustrative route panel for\n * order tracking (there is no live-GPS backend, so we never fake live coords).\n */\n\nimport {\n useCallback,\n useMemo,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from 'react';\nimport { Store, MapPin, Navigation, Home } from 'lucide-react';\nimport { cn } from '../../utils/cn';\n\n// ============================================================================\n// Media with graceful, tokenized fallback\n// ============================================================================\n\ninterface ImageWithFallbackProps {\n src?: string | null;\n alt: string;\n /** Seed text for the placeholder initial (usually the name). */\n seed?: string;\n className?: string;\n /** Icon shown in the placeholder (defaults to a storefront). */\n icon?: ReactNode;\n}\n\n/**\n * Renders `src` when present and loadable; otherwise a tokenized accent-gradient\n * placeholder with the seed's initial. Fills its positioned parent.\n */\nexport function ImageWithFallback({ src, alt, seed, className, icon }: ImageWithFallbackProps) {\n const [failed, setFailed] = useState(false);\n const showImage = Boolean(src) && !failed;\n\n if (showImage) {\n return (\n <img\n src={src as string}\n alt={alt}\n loading=\"lazy\"\n onError={() => setFailed(true)}\n className={cn('h-full w-full object-cover', className)}\n />\n );\n }\n\n const initial = (seed ?? alt).trim().charAt(0).toUpperCase() || '?';\n return (\n <div\n aria-label={alt}\n role=\"img\"\n className={cn(\n 'flex h-full w-full items-center justify-center bg-gradient-to-br from-accent-primary/25 via-accent-primary/10 to-bg-sunken',\n className\n )}\n >\n <div className=\"flex flex-col items-center gap-1 text-accent-primary/70\">\n {icon ?? <Store className=\"h-6 w-6\" aria-hidden=\"true\" />}\n <span className=\"text-lg font-bold leading-none\">{initial}</span>\n </div>\n </div>\n );\n}\n\n/** Convenience wrapper for provider/cart imagery. */\nexport function ProviderImage(props: Omit<ImageWithFallbackProps, 'icon'>) {\n return <ImageWithFallback {...props} icon={<Store className=\"h-6 w-6\" aria-hidden=\"true\" />} />;\n}\n\n// ============================================================================\n// Favorites — real localStorage-backed store, reactive across pages\n// ============================================================================\n\nconst FAVORITES_KEY = 'kadaikodi:favorites:providers';\nconst listeners = new Set<() => void>();\n\nfunction readRaw(): string {\n if (typeof localStorage === 'undefined') return '[]';\n return localStorage.getItem(FAVORITES_KEY) ?? '[]';\n}\n\nfunction parse(raw: string): string[] {\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed) ? parsed.filter((x): x is string => typeof x === 'string') : [];\n } catch {\n return [];\n }\n}\n\n/** Toggle a provider id in the favorites store and notify subscribers. */\nexport function toggleFavorite(id: string): void {\n if (typeof localStorage === 'undefined') return;\n const current = parse(readRaw());\n const next = current.includes(id) ? current.filter((x) => x !== id) : [...current, id];\n localStorage.setItem(FAVORITES_KEY, JSON.stringify(next));\n listeners.forEach((l) => l());\n}\n\nconst subscribe = (cb: () => void): (() => void) => {\n listeners.add(cb);\n const onStorage = (e: StorageEvent): void => {\n if (e.key === FAVORITES_KEY) cb();\n };\n if (typeof window !== 'undefined') window.addEventListener('storage', onStorage);\n return () => {\n listeners.delete(cb);\n if (typeof window !== 'undefined') window.removeEventListener('storage', onStorage);\n };\n};\n\n/** Reactive access to the customer's saved-provider ids. */\nexport function useFavorites(): {\n ids: string[];\n has: (id: string) => boolean;\n toggle: (id: string) => void;\n} {\n const raw = useSyncExternalStore(subscribe, readRaw, () => '[]');\n const ids = useMemo(() => parse(raw), [raw]);\n const has = useCallback((id: string) => ids.includes(id), [ids]);\n return { ids, has, toggle: toggleFavorite };\n}\n\n// ============================================================================\n// Stylised route map (honest illustration — no live GPS backend)\n// ============================================================================\n\ninterface StylizedRouteMapProps {\n /** 0–1 progress of the courier along the illustrative route. */\n progress?: number;\n originLabel?: string;\n destinationLabel?: string;\n className?: string;\n}\n\n/**\n * A tokenized, animated-dashed SVG \"route\" between the cart and the delivery\n * address. It is explicitly labelled as illustrative — Kadaikodi has no live-GPS\n * feed, so this never pretends to show a real courier position.\n */\nexport function StylizedRouteMap({\n progress = 0.5,\n originLabel = 'Cart',\n destinationLabel = 'You',\n className,\n}: StylizedRouteMapProps) {\n const clamped = Math.min(1, Math.max(0, progress));\n // Path points in the 0..400 x 0..220 viewBox.\n const start = { x: 60, y: 168 };\n const end = { x: 340, y: 56 };\n const c1 = { x: 150, y: 60 };\n const c2 = { x: 250, y: 200 };\n const t = clamped;\n const mt = 1 - t;\n // Cubic bezier point at t for the courier marker.\n const cx =\n mt * mt * mt * start.x +\n 3 * mt * mt * t * c1.x +\n 3 * mt * t * t * c2.x +\n t * t * t * end.x;\n const cy =\n mt * mt * mt * start.y +\n 3 * mt * mt * t * c1.y +\n 3 * mt * t * t * c2.y +\n t * t * t * end.y;\n\n return (\n <div\n className={cn(\n 'relative overflow-hidden rounded-xl border border-border-subtle bg-bg-sunken',\n className\n )}\n >\n <svg viewBox=\"0 0 400 220\" className=\"h-full w-full\" role=\"img\" aria-label=\"Illustrative delivery route\">\n {/* Faint \"streets\" grid */}\n <g stroke=\"var(--color-border-subtle)\" strokeWidth=\"1\" opacity=\"0.6\">\n <path d=\"M0 40 H400 M0 90 H400 M0 140 H400 M0 190 H400\" />\n <path d=\"M70 0 V220 M150 0 V220 M230 0 V220 M310 0 V220\" />\n </g>\n {/* Route */}\n <path\n d={`M${start.x} ${start.y} C ${c1.x} ${c1.y}, ${c2.x} ${c2.y}, ${end.x} ${end.y}`}\n fill=\"none\"\n stroke=\"var(--color-accent-primary)\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeDasharray=\"2 8\"\n opacity=\"0.9\"\n />\n {/* Origin (cart) */}\n <circle cx={start.x} cy={start.y} r=\"16\" fill=\"var(--color-accent-primary)\" opacity=\"0.12\" />\n <circle cx={start.x} cy={start.y} r=\"6\" fill=\"var(--color-accent-primary)\" />\n {/* Destination (home) */}\n <circle cx={end.x} cy={end.y} r=\"9\" fill=\"var(--color-status-success-text)\" />\n {/* Courier marker */}\n <circle cx={cx} cy={cy} r=\"12\" fill=\"var(--color-accent-primary)\" opacity=\"0.18\">\n <animate attributeName=\"r\" values=\"10;16;10\" dur=\"2.4s\" repeatCount=\"indefinite\" />\n </circle>\n <circle cx={cx} cy={cy} r=\"6\" fill=\"var(--color-accent-primary)\" stroke=\"var(--color-bg-surface)\" strokeWidth=\"2\" />\n </svg>\n\n {/* Corner labels */}\n <span className=\"absolute bottom-2 left-2 inline-flex items-center gap-1 rounded-full bg-bg-surface/80 px-2 py-0.5 text-[10px] font-medium text-text-secondary backdrop-blur-sm\">\n <MapPin className=\"h-3 w-3 text-accent-primary\" /> {originLabel}\n </span>\n <span className=\"absolute right-2 top-2 inline-flex items-center gap-1 rounded-full bg-bg-surface/80 px-2 py-0.5 text-[10px] font-medium text-text-secondary backdrop-blur-sm\">\n <Home className=\"h-3 w-3 text-status-success-text\" /> {destinationLabel}\n </span>\n <span className=\"absolute right-2 bottom-2 inline-flex items-center gap-1 rounded-full bg-bg-surface/70 px-2 py-0.5 text-[10px] text-text-muted backdrop-blur-sm\">\n <Navigation className=\"h-3 w-3\" /> Illustrative route\n </span>\n </div>\n );\n}\n"],"mappings":"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"}
|