@burdenoff/microfe-kadaikodi 2026.704.2 → 2026.704.4
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 +30 -26
- package/dist/kadaikodi/KadaikodiRoutes.js.map +1 -1
- package/dist/kadaikodi/hooks/useKadaikodiApi.js +76 -6
- package/dist/kadaikodi/hooks/useKadaikodiApi.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminAnalyticsPage.js +45 -107
- package/dist/kadaikodi/pages/admin/AdminAnalyticsPage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminDashboard.js +30 -32
- package/dist/kadaikodi/pages/admin/AdminDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminFinancialPage.js +137 -120
- package/dist/kadaikodi/pages/admin/AdminFinancialPage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminOrdersPage.js +2 -2
- 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 +278 -283
- 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 +105 -111
- 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 +84 -70
- 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 +202 -139
- package/dist/kadaikodi/pages/cart-owner/InventoryManagementPage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/InvestmentListingPage.js +232 -0
- package/dist/kadaikodi/pages/cart-owner/InvestmentListingPage.js.map +1 -0
- package/dist/kadaikodi/pages/cart-owner/OrderManagementPage.js +84 -79
- package/dist/kadaikodi/pages/cart-owner/OrderManagementPage.js.map +1 -1
- 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 +243 -143
- package/dist/kadaikodi/pages/customer/BookingDetailsPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CartDetailPage.js +4 -4
- package/dist/kadaikodi/pages/customer/CartDetailPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CartDiscoveryPage.js +2 -2
- package/dist/kadaikodi/pages/customer/CartDiscoveryPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CheckoutPage.js +4 -4
- package/dist/kadaikodi/pages/customer/CheckoutPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CustomerDashboard.js +6 -6
- 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 +6 -4
- package/dist/kadaikodi/pages/customer/DirectServiceBookingPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/FavoritesPage.js +6 -6
- package/dist/kadaikodi/pages/customer/FavoritesPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/OrderHistoryPage.js +4 -4
- package/dist/kadaikodi/pages/customer/OrderHistoryPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/OrderTrackingPage.js +409 -304
- package/dist/kadaikodi/pages/customer/OrderTrackingPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/ReviewsPage.js +209 -162
- package/dist/kadaikodi/pages/customer/ReviewsPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/SavedAddressesPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestmentDetailsPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestmentOpportunitiesPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestorAnalyticsPage.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 +207 -186
- package/dist/kadaikodi/pages/worker/TaskDetailsPage.js.map +1 -1
- package/dist/kadaikodi/pages/worker/WorkerOrdersPage.js.map +1 -1
- package/dist/kadaikodi/pages/worker/WorkerProfilePage.js +340 -272
- package/dist/kadaikodi/pages/worker/WorkerProfilePage.js.map +1 -1
- package/dist/kadaikodi/pages/worker/WorkerSchedulePage.js +122 -75
- 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 * 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 { useOrders, useCreateReview } from '../../hooks/useKadaikodiApi';\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 { data: myOrders = [] } = useOrders({ customerId: currentUser?.id });\n const createReview = useCreateReview();\n // Reviews are attached to a delivered order + its provider. Only delivered\n // orders can be reviewed, so those are the options the customer picks from.\n const reviewableOrders = myOrders.filter((o) => o.status === 'delivered');\n const [reviews, setReviews] = useState<Review[]>([]);\n const [showWriteReview, setShowWriteReview] = useState(false);\n const [selectedOrderId, setSelectedOrderId] = useState('');\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 (!selectedOrderId) {\n showStatus('Please choose which delivered order you want to review.');\n return;\n }\n if (!reviewForm.title.trim() || !reviewForm.comment.trim()) {\n showStatus('Please fill in the Review Title and Comment.');\n return;\n }\n const targetOrder = reviewableOrders.find((o) => o.id === selectedOrderId);\n if (!targetOrder || !currentUser?.id) {\n showStatus('Could not find that order. Please try again.');\n return;\n }\n\n // Reviews attach to a specific provider + delivered order — persist via the\n // real createReview mutation.\n createReview.mutate(\n {\n providerId: targetOrder.cartId,\n orderId: targetOrder.id,\n customerId: currentUser.id,\n rating: reviewForm.rating,\n review: `${reviewForm.title.trim()} — ${reviewForm.comment.trim()}`,\n },\n {\n onSuccess: () => {\n setReviewForm({ rating: 5, title: '', comment: '' });\n setSelectedOrderId('');\n setShowWriteReview(false);\n showStatus('Thanks! Your review has been posted to the cart.');\n },\n onError: () => showStatus('Could not post your review. Please try again.'),\n }\n );\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/cart/${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 {/* Order selection — reviews attach to a delivered order */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n Which order? <span className=\"text-status-error-text\">*</span>\n </label>\n {reviewableOrders.length > 0 ? (\n <select\n value={selectedOrderId}\n onChange={(e) => setSelectedOrderId(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=\"\">Select a delivered order…</option>\n {reviewableOrders.map((o) => (\n <option key={o.id} value={o.id}>\n {o.cartName} · {formatDate(o.orderDate)}\n </option>\n ))}\n </select>\n ) : (\n <p className=\"rounded-lg bg-bg-sunken p-3 text-xs text-text-secondary\">\n You can review a cart once one of your orders has been delivered. Place an order\n and come back after it arrives.\n </p>\n )}\n </div>\n\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 disabled={createReview.isPending || reviewableOrders.length === 0}\n className={cn(\n 'rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background transition-opacity',\n createReview.isPending || reviewableOrders.length === 0\n ? 'opacity-60 cursor-not-allowed'\n : 'hover:opacity-90'\n )}\n >\n {createReview.isPending ? 'Posting…' : '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 +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":";;;;;;;;;AA+CA,IAAM,IAA6B;CACjC,MAAM;CACN,UAAU;CACV,cAAc;CACd,cAAc;CACd,MAAM;CACN,OAAO;CACP,SAAS;CACT,WAAW;CACZ,EAMK,IAAU;CACd,MAAM;CACN,QAAQ;CACR,OAAO;CACR,EAEK,IAA0C;CAC9C,MAAM;CACN,QAAQ;CACR,OAAO;CACR;AAMD,SAAS,EAAY,EACnB,SACA,aACA,aACA,aACA,kBAOC;AACD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,WAAU;eAAjB,CAAsE,iBACvD,kBAAC,QAAD;MAAM,WAAU;gBAAyB;MAAQ,CAAA,CACxD;QACR,kBAAC,UAAD;KACE,OAAO,EAAK;KACZ,WAAW,MAAM,EAAS,QAAQ,EAAE,OAAO,MAAM;KACjD,WAAU;eAHZ;MAKE,kBAAC,UAAD;OAAQ,OAAM;iBAAO;OAAa,CAAA;MAClC,kBAAC,UAAD;OAAQ,OAAM;iBAAS;OAAe,CAAA;MACtC,kBAAC,UAAD;OAAQ,OAAM;iBAAQ;OAAc,CAAA;MAC7B;OACL,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,WAAU;eAAqD;KAAgB,CAAA,EACtF,kBAAC,SAAD;KACE,MAAK;KACL,aAAY;KACZ,OAAO,EAAK;KACZ,WAAW,MAAM,EAAS,YAAY,EAAE,OAAO,MAAM;KACrD,WAAU;KACV,CAAA,CACE,EAAA,CAAA,CACF;;GAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IAAO,WAAU;cAAjB,CAAsE,mBACrD,kBAAC,QAAD;KAAM,WAAU;eAAyB;KAAQ,CAAA,CAC1D;OACR,kBAAC,SAAD;IACE,MAAK;IACL,aAAY;IACZ,OAAO,EAAK;IACZ,WAAW,MAAM,EAAS,gBAAgB,EAAE,OAAO,MAAM;IACzD,WAAU;IACV,CAAA,CACE,EAAA,CAAA;GAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IAAO,WAAU;cAAqD;IAAsB,CAAA,EAC5F,kBAAC,SAAD;IACE,MAAK;IACL,aAAY;IACZ,OAAO,EAAK;IACZ,WAAW,MAAM,EAAS,gBAAgB,EAAE,OAAO,MAAM;IACzD,WAAU;IACV,CAAA,CACE,EAAA,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CAAsE,SAC/D,kBAAC,QAAD;OAAM,WAAU;iBAAyB;OAAQ,CAAA,CAChD;SACR,kBAAC,SAAD;MACE,MAAK;MACL,aAAY;MACZ,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS,QAAQ,EAAE,OAAO,MAAM;MACjD,WAAU;MACV,CAAA,CACE,EAAA,CAAA;KACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CAAsE,UAC9D,kBAAC,QAAD;OAAM,WAAU;iBAAyB;OAAQ,CAAA,CACjD;SACR,kBAAC,SAAD;MACE,MAAK;MACL,aAAY;MACZ,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS,SAAS,EAAE,OAAO,MAAM;MAClD,WAAU;MACV,CAAA,CACE,EAAA,CAAA;KACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CAAsE,aAC3D,kBAAC,QAAD;OAAM,WAAU;iBAAyB;OAAQ,CAAA,CACpD;SACR,kBAAC,SAAD;MACE,MAAK;MACL,aAAY;MACZ,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS,WAAW,EAAE,OAAO,MAAM;MACpD,WAAU;MACV,CAAA,CACE,EAAA,CAAA;KACF;;GAGN,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,SAAD;KACE,MAAK;KACL,SAAS,EAAK;KACd,WAAW,MAAM,EAAS,aAAa,EAAE,OAAO,QAAQ;KACxD,WAAU;KACV,CAAA,EACF,kBAAC,QAAD;KAAM,WAAU;eAA8B;KAA6B,CAAA,CACrE;;GAGR,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAET;KACM,CAAA,EACT,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eACX;KAEQ,CAAA,CACL;;GACF;;;AAQV,SAAgB,IAAqB;CACnC,IAAM,EAAE,kBAAe,GAAc,EAC/B,CAAC,GAAW,KAAgB,EAAyB,EAAE,CAAC,EACxD,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAgB,KAAqB,EAA8B,KAAK,EACzE,CAAC,GAAY,KAAiB,EAA0B,EAAE,GAAG,GAAW,CAAC,EACzE,CAAC,GAAe,KAAoB,EAAwB,KAAK,EAGjE,KAAc,MAAgB;AAElC,EADA,EAAiB,EAAI,EACrB,iBAAiB,EAAiB,KAAK,EAAE,IAAK;IAG1C,KAAgB,MAElB,CAAC,EAAK,aAAa,MAAM,IACzB,CAAC,EAAK,KAAK,MAAM,IACjB,CAAC,EAAK,MAAM,MAAM,IAClB,CAAC,EAAK,QAAQ,MAAM,IAEpB,MAAM,8EAA8E,EAC7E,MAEF,IAIH,UAAyB;AAC7B,MAAI,CAAC,EAAa,EAAW,CAAE;EAE/B,IAAM,IAA6B;GACjC,IAAI,QAAQ,KAAK,KAAK;GACtB,MAAM,EAAW;GACjB,UAAU,EAAW;GACrB,cAAc,EAAW;GACzB,cAAc,EAAW;GACzB,MAAM,EAAW;GACjB,OAAO,EAAW;GAClB,SAAS,EAAW;GACpB,WAAW,EAAW;GACtB,YAAY;GACb;AAYD,EAVA,GAAc,MAAS;GACrB,IAAI,IAAU,CAAC,GAAG,EAAK;AAIvB,UAHI,EAAa,cACf,IAAU,EAAQ,KAAK,OAAO;IAAE,GAAG;IAAG,WAAW;IAAO,EAAE,GAErD,CAAC,GAAG,GAAS,EAAa;IACjC,EAEF,EAAc,EAAE,GAAG,GAAW,CAAC,EAC/B,EAAe,GAAM,EACrB,EAAW,6BAA6B;IAGpC,UAAuB;AACtB,OACA,EAAa,EAAe,KAEjC,GAAc,MACZ,EAAK,KAAK,MACJ,EAAE,OAAO,EAAe,KAAW,IACnC,EAAe,YAAkB;GAAE,GAAG;GAAG,WAAW;GAAO,GACxD,EACP,CACH,EACD,EAAkB,KAAK,EACvB,EAAW,+BAA+B;IAGtC,KAAgB,MAAe;AAEnC,MADa,EAAU,MAAM,MAAM,EAAE,OAAO,EAAG,EACrC,WAAW;AACnB,SAAM,uEAAuE;AAC7E;;AAGF,EADA,GAAc,MAAS,EAAK,QAAQ,MAAM,EAAE,OAAO,EAAG,CAAC,EACvD,EAAW,+BAA+B;IAGtC,KAAoB,MAAe;AAEvC,EADA,GAAc,MAAS,EAAK,KAAK,OAAO;GAAE,GAAG;GAAG,WAAW,EAAE,OAAO;GAAI,EAAE,CAAC,EAC3E,EAAW,0BAA0B;IAGjC,KACJ,GACA,GACA,MACG;AACH,KAAQ,OAAe;GAAE,GAAG;IAAO,IAAQ;GAAO,EAAE;;AAGtD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACG,IAYG,KAAA,IAXF,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAe,GAAK;GACnC,WAAW,EACT,uFACA,iDACD;aANH,CAQE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,kBAErB;;YAff;GAmBE,kBAAC,GAAD;IAAa,WAAU;cAAO;IAIhB,CAAA;GAGb,EAAU,SAAS,KAClB,kBAAC,GAAD;IAAe,WAAU;cACvB,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAC9B,kBAAC,QAAD,EAAA,UAAA;MACG,EAAU;MAAO;MAAQ,EAAU,WAAW,IAAI,YAAY;MAC9D,EAAU,MAAM,MAAM,EAAE,UAAU,GAC/B,qBAAqB,EAAU,MAAM,MAAM,EAAE,UAAU,EAAE,YAAY,kBACrE;MACC,EAAA,CAAA,CACH;;IACQ,CAAA;GAIjB,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAc,WAAU,6CAA8C,CAAA,EACtE,kBAAC,KAAD;KAAG,WAAU;eAAoC;KAAkB,CAAA,CAC/D;;GAIP,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAiD;KAAoB,CAAA,EACnF,kBAAC,GAAD;KACE,MAAM;KACN,WAAW,GAAO,MAAU,EAAiB,GAAe,GAAO,EAAM;KACzE,UAAU;KACV,gBAAgB;AAEd,MADA,EAAe,GAAM,EACrB,EAAc,EAAE,GAAG,GAAW,CAAC;;KAEjC,aAAY;KACZ,CAAA,CACE;;GAIP,EAAU,SAAS,IAClB,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAU,KAAK,MAAY;KAC1B,IAAM,IAAO,EAAQ,EAAQ;AAC7B,YACE,kBAAC,OAAD;MAEE,WAAW,EACT,uCACA,EAAQ,YAAY,yBAAyB,uBAC9C;gBAED,kBAAC,OAAD;OAAK,WAAU;iBAAf,CAEE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAM,WAAU,+BAAgC,CAAA;SAC5C,CAAA,EACN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UAEE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD;YACE,WAAW,EACT,wFACA,EAAgB,EAAQ,MACzB;sBAEA,EAAQ;YACJ,CAAA,EACN,EAAQ,aACP,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CACE,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA,EAAA,UAE/B;cAEL;;UAGL,EAAQ,YACP,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAQ;WACP,CAAA;UAIN,kBAAC,KAAD;WAAG,WAAU;qBAAoC,EAAQ;WAAiB,CAAA;UACzE,EAAQ,gBACP,kBAAC,KAAD;WAAG,WAAU;qBAA+B,EAAQ;WAAiB,CAAA;UAEvE,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAQ;YAAK;YAAG,EAAQ;YAAM;YAAI,EAAQ;YACzC;;UACA;WACF;WAGN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACG,CAAC,EAAQ,aACR,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAiB,EAAQ,GAAG;UAC3C,OAAM;UACN,WAAU;oBAEV,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;UACrB,CAAA;SAEX,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAkB,EAAE,GAAG,GAAS,CAAC;UAChD,OAAM;UACN,WAAU;oBAEV,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;UACvB,CAAA;SACT,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAa,EAAQ,GAAG;UACvC,OAAM;UACN,WAAU;oBAEV,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;UACvB,CAAA;SACL;UACF;;MACF,EA/EC,EAAQ,GA+ET;MAER;IACE,CAAA,GAEN,kBAAC,GAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAQ,WAAU,aAAc,CAAA;IACtC,OAAM;IACN,aAAY;IACZ,QACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAe,GAAK;KACnC,WAAU;eACX;KAEQ,CAAA;IAEX,CAAA;GAIH,KACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAU;gBAAf,CAEE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAAiB,CAAA,EACzE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAkB,KAAK;QACtC,WAAU;kBAEV,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;QAClB,CAAA,CACL;UAEN,kBAAC,GAAD;OACE,MAAM;OACN,WAAW,GAAO,MAAU,EAAiB,GAAmB,GAAO,EAAM;OAC7E,UAAU;OACV,gBAAgB,EAAkB,KAAK;OACvC,aAAY;OACZ,CAAA,CACE;;KACF,CAAA;IACF,CAAA;GAEG"}
|
|
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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvestmentDetailsPage.js","names":[],"sources":["../../../../src/kadaikodi/pages/investor/InvestmentDetailsPage.tsx"],"sourcesContent":["/**\n * Investment Details Page\n * @module kadaikodi/pages/investor\n *\n * Serves both /investor/opportunity/:id and /investor/investments/:id. Resolves\n * the opportunity (and the investor's holding, if any) from the live backend by\n * route id — no fabricated business figures, documents or updates.\n */\n\nimport { useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n TrendingUp,\n Shield,\n Users,\n IndianRupee,\n BarChart3,\n PieChart,\n Building2,\n Target,\n Clock,\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 { useInvestmentOpportunities, useInvestments } from '../../hooks/useKadaikodiApi';\nimport { cn } from '../../utils/cn';\nimport { formatCurrency, formatDate } from '../../utils/formatters';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getRiskConfig(risk: string): { label: string; color: string } {\n switch (risk) {\n case 'low':\n return { label: 'Low Risk', color: 'text-status-success-text' };\n case 'medium':\n return { label: 'Medium Risk', color: 'text-status-warning-text' };\n case 'high':\n return { label: 'High Risk', color: 'text-status-error-text' };\n default:\n return { label: risk, color: 'text-text-secondary' };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport function InvestmentDetailsPage() {\n const { navigateTo, currentUser } = useKadaikodi();\n const { id } = useParams<{ id: string }>();\n\n const { data: opportunities = [], isLoading: opportunitiesLoading } =\n useInvestmentOpportunities();\n const { data: investments = [], isLoading: investmentsLoading } = useInvestments(\n currentUser?.id || ''\n );\n\n const isLoading = opportunitiesLoading || investmentsLoading;\n\n // The route id may be an investment id or an opportunity id. Resolve the\n // holding first (if it belongs to this investor), then the opportunity.\n const investment = useMemo(\n () => investments.find((inv) => inv.id === id),\n [investments, id]\n );\n const opportunity = useMemo(\n () =>\n opportunities.find((opp) => opp.id === id || opp.id === investment?.opportunityId) ?? null,\n [opportunities, id, investment]\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Investment Details\" description=\"Loading...\">\n <div className=\"flex items-center justify-center py-20\">\n <div className=\"text-center\">\n <div className=\"mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-b-2 border-primary\" />\n <p className=\"text-sm text-text-secondary\">Loading investment details...</p>\n </div>\n </div>\n </PageLayout>\n );\n }\n\n if (!opportunity && !investment) {\n return (\n <PageLayout title=\"Investment Not Found\">\n <EmptyState\n illustration=\"empty-generic\"\n title=\"Investment not found\"\n description=\"We couldn't find this investment. It may have been closed or the link is out of date.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/investor/portfolio')}\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 Back to Portfolio\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n const cartName = opportunity?.cartName ?? investment?.cartName ?? 'Investment';\n const risk = opportunity ? getRiskConfig(opportunity.riskLevel) : null;\n const fundingPct =\n opportunity && opportunity.targetAmount > 0\n ? Math.min((opportunity.raisedAmount / opportunity.targetAmount) * 100, 100)\n : 0;\n const daysLeft = opportunity?.expiresAt\n ? Math.max(\n 0,\n Math.ceil((new Date(opportunity.expiresAt).getTime() - Date.now()) / 86400000)\n )\n : null;\n\n const holdingReturns = investment ? investment.currentValue - investment.investedAmount : 0;\n const holdingReturnsPct =\n investment && investment.investedAmount > 0\n ? (holdingReturns / investment.investedAmount) * 100\n : 0;\n\n return (\n <PageLayout\n title={cartName}\n description=\"Investment Details & Performance\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/investor/portfolio')}\n className=\"flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-accent transition-colors\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n <span className=\"hidden sm:inline\">Back to Portfolio</span>\n <span className=\"sm:hidden\">Back</span>\n </button>\n }\n >\n <PagePurpose className=\"mb-6\">\n A deep dive into this investment. Review the funding progress, expected returns and terms on\n offer, and — if you already hold a stake — exactly what you've put in and what it's\n worth today.\n </PagePurpose>\n\n {/* Summary Cards — the one emphasis zone for this page */}\n <EmphasisPanel>\n {investment ? (\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <IndianRupee className=\"mx-auto mb-2 h-6 w-6 text-primary sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(investment.investedAmount, { minimumFractionDigits: 0 })}\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Your Investment</div>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <TrendingUp className=\"mx-auto mb-2 h-6 w-6 text-status-success-text sm:h-8 sm:w-8\" />\n <div\n className={cn(\n 'text-lg font-bold sm:text-2xl',\n holdingReturns >= 0 ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {holdingReturnsPct >= 0 ? '+' : ''}\n {holdingReturnsPct.toFixed(1)}%\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Returns</div>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <Building2 className=\"mx-auto mb-2 h-6 w-6 text-text-primary sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(investment.currentValue, { minimumFractionDigits: 0 })}\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Current Value</div>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <PieChart className=\"mx-auto mb-2 h-6 w-6 text-text-primary sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {investment.returnsPercentage}%\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Expected Return</div>\n </div>\n </div>\n ) : (\n opportunity && (\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <Target className=\"mx-auto mb-2 h-6 w-6 text-primary sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(opportunity.targetAmount, { minimumFractionDigits: 0 })}\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Target Amount</div>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <IndianRupee className=\"mx-auto mb-2 h-6 w-6 text-status-success-text sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(opportunity.raisedAmount, { minimumFractionDigits: 0 })}\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Raised So Far</div>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <TrendingUp className=\"mx-auto mb-2 h-6 w-6 text-status-success-text sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-status-success-text sm:text-2xl\">\n {opportunity.expectedReturns}%\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Expected Returns</div>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <IndianRupee className=\"mx-auto mb-2 h-6 w-6 text-text-primary sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(opportunity.minimumInvestment, { minimumFractionDigits: 0 })}\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Min Investment</div>\n </div>\n </div>\n )\n )}\n </EmphasisPanel>\n\n <div className=\"mt-6 grid gap-6 lg:grid-cols-3\">\n {/* Opportunity Details */}\n <div className=\"space-y-6 lg:col-span-2\">\n {opportunity && (\n <>\n {opportunity.description && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">About This Cart</h2>\n <p className=\"text-sm leading-relaxed text-text-secondary\">\n {opportunity.description}\n </p>\n </div>\n )}\n\n {/* Funding Progress */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Funding Progress</h2>\n <div className=\"mb-2 flex justify-between text-sm\">\n <span className=\"font-medium text-text-primary\">\n {formatCurrency(opportunity.raisedAmount, { minimumFractionDigits: 0 })} raised\n </span>\n <span className=\"text-text-secondary\">\n of {formatCurrency(opportunity.targetAmount, { minimumFractionDigits: 0 })}\n </span>\n </div>\n <div className=\"h-2 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg transition-all duration-300\"\n style={{ width: `${fundingPct}%` }}\n />\n </div>\n <div className=\"mt-2 flex justify-between text-xs text-text-secondary\">\n <span>{fundingPct.toFixed(0)}% funded</span>\n {daysLeft !== null && <span>{daysLeft} days left</span>}\n </div>\n </div>\n </>\n )}\n\n {/* Your Holding */}\n {investment && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Your Holding</h2>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div>\n <p className=\"text-sm text-text-secondary\">Invested Amount</p>\n <p className=\"font-medium text-text-primary\">\n {formatCurrency(investment.investedAmount, { minimumFractionDigits: 0 })}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Current Value</p>\n <p className=\"font-medium text-text-primary\">\n {formatCurrency(investment.currentValue, { minimumFractionDigits: 0 })}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Returns Realised</p>\n <p className=\"font-medium text-text-primary\">\n {formatCurrency(investment.returns, { minimumFractionDigits: 0 })}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Invested On</p>\n <p className=\"font-medium text-text-primary\">\n {formatDate(investment.investmentDate, {\n hour: undefined,\n minute: undefined,\n })}\n </p>\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {opportunity && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Investment Terms</h2>\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <TrendingUp className=\"h-4 w-4\" />\n Expected Returns\n </span>\n <span className=\"font-medium text-status-success-text\">\n {opportunity.expectedReturns}%\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Clock className=\"h-4 w-4\" />\n Lock-in Period\n </span>\n <span className=\"font-medium text-text-primary\">\n {opportunity.investmentPeriod} months\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <IndianRupee className=\"h-4 w-4\" />\n Minimum Investment\n </span>\n <span className=\"font-medium text-text-primary\">\n {formatCurrency(opportunity.minimumInvestment, { minimumFractionDigits: 0 })}\n </span>\n </div>\n {risk && (\n <div className=\"flex items-center justify-between\">\n <span className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Shield className=\"h-4 w-4\" />\n Risk Level\n </span>\n <span className={cn('font-medium', risk.color)}>{risk.label}</span>\n </div>\n )}\n {opportunity.investors.length > 0 && (\n <div className=\"flex items-center justify-between\">\n <span className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Users className=\"h-4 w-4\" />\n Investors\n </span>\n <span className=\"font-medium text-text-primary\">\n {opportunity.investors.length}\n </span>\n </div>\n )}\n </div>\n\n {opportunity.status === 'active' && !investment && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/investor/opportunities')}\n className=\"mt-6 flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <BarChart3 className=\"h-4 w-4\" />\n Invest in this Cart\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InvestmentDetailsPage.js","names":[],"sources":["../../../../src/kadaikodi/pages/investor/InvestmentDetailsPage.tsx"],"sourcesContent":["/**\n * Investment Details Page\n * @module kadaikodi/pages/investor\n *\n * Serves both /investor/opportunity/:id and /investor/investments/:id. Resolves\n * the opportunity (and the investor's holding, if any) from the live backend by\n * route id — no fabricated business figures, documents or updates.\n */\n\nimport { useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n TrendingUp,\n Shield,\n Users,\n IndianRupee,\n BarChart3,\n PieChart,\n Building2,\n Target,\n Clock,\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 { useInvestmentOpportunities, useInvestments } from '../../hooks/useKadaikodiApi';\nimport { cn } from '../../utils/cn';\nimport { formatCurrency, formatDate } from '../../utils/formatters';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getRiskConfig(risk: string): { label: string; color: string } {\n switch (risk) {\n case 'low':\n return { label: 'Low Risk', color: 'text-status-success-text' };\n case 'medium':\n return { label: 'Medium Risk', color: 'text-status-warning-text' };\n case 'high':\n return { label: 'High Risk', color: 'text-status-error-text' };\n default:\n return { label: risk, color: 'text-text-secondary' };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport function InvestmentDetailsPage() {\n const { navigateTo, currentUser } = useKadaikodi();\n const { id } = useParams<{ id: string }>();\n\n const { data: opportunities = [], isLoading: opportunitiesLoading } =\n useInvestmentOpportunities();\n const { data: investments = [], isLoading: investmentsLoading } = useInvestments(\n currentUser?.id || ''\n );\n\n const isLoading = opportunitiesLoading || investmentsLoading;\n\n // The route id may be an investment id or an opportunity id. Resolve the\n // holding first (if it belongs to this investor), then the opportunity.\n const investment = useMemo(() => investments.find((inv) => inv.id === id), [investments, id]);\n const opportunity = useMemo(\n () =>\n opportunities.find((opp) => opp.id === id || opp.id === investment?.opportunityId) ?? null,\n [opportunities, id, investment]\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Investment Details\" description=\"Loading...\">\n <div className=\"flex items-center justify-center py-20\">\n <div className=\"text-center\">\n <div className=\"mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-b-2 border-primary\" />\n <p className=\"text-sm text-text-secondary\">Loading investment details...</p>\n </div>\n </div>\n </PageLayout>\n );\n }\n\n if (!opportunity && !investment) {\n return (\n <PageLayout title=\"Investment Not Found\">\n <EmptyState\n illustration=\"empty-generic\"\n title=\"Investment not found\"\n description=\"We couldn't find this investment. It may have been closed or the link is out of date.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/investor/portfolio')}\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 Back to Portfolio\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n const cartName = opportunity?.cartName ?? investment?.cartName ?? 'Investment';\n const risk = opportunity ? getRiskConfig(opportunity.riskLevel) : null;\n const fundingPct =\n opportunity && opportunity.targetAmount > 0\n ? Math.min((opportunity.raisedAmount / opportunity.targetAmount) * 100, 100)\n : 0;\n const daysLeft = opportunity?.expiresAt\n ? Math.max(0, Math.ceil((new Date(opportunity.expiresAt).getTime() - Date.now()) / 86400000))\n : null;\n\n const holdingReturns = investment ? investment.currentValue - investment.investedAmount : 0;\n const holdingReturnsPct =\n investment && investment.investedAmount > 0\n ? (holdingReturns / investment.investedAmount) * 100\n : 0;\n\n return (\n <PageLayout\n title={cartName}\n description=\"Investment Details & Performance\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/investor/portfolio')}\n className=\"flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-accent transition-colors\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n <span className=\"hidden sm:inline\">Back to Portfolio</span>\n <span className=\"sm:hidden\">Back</span>\n </button>\n }\n >\n <PagePurpose className=\"mb-6\">\n A deep dive into this investment. Review the funding progress, expected returns and terms on\n offer, and — if you already hold a stake — exactly what you've put in and what\n it's worth today.\n </PagePurpose>\n\n {/* Summary Cards — the one emphasis zone for this page */}\n <EmphasisPanel>\n {investment ? (\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <IndianRupee className=\"mx-auto mb-2 h-6 w-6 text-primary sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(investment.investedAmount, { minimumFractionDigits: 0 })}\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Your Investment</div>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <TrendingUp className=\"mx-auto mb-2 h-6 w-6 text-status-success-text sm:h-8 sm:w-8\" />\n <div\n className={cn(\n 'text-lg font-bold sm:text-2xl',\n holdingReturns >= 0 ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {holdingReturnsPct >= 0 ? '+' : ''}\n {holdingReturnsPct.toFixed(1)}%\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Returns</div>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <Building2 className=\"mx-auto mb-2 h-6 w-6 text-text-primary sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(investment.currentValue, { minimumFractionDigits: 0 })}\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Current Value</div>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <PieChart className=\"mx-auto mb-2 h-6 w-6 text-text-primary sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {investment.returnsPercentage}%\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Expected Return</div>\n </div>\n </div>\n ) : (\n opportunity && (\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <Target className=\"mx-auto mb-2 h-6 w-6 text-primary sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(opportunity.targetAmount, { minimumFractionDigits: 0 })}\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Target Amount</div>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <IndianRupee className=\"mx-auto mb-2 h-6 w-6 text-status-success-text sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(opportunity.raisedAmount, { minimumFractionDigits: 0 })}\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Raised So Far</div>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <TrendingUp className=\"mx-auto mb-2 h-6 w-6 text-status-success-text sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-status-success-text sm:text-2xl\">\n {opportunity.expectedReturns}%\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Expected Returns</div>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-center\">\n <IndianRupee className=\"mx-auto mb-2 h-6 w-6 text-text-primary sm:h-8 sm:w-8\" />\n <div className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(opportunity.minimumInvestment, { minimumFractionDigits: 0 })}\n </div>\n <div className=\"text-xs text-text-secondary sm:text-sm\">Min Investment</div>\n </div>\n </div>\n )\n )}\n </EmphasisPanel>\n\n <div className=\"mt-6 grid gap-6 lg:grid-cols-3\">\n {/* Opportunity Details */}\n <div className=\"space-y-6 lg:col-span-2\">\n {opportunity && (\n <>\n {opportunity.description && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">About This Cart</h2>\n <p className=\"text-sm leading-relaxed text-text-secondary\">\n {opportunity.description}\n </p>\n </div>\n )}\n\n {/* Funding Progress */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Funding Progress</h2>\n <div className=\"mb-2 flex justify-between text-sm\">\n <span className=\"font-medium text-text-primary\">\n {formatCurrency(opportunity.raisedAmount, { minimumFractionDigits: 0 })} raised\n </span>\n <span className=\"text-text-secondary\">\n of {formatCurrency(opportunity.targetAmount, { minimumFractionDigits: 0 })}\n </span>\n </div>\n <div className=\"h-2 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg transition-all duration-300\"\n style={{ width: `${fundingPct}%` }}\n />\n </div>\n <div className=\"mt-2 flex justify-between text-xs text-text-secondary\">\n <span>{fundingPct.toFixed(0)}% funded</span>\n {daysLeft !== null && <span>{daysLeft} days left</span>}\n </div>\n </div>\n </>\n )}\n\n {/* Your Holding */}\n {investment && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Your Holding</h2>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div>\n <p className=\"text-sm text-text-secondary\">Invested Amount</p>\n <p className=\"font-medium text-text-primary\">\n {formatCurrency(investment.investedAmount, { minimumFractionDigits: 0 })}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Current Value</p>\n <p className=\"font-medium text-text-primary\">\n {formatCurrency(investment.currentValue, { minimumFractionDigits: 0 })}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Returns Realised</p>\n <p className=\"font-medium text-text-primary\">\n {formatCurrency(investment.returns, { minimumFractionDigits: 0 })}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Invested On</p>\n <p className=\"font-medium text-text-primary\">\n {formatDate(investment.investmentDate, {\n hour: undefined,\n minute: undefined,\n })}\n </p>\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {opportunity && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Investment Terms</h2>\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <TrendingUp className=\"h-4 w-4\" />\n Expected Returns\n </span>\n <span className=\"font-medium text-status-success-text\">\n {opportunity.expectedReturns}%\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Clock className=\"h-4 w-4\" />\n Lock-in Period\n </span>\n <span className=\"font-medium text-text-primary\">\n {opportunity.investmentPeriod} months\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <IndianRupee className=\"h-4 w-4\" />\n Minimum Investment\n </span>\n <span className=\"font-medium text-text-primary\">\n {formatCurrency(opportunity.minimumInvestment, { minimumFractionDigits: 0 })}\n </span>\n </div>\n {risk && (\n <div className=\"flex items-center justify-between\">\n <span className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Shield className=\"h-4 w-4\" />\n Risk Level\n </span>\n <span className={cn('font-medium', risk.color)}>{risk.label}</span>\n </div>\n )}\n {opportunity.investors.length > 0 && (\n <div className=\"flex items-center justify-between\">\n <span className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Users className=\"h-4 w-4\" />\n Investors\n </span>\n <span className=\"font-medium text-text-primary\">\n {opportunity.investors.length}\n </span>\n </div>\n )}\n </div>\n\n {opportunity.status === 'active' && !investment && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/investor/opportunities')}\n className=\"mt-6 flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <BarChart3 className=\"h-4 w-4\" />\n Invest in this Cart\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|