@burdenoff/microfe-proserviceworld 2026.624.1 → 2026.626.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/proserviceworld/pages/AdminReportsPage.js +396 -329
- package/dist/proserviceworld/pages/AdminReportsPage.js.map +1 -1
- package/dist/proserviceworld/pages/AdminUsersPage.js +261 -237
- package/dist/proserviceworld/pages/AdminUsersPage.js.map +1 -1
- package/dist/proserviceworld/pages/AnalyticsPage.js +373 -315
- package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
- package/dist/proserviceworld/pages/BookingsPage.js +221 -200
- package/dist/proserviceworld/pages/BookingsPage.js.map +1 -1
- package/dist/proserviceworld/pages/DashboardPage.js +474 -405
- package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
- package/dist/proserviceworld/pages/DocumentsPage.js +386 -317
- package/dist/proserviceworld/pages/DocumentsPage.js.map +1 -1
- package/dist/proserviceworld/pages/MessagesPage.js +201 -192
- package/dist/proserviceworld/pages/MessagesPage.js.map +1 -1
- package/dist/proserviceworld/pages/PaymentsPage.js +398 -340
- package/dist/proserviceworld/pages/PaymentsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ProjectsPage.js +543 -463
- package/dist/proserviceworld/pages/ProjectsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ProviderDetailPage.js +420 -346
- package/dist/proserviceworld/pages/ProviderDetailPage.js.map +1 -1
- package/dist/proserviceworld/pages/ProvidersPage.js +249 -244
- package/dist/proserviceworld/pages/ProvidersPage.js.map +1 -1
- package/dist/proserviceworld/pages/ReviewsPage.js +93 -76
- package/dist/proserviceworld/pages/ReviewsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ServiceDetailsPage.js +377 -309
- package/dist/proserviceworld/pages/ServiceDetailsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ServicesPage.js +116 -109
- package/dist/proserviceworld/pages/ServicesPage.js.map +1 -1
- package/dist/proserviceworld/pages/VerificationPage.js +264 -251
- package/dist/proserviceworld/pages/VerificationPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProvidersPage.js","names":[],"sources":["../../../src/proserviceworld/pages/ProvidersPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Card, CardContent, Button, Badge, Input } from '@burdenoff/fe-libs/ui';\nimport { Search, Filter, Star, Users, MapPin, CheckCircle, Clock, Briefcase } from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\nimport type { ServiceCategory } from '../types';\nimport { tWithFallback } from '../utils/i18n';\n\nconst categoryOptions: { value: ServiceCategory | ''; key: string; fallback: string }[] = [\n {\n value: '',\n key: 'proserviceworld.shared.filters.allCategories',\n fallback: 'All Categories',\n },\n {\n value: 'business_consulting',\n key: 'proserviceworld.shared.categories.businessConsulting',\n fallback: 'Business Consulting',\n },\n {\n value: 'financial_services',\n key: 'proserviceworld.shared.categories.financialServices',\n fallback: 'Financial Services',\n },\n {\n value: 'legal_services',\n key: 'proserviceworld.shared.categories.legalServices',\n fallback: 'Legal Services',\n },\n {\n value: 'accounting_bookkeeping',\n key: 'proserviceworld.shared.categories.accountingBookkeeping',\n fallback: 'Accounting & Bookkeeping',\n },\n {\n value: 'marketing_advertising',\n key: 'proserviceworld.shared.categories.marketingAdvertising',\n fallback: 'Marketing & Advertising',\n },\n {\n value: 'it_technology',\n key: 'proserviceworld.shared.categories.itTechnology',\n fallback: 'IT & Technology',\n },\n {\n value: 'human_resources',\n key: 'proserviceworld.shared.categories.humanResources',\n fallback: 'Human Resources',\n },\n {\n value: 'creative_design',\n key: 'proserviceworld.shared.categories.creativeDesign',\n fallback: 'Creative & Design',\n },\n {\n value: 'education_training',\n key: 'proserviceworld.shared.categories.educationTraining',\n fallback: 'Education & Training',\n },\n];\n\nexport function ProvidersPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n const { providers } = useProServiceWorldStore();\n const [searchQuery, setSearchQuery] = useState('');\n const [selectedCategory, setSelectedCategory] = useState<ServiceCategory | ''>('');\n const [minRating, setMinRating] = useState<number | ''>('');\n const [showFilters, setShowFilters] = useState(false);\n\n const filteredProviders = useMemo(() => {\n let result = [...providers];\n\n if (searchQuery) {\n const q = searchQuery.toLowerCase();\n result = result.filter(\n (p) =>\n p.name.toLowerCase().includes(q) ||\n p.businessName.toLowerCase().includes(q) ||\n p.tagline.toLowerCase().includes(q) ||\n p.specializations.some((s) => s.toLowerCase().includes(q))\n );\n }\n\n if (selectedCategory) {\n result = result.filter((p) => p.category === selectedCategory);\n }\n\n if (minRating) {\n result = result.filter((p) => p.rating >= minRating);\n }\n\n result.sort((a, b) => b.rating - a.rating);\n return result;\n }, [providers, searchQuery, selectedCategory, minRating]);\n\n const avgRating =\n filteredProviders.length > 0\n ? (\n filteredProviders.reduce((sum, p) => sum + p.rating, 0) / filteredProviders.length\n ).toFixed(1)\n : '0';\n\n const countries = new Set(filteredProviders.map((p) => p.country)).size;\n\n const clearFilters = () => {\n setSearchQuery('');\n setSelectedCategory('');\n setMinRating('');\n };\n const subtitle = searchQuery\n ? tr(\n 'proserviceworld.providers.subtitleWithQuery',\n '{{count}} providers found for \"{{query}}\"',\n {\n count: filteredProviders.length,\n query: searchQuery,\n }\n )\n : tr('proserviceworld.providers.subtitle', '{{count}} providers found', {\n count: filteredProviders.length,\n });\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Page Header */}\n <div className=\"flex flex-col md:flex-row md:items-center md:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n {tr('proserviceworld.providers.title', 'Service Providers')}\n </h1>\n <p className=\"text-sm text-text-secondary\">{subtitle}</p>\n </div>\n <Button variant=\"outline\" onClick={() => setShowFilters(!showFilters)}>\n <Filter className=\"h-4 w-4 mr-2\" />\n {tr('proserviceworld.providers.actions.filters', 'Filters')}\n </Button>\n </div>\n\n {/* Search Bar */}\n <div className=\"flex gap-4\">\n <div className=\"flex-1 relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-text-secondary\" />\n <Input\n type=\"text\"\n placeholder={tr(\n 'proserviceworld.providers.search.placeholder',\n 'Search providers by name, business, or specialization...'\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"pl-10 h-12\"\n />\n </div>\n </div>\n\n {/* Filters Panel */}\n {showFilters && (\n <Card>\n <CardContent className=\"p-6\">\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium mb-2\">\n {tr('proserviceworld.providers.filters.category', 'Category')}\n </label>\n <select\n value={selectedCategory}\n onChange={(e) => setSelectedCategory(e.target.value as ServiceCategory | '')}\n className=\"w-full p-2 border border-border-default rounded-md bg-bg-surface text-text-primary\"\n >\n {categoryOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {tr(opt.key, opt.fallback)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-2\">\n {tr('proserviceworld.providers.filters.minimumRating', 'Minimum Rating')}\n </label>\n <select\n value={minRating}\n onChange={(e) => setMinRating(e.target.value ? Number(e.target.value) : '')}\n className=\"w-full p-2 border border-border-default rounded-md bg-bg-surface text-text-primary\"\n >\n <option value=\"\">\n {tr('proserviceworld.providers.filters.anyRating', 'Any Rating')}\n </option>\n <option value=\"4\">\n {tr('proserviceworld.providers.filters.rating4', '4+ Stars')}\n </option>\n <option value=\"4.5\">\n {tr('proserviceworld.providers.filters.rating45', '4.5+ Stars')}\n </option>\n <option value=\"4.8\">\n {tr('proserviceworld.providers.filters.rating48', '4.8+ Stars')}\n </option>\n </select>\n </div>\n <div className=\"flex items-end\">\n <Button variant=\"outline\" onClick={clearFilters} className=\"w-full\">\n {tr('proserviceworld.providers.filters.clear', 'Clear Filters')}\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )}\n\n {/* Provider Grid */}\n {filteredProviders.length > 0 ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6\">\n {filteredProviders.map((provider) => (\n <Card key={provider.id} className=\"hover:shadow-md transition-shadow\">\n <CardContent className=\"p-6\">\n <div className=\"flex items-center space-x-4 mb-4\">\n <div className=\"h-12 w-12 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <span className=\"text-primary font-bold\">{provider.name.charAt(0)}</span>\n </div>\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"font-semibold truncate\">{provider.businessName}</h3>\n <p className=\"text-sm text-text-secondary truncate\">{provider.name}</p>\n </div>\n {provider.verified && <CheckCircle className=\"h-5 w-5 text-blue-600 shrink-0\" />}\n </div>\n\n <p className=\"text-sm text-text-secondary mb-3 line-clamp-2\">\n {provider.tagline}\n </p>\n\n <div className=\"flex flex-wrap gap-1 mb-3\">\n {provider.specializations.slice(0, 2).map((spec) => (\n <Badge key={spec} variant=\"secondary\" className=\"text-xs\">\n {spec}\n </Badge>\n ))}\n {provider.specializations.length > 2 && (\n <Badge variant=\"outline\" className=\"text-xs\">\n +{provider.specializations.length - 2}\n </Badge>\n )}\n </div>\n\n <div className=\"flex items-center justify-between text-sm mb-3\">\n <div className=\"flex items-center\">\n <Star className=\"h-4 w-4 text-yellow-400 fill-current mr-1\" />\n <span className=\"font-medium\">{provider.rating.toFixed(1)}</span>\n <span className=\"text-text-secondary ml-1\">({provider.totalReviews})</span>\n </div>\n <div className=\"flex items-center text-text-secondary\">\n <MapPin className=\"h-3 w-3 mr-1\" />\n <span>{provider.location}</span>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-2 text-xs text-text-secondary border-t pt-3\">\n <div className=\"flex items-center\">\n <Clock className=\"h-3 w-3 mr-1\" />\n {tr('proserviceworld.providers.card.responseTime', '{{count}}h response', {\n count: provider.responseTime,\n })}\n </div>\n <div className=\"flex items-center\">\n <Briefcase className=\"h-3 w-3 mr-1\" />\n {tr('proserviceworld.providers.card.projects', '{{count}} projects', {\n count: provider.projectsCompleted,\n })}\n </div>\n </div>\n\n <Button variant=\"outline\" size=\"sm\" className=\"w-full mt-4\">\n {tr('proserviceworld.providers.actions.viewProfile', 'View Profile')}\n </Button>\n </CardContent>\n </Card>\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12\">\n <Users className=\"h-12 w-12 mx-auto text-text-secondary mb-4\" />\n <h3 className=\"text-lg font-medium mb-2\">\n {tr('proserviceworld.providers.empty.title', 'No providers found')}\n </h3>\n <p className=\"text-text-secondary\">\n {tr(\n 'proserviceworld.shared.empty.adjustFilters',\n 'Try adjusting your search criteria or filters.'\n )}\n </p>\n </div>\n )}\n\n {/* Provider Statistics */}\n {filteredProviders.length > 0 && (\n <Card className=\"bg-action-primary-bg/5\">\n <CardContent className=\"p-8\">\n <div className=\"text-center mb-8\">\n <h2 className=\"text-xl font-bold\">\n {tr('proserviceworld.providers.stats.title', 'Provider Statistics')}\n </h2>\n </div>\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-8\">\n <div className=\"text-center\">\n <div className=\"flex items-center justify-center w-12 h-12 mx-auto bg-action-primary-bg/10 rounded-lg mb-4\">\n <Star className=\"h-6 w-6 text-primary\" />\n </div>\n <div className=\"text-2xl font-bold\">{avgRating}</div>\n <div className=\"text-sm text-text-secondary\">\n {tr('proserviceworld.providers.stats.averageRating', 'Average Rating')}\n </div>\n </div>\n <div className=\"text-center\">\n <div className=\"flex items-center justify-center w-12 h-12 mx-auto bg-action-primary-bg/10 rounded-lg mb-4\">\n <Users className=\"h-6 w-6 text-primary\" />\n </div>\n <div className=\"text-2xl font-bold\">{filteredProviders.length}</div>\n <div className=\"text-sm text-text-secondary\">\n {tr('proserviceworld.providers.stats.expertProviders', 'Expert Providers')}\n </div>\n </div>\n <div className=\"text-center\">\n <div className=\"flex items-center justify-center w-12 h-12 mx-auto bg-action-primary-bg/10 rounded-lg mb-4\">\n <MapPin className=\"h-6 w-6 text-primary\" />\n </div>\n <div className=\"text-2xl font-bold\">{countries}</div>\n <div className=\"text-sm text-text-secondary\">\n {tr('proserviceworld.providers.stats.countries', 'Countries')}\n </div>\n </div>\n </div>\n </CardContent>\n </Card>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProvidersPage.js","names":[],"sources":["../../../src/proserviceworld/pages/ProvidersPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n Card,\n CardContent,\n Button,\n Badge,\n Input,\n GlassCard,\n EmphasisPanel,\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Search,\n Filter,\n Star,\n Users,\n MapPin,\n CheckCircle,\n Clock,\n Briefcase,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\nimport type { ServiceCategory } from '../types';\nimport { tWithFallback } from '../utils/i18n';\n\nconst categoryOptions: { value: ServiceCategory | ''; key: string; fallback: string }[] = [\n {\n value: '',\n key: 'proserviceworld.shared.filters.allCategories',\n fallback: 'All Categories',\n },\n {\n value: 'business_consulting',\n key: 'proserviceworld.shared.categories.businessConsulting',\n fallback: 'Business Consulting',\n },\n {\n value: 'financial_services',\n key: 'proserviceworld.shared.categories.financialServices',\n fallback: 'Financial Services',\n },\n {\n value: 'legal_services',\n key: 'proserviceworld.shared.categories.legalServices',\n fallback: 'Legal Services',\n },\n {\n value: 'accounting_bookkeeping',\n key: 'proserviceworld.shared.categories.accountingBookkeeping',\n fallback: 'Accounting & Bookkeeping',\n },\n {\n value: 'marketing_advertising',\n key: 'proserviceworld.shared.categories.marketingAdvertising',\n fallback: 'Marketing & Advertising',\n },\n {\n value: 'it_technology',\n key: 'proserviceworld.shared.categories.itTechnology',\n fallback: 'IT & Technology',\n },\n {\n value: 'human_resources',\n key: 'proserviceworld.shared.categories.humanResources',\n fallback: 'Human Resources',\n },\n {\n value: 'creative_design',\n key: 'proserviceworld.shared.categories.creativeDesign',\n fallback: 'Creative & Design',\n },\n {\n value: 'education_training',\n key: 'proserviceworld.shared.categories.educationTraining',\n fallback: 'Education & Training',\n },\n];\n\nexport function ProvidersPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n const { providers } = useProServiceWorldStore();\n const [searchQuery, setSearchQuery] = useState('');\n const [selectedCategory, setSelectedCategory] = useState<ServiceCategory | ''>('');\n const [minRating, setMinRating] = useState<number | ''>('');\n const [showFilters, setShowFilters] = useState(false);\n\n const filteredProviders = useMemo(() => {\n let result = [...providers];\n\n if (searchQuery) {\n const q = searchQuery.toLowerCase();\n result = result.filter(\n (p) =>\n p.name.toLowerCase().includes(q) ||\n p.businessName.toLowerCase().includes(q) ||\n p.tagline.toLowerCase().includes(q) ||\n p.specializations.some((s) => s.toLowerCase().includes(q))\n );\n }\n\n if (selectedCategory) {\n result = result.filter((p) => p.category === selectedCategory);\n }\n\n if (minRating) {\n result = result.filter((p) => p.rating >= minRating);\n }\n\n result.sort((a, b) => b.rating - a.rating);\n return result;\n }, [providers, searchQuery, selectedCategory, minRating]);\n\n const avgRating =\n filteredProviders.length > 0\n ? (\n filteredProviders.reduce((sum, p) => sum + p.rating, 0) / filteredProviders.length\n ).toFixed(1)\n : '0';\n\n const countries = new Set(filteredProviders.map((p) => p.country)).size;\n\n const clearFilters = () => {\n setSearchQuery('');\n setSelectedCategory('');\n setMinRating('');\n };\n const subtitle = searchQuery\n ? tr('proserviceworld.providers.subtitleWithQuery', '{{count}} providers found for \"{{query}}\"', {\n count: filteredProviders.length,\n query: searchQuery,\n })\n : tr('proserviceworld.providers.subtitle', '{{count}} providers found', {\n count: filteredProviders.length,\n });\n\n return (\n <div className=\"space-y-6 p-6 bg-bg-canvas min-h-screen\">\n {/* Hero header — the ONE aurora backdrop for this MFE */}\n <div className=\"relative overflow-hidden rounded-card -mx-2 px-2 pt-1\">\n <AuroraBackground intensity=\"subtle\" />\n <div className=\"flex flex-col md:flex-row md:items-center md:justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight\">\n <GradientText>{tr('proserviceworld.providers.title', 'Service Providers')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{subtitle}</p>\n </div>\n <Button\n variant=\"outline\"\n onClick={() => setShowFilters(!showFilters)}\n aria-expanded={showFilters}\n >\n <Filter className=\"h-4 w-4 mr-2\" />\n {tr('proserviceworld.providers.actions.filters', 'Filters')}\n </Button>\n </div>\n </div>\n\n <PagePurpose icon={<Users className=\"h-4 w-4\" />}>\n {tr(\n 'proserviceworld.providers.purpose',\n 'Find and vet the experts behind the work. Search by name, business, or specialization and filter by category and rating to shortlist trusted providers — then open a profile to view their services, reviews, and credentials before reaching out.'\n )}\n </PagePurpose>\n\n {/* Search Bar */}\n <div className=\"flex gap-4\">\n <div className=\"flex-1 relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-text-secondary\" />\n <Input\n type=\"text\"\n placeholder={tr(\n 'proserviceworld.providers.search.placeholder',\n 'Search providers by name, business, or specialization...'\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"pl-10 h-12\"\n />\n </div>\n </div>\n\n {/* Filters Panel */}\n {showFilters && (\n <Card className=\"border-border-seam\">\n <CardContent className=\"p-6\">\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium mb-2\">\n {tr('proserviceworld.providers.filters.category', 'Category')}\n </label>\n <select\n value={selectedCategory}\n onChange={(e) => setSelectedCategory(e.target.value as ServiceCategory | '')}\n className=\"w-full p-2 border border-border-seam rounded-md bg-bg-surface text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n {categoryOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {tr(opt.key, opt.fallback)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-2\">\n {tr('proserviceworld.providers.filters.minimumRating', 'Minimum Rating')}\n </label>\n <select\n value={minRating}\n onChange={(e) => setMinRating(e.target.value ? Number(e.target.value) : '')}\n className=\"w-full p-2 border border-border-seam rounded-md bg-bg-surface text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <option value=\"\">\n {tr('proserviceworld.providers.filters.anyRating', 'Any Rating')}\n </option>\n <option value=\"4\">\n {tr('proserviceworld.providers.filters.rating4', '4+ Stars')}\n </option>\n <option value=\"4.5\">\n {tr('proserviceworld.providers.filters.rating45', '4.5+ Stars')}\n </option>\n <option value=\"4.8\">\n {tr('proserviceworld.providers.filters.rating48', '4.8+ Stars')}\n </option>\n </select>\n </div>\n <div className=\"flex items-end\">\n <Button variant=\"outline\" onClick={clearFilters} className=\"w-full\">\n {tr('proserviceworld.providers.filters.clear', 'Clear Filters')}\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )}\n\n {/* Provider Grid */}\n {filteredProviders.length > 0 ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6\">\n {filteredProviders.map((provider) => (\n <GlassCard key={provider.id} treatment=\"glass\" className=\"border-border-seam transition-shadow hover:shadow-pop\">\n <div className=\"flex items-center space-x-4 mb-4\">\n <div className=\"h-12 w-12 rounded-full bg-accent-soft flex items-center justify-center\">\n <span className=\"text-text-primary font-bold\">{provider.name.charAt(0)}</span>\n </div>\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"font-semibold truncate\">{provider.businessName}</h3>\n <p className=\"text-sm text-text-secondary truncate\">{provider.name}</p>\n </div>\n {provider.verified && (\n <CheckCircle className=\"h-5 w-5 text-accent-blue shrink-0\" aria-label=\"Verified\" />\n )}\n </div>\n\n <p className=\"text-sm text-text-secondary mb-3 line-clamp-2\">{provider.tagline}</p>\n\n <div className=\"flex flex-wrap gap-1 mb-3\">\n {provider.specializations.slice(0, 2).map((spec) => (\n <Badge key={spec} variant=\"secondary\" className=\"text-xs\">\n {spec}\n </Badge>\n ))}\n {provider.specializations.length > 2 && (\n <Badge variant=\"outline\" className=\"text-xs\">\n +{provider.specializations.length - 2}\n </Badge>\n )}\n </div>\n\n <div className=\"flex items-center justify-between text-sm mb-3\">\n <div className=\"flex items-center\">\n <Star className=\"h-4 w-4 text-status-warning-text fill-current mr-1\" />\n <span className=\"font-medium tabular-nums\">{provider.rating.toFixed(1)}</span>\n <span className=\"text-text-secondary ml-1 tabular-nums\">\n ({provider.totalReviews})\n </span>\n </div>\n <div className=\"flex items-center text-text-secondary\">\n <MapPin className=\"h-3 w-3 mr-1\" />\n <span>{provider.location}</span>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-2 text-xs text-text-secondary border-t border-border-seam pt-3\">\n <div className=\"flex items-center\">\n <Clock className=\"h-3 w-3 mr-1\" />\n {tr('proserviceworld.providers.card.responseTime', '{{count}}h response', {\n count: provider.responseTime,\n })}\n </div>\n <div className=\"flex items-center\">\n <Briefcase className=\"h-3 w-3 mr-1\" />\n {tr('proserviceworld.providers.card.projects', '{{count}} projects', {\n count: provider.projectsCompleted,\n })}\n </div>\n </div>\n\n <Button variant=\"outline\" size=\"sm\" className=\"w-full mt-4\">\n {tr('proserviceworld.providers.actions.viewProfile', 'View Profile')}\n </Button>\n </GlassCard>\n ))}\n </div>\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={tr('proserviceworld.providers.empty.title', 'No providers found')}\n description={tr(\n 'proserviceworld.shared.empty.adjustFilters',\n 'Try adjusting your search criteria or filters.'\n )}\n action={\n <Button variant=\"outline\" onClick={clearFilters}>\n {tr('proserviceworld.providers.filters.clear', 'Clear Filters')}\n </Button>\n }\n />\n )}\n\n {/* Provider Statistics — the single emphasised insight zone */}\n {filteredProviders.length > 0 && (\n <EmphasisPanel className=\"p-8\">\n <div className=\"text-center mb-8\">\n <h2 className=\"text-xl font-bold tracking-tight\">\n {tr('proserviceworld.providers.stats.title', 'Provider Statistics')}\n </h2>\n </div>\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-8\">\n <div className=\"text-center\">\n <div className=\"flex items-center justify-center w-12 h-12 mx-auto bg-accent-soft rounded-lg mb-4\">\n <Star className=\"h-6 w-6 text-text-primary\" />\n </div>\n <div className=\"text-2xl font-bold tabular-nums\">{avgRating}</div>\n <div className=\"text-sm text-text-secondary\">\n {tr('proserviceworld.providers.stats.averageRating', 'Average Rating')}\n </div>\n </div>\n <div className=\"text-center\">\n <div className=\"flex items-center justify-center w-12 h-12 mx-auto bg-accent-soft rounded-lg mb-4\">\n <Users className=\"h-6 w-6 text-text-primary\" />\n </div>\n <div className=\"text-2xl font-bold tabular-nums\">{filteredProviders.length}</div>\n <div className=\"text-sm text-text-secondary\">\n {tr('proserviceworld.providers.stats.expertProviders', 'Expert Providers')}\n </div>\n </div>\n <div className=\"text-center\">\n <div className=\"flex items-center justify-center w-12 h-12 mx-auto bg-accent-soft rounded-lg mb-4\">\n <MapPin className=\"h-6 w-6 text-text-primary\" />\n </div>\n <div className=\"text-2xl font-bold tabular-nums\">{countries}</div>\n <div className=\"text-sm text-text-secondary\">\n {tr('proserviceworld.providers.stats.countries', 'Countries')}\n </div>\n </div>\n </div>\n </EmphasisPanel>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -2,10 +2,10 @@ import { useProServiceWorldStore as e } from "../store/proserviceworldStore.js";
|
|
|
2
2
|
import { useMemo as t, useState as n } from "react";
|
|
3
3
|
import { ArrowUpDown as r, Award as i, BarChart3 as a, Filter as o, MessageSquare as s, Search as c, Star as l, ThumbsDown as u, ThumbsUp as d, TrendingUp as f } from "lucide-react";
|
|
4
4
|
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
5
|
-
import {
|
|
5
|
+
import { AuroraBackground as h, Badge as g, Button as _, Card as v, CardContent as y, CardHeader as b, CardTitle as x, EmphasisPanel as S, GradientText as C, IllustratedEmptyState as w, Input as T, PagePurpose as E } from "@burdenoff/fe-libs/ui";
|
|
6
6
|
//#region src/proserviceworld/pages/ReviewsPage.tsx
|
|
7
|
-
function
|
|
8
|
-
let { reviews:
|
|
7
|
+
function D() {
|
|
8
|
+
let { reviews: D } = e(), [O, k] = n("received"), [A, j] = n(""), [M, N] = n(null), [P, F] = n("date"), [I, L] = n(!1), R = D.length > 0 ? (D.reduce((e, t) => e + t.rating, 0) / D.length).toFixed(1) : "0", z = D.length, B = D.filter((e) => e.rating >= 4).length, V = D.filter((e) => e.rating === 5).length, H = t(() => {
|
|
9
9
|
let e = {
|
|
10
10
|
5: 0,
|
|
11
11
|
4: 0,
|
|
@@ -13,99 +13,107 @@ function S() {
|
|
|
13
13
|
2: 0,
|
|
14
14
|
1: 0
|
|
15
15
|
};
|
|
16
|
-
for (let t of
|
|
16
|
+
for (let t of D) e[t.rating] = (e[t.rating] ?? 0) + 1;
|
|
17
17
|
return e;
|
|
18
|
-
}, [
|
|
19
|
-
let e = [...
|
|
20
|
-
if (
|
|
21
|
-
let t =
|
|
18
|
+
}, [D]), U = t(() => {
|
|
19
|
+
let e = [...D];
|
|
20
|
+
if (A.trim()) {
|
|
21
|
+
let t = A.toLowerCase();
|
|
22
22
|
e = e.filter((e) => e.comment.toLowerCase().includes(t) || e.clientName.toLowerCase().includes(t) || e.serviceName.toLowerCase().includes(t) || e.providerName.toLowerCase().includes(t));
|
|
23
23
|
}
|
|
24
|
-
return
|
|
24
|
+
return M !== null && (e = e.filter((e) => e.rating === M)), e.sort((e, t) => P === "date" ? new Date(t.createdAt).getTime() - new Date(e.createdAt).getTime() : P === "rating" ? t.rating - e.rating : t.helpful - e.helpful), e;
|
|
25
25
|
}, [
|
|
26
|
-
S,
|
|
27
|
-
T,
|
|
28
26
|
D,
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
A,
|
|
28
|
+
M,
|
|
29
|
+
P
|
|
30
|
+
]), W = (e) => Array.from({ length: 5 }, (t, n) => /* @__PURE__ */ p(l, { className: `h-4 w-4 ${n < e ? "fill-status-warning-text text-status-warning-text" : "text-text-secondary/30"}` }, n));
|
|
31
31
|
return /* @__PURE__ */ m("div", {
|
|
32
|
-
className: "space-y-6 p-6",
|
|
32
|
+
className: "space-y-6 p-6 bg-bg-canvas min-h-screen",
|
|
33
33
|
children: [
|
|
34
34
|
/* @__PURE__ */ m("div", {
|
|
35
|
-
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
36
|
-
children: [
|
|
37
|
-
|
|
38
|
-
children: "
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
className: "relative overflow-hidden rounded-card -mx-2 px-2 pt-1 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
36
|
+
children: [
|
|
37
|
+
/* @__PURE__ */ p(h, { intensity: "subtle" }),
|
|
38
|
+
/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("h1", {
|
|
39
|
+
className: "text-2xl font-semibold tracking-tight",
|
|
40
|
+
children: /* @__PURE__ */ p(C, { children: "Reviews" })
|
|
41
|
+
}), /* @__PURE__ */ p("p", {
|
|
42
|
+
className: "text-sm text-text-secondary",
|
|
43
|
+
children: "View and manage your service reviews and ratings."
|
|
44
|
+
})] }),
|
|
45
|
+
/* @__PURE__ */ m(_, {
|
|
46
|
+
size: "sm",
|
|
47
|
+
onClick: () => L(!0),
|
|
48
|
+
children: [/* @__PURE__ */ p(s, { className: "mr-2 h-4 w-4" }), " Write Review"]
|
|
49
|
+
})
|
|
50
|
+
]
|
|
51
|
+
}),
|
|
52
|
+
/* @__PURE__ */ p(E, {
|
|
53
|
+
icon: /* @__PURE__ */ p(l, { className: "h-4 w-4" }),
|
|
54
|
+
children: "Reviews build the trust that wins work on ProServiceWorld. Track the ratings you have received and given, see how your scores break down star-by-star, and write thoughtful reviews to recognise great providers and clients."
|
|
47
55
|
}),
|
|
48
56
|
/* @__PURE__ */ m("div", {
|
|
49
57
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
50
58
|
children: [
|
|
51
|
-
/* @__PURE__ */ p(
|
|
59
|
+
/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, {
|
|
52
60
|
className: "pt-4",
|
|
53
61
|
children: /* @__PURE__ */ m("div", {
|
|
54
62
|
className: "flex items-center gap-3",
|
|
55
63
|
children: [/* @__PURE__ */ p("div", {
|
|
56
|
-
className: "rounded-lg bg-
|
|
57
|
-
children: /* @__PURE__ */ p(l, { className: "h-5 w-5 text-
|
|
64
|
+
className: "rounded-lg bg-status-warning-bg-subtle p-2",
|
|
65
|
+
children: /* @__PURE__ */ p(l, { className: "h-5 w-5 text-status-warning-text" })
|
|
58
66
|
}), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("p", {
|
|
59
67
|
className: "text-2xl font-bold",
|
|
60
|
-
children:
|
|
68
|
+
children: R
|
|
61
69
|
}), /* @__PURE__ */ p("p", {
|
|
62
70
|
className: "text-xs text-text-secondary",
|
|
63
71
|
children: "Average Rating"
|
|
64
72
|
})] })]
|
|
65
73
|
})
|
|
66
74
|
}) }),
|
|
67
|
-
/* @__PURE__ */ p(
|
|
75
|
+
/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, {
|
|
68
76
|
className: "pt-4",
|
|
69
77
|
children: /* @__PURE__ */ m("div", {
|
|
70
78
|
className: "flex items-center gap-3",
|
|
71
79
|
children: [/* @__PURE__ */ p("div", {
|
|
72
|
-
className: "rounded-lg bg-
|
|
73
|
-
children: /* @__PURE__ */ p(a, { className: "h-5 w-5 text-blue
|
|
80
|
+
className: "rounded-lg bg-status-info-bg-subtle p-2",
|
|
81
|
+
children: /* @__PURE__ */ p(a, { className: "h-5 w-5 text-accent-blue" })
|
|
74
82
|
}), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("p", {
|
|
75
83
|
className: "text-2xl font-bold",
|
|
76
|
-
children:
|
|
84
|
+
children: z
|
|
77
85
|
}), /* @__PURE__ */ p("p", {
|
|
78
86
|
className: "text-xs text-text-secondary",
|
|
79
87
|
children: "Total Reviews"
|
|
80
88
|
})] })]
|
|
81
89
|
})
|
|
82
90
|
}) }),
|
|
83
|
-
/* @__PURE__ */ p(
|
|
91
|
+
/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, {
|
|
84
92
|
className: "pt-4",
|
|
85
93
|
children: /* @__PURE__ */ m("div", {
|
|
86
94
|
className: "flex items-center gap-3",
|
|
87
95
|
children: [/* @__PURE__ */ p("div", {
|
|
88
|
-
className: "rounded-lg bg-
|
|
89
|
-
children: /* @__PURE__ */ p(f, { className: "h-5 w-5 text-
|
|
96
|
+
className: "rounded-lg bg-status-success-bg-subtle p-2",
|
|
97
|
+
children: /* @__PURE__ */ p(f, { className: "h-5 w-5 text-status-success-text" })
|
|
90
98
|
}), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("p", {
|
|
91
99
|
className: "text-2xl font-bold",
|
|
92
|
-
children:
|
|
100
|
+
children: B
|
|
93
101
|
}), /* @__PURE__ */ p("p", {
|
|
94
102
|
className: "text-xs text-text-secondary",
|
|
95
103
|
children: "Positive (4-5 Stars)"
|
|
96
104
|
})] })]
|
|
97
105
|
})
|
|
98
106
|
}) }),
|
|
99
|
-
/* @__PURE__ */ p(
|
|
107
|
+
/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, {
|
|
100
108
|
className: "pt-4",
|
|
101
109
|
children: /* @__PURE__ */ m("div", {
|
|
102
110
|
className: "flex items-center gap-3",
|
|
103
111
|
children: [/* @__PURE__ */ p("div", {
|
|
104
|
-
className: "rounded-lg bg-purple-
|
|
105
|
-
children: /* @__PURE__ */ p(i, { className: "h-5 w-5 text-purple
|
|
112
|
+
className: "rounded-lg bg-accent-purple-subtle p-2",
|
|
113
|
+
children: /* @__PURE__ */ p(i, { className: "h-5 w-5 text-accent-purple" })
|
|
106
114
|
}), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("p", {
|
|
107
115
|
className: "text-2xl font-bold",
|
|
108
|
-
children:
|
|
116
|
+
children: V
|
|
109
117
|
}), /* @__PURE__ */ p("p", {
|
|
110
118
|
className: "text-xs text-text-secondary",
|
|
111
119
|
children: "5-Star Reviews"
|
|
@@ -114,10 +122,10 @@ function S() {
|
|
|
114
122
|
}) })
|
|
115
123
|
]
|
|
116
124
|
}),
|
|
117
|
-
/* @__PURE__ */ m(
|
|
118
|
-
className: "text-base",
|
|
125
|
+
/* @__PURE__ */ m(S, { children: [/* @__PURE__ */ p("h2", {
|
|
126
|
+
className: "mb-4 text-base font-semibold tracking-tight",
|
|
119
127
|
children: "Rating Distribution"
|
|
120
|
-
})
|
|
128
|
+
}), /* @__PURE__ */ p("div", { children: /* @__PURE__ */ p("div", {
|
|
121
129
|
className: "space-y-2",
|
|
122
130
|
children: [
|
|
123
131
|
5,
|
|
@@ -126,10 +134,10 @@ function S() {
|
|
|
126
134
|
2,
|
|
127
135
|
1
|
|
128
136
|
].map((e) => {
|
|
129
|
-
let t =
|
|
137
|
+
let t = H[e] ?? 0, n = z > 0 ? Math.round(t / z * 100) : 0;
|
|
130
138
|
return /* @__PURE__ */ m("button", {
|
|
131
|
-
onClick: () =>
|
|
132
|
-
className: `flex w-full items-center gap-3 rounded px-2 py-1 transition-colors ${
|
|
139
|
+
onClick: () => N(M === e ? null : e),
|
|
140
|
+
className: `flex w-full items-center gap-3 rounded px-2 py-1 transition-colors ${M === e ? "bg-action-primary-bg/10" : "hover:bg-bg-sunken/50"}`,
|
|
133
141
|
children: [
|
|
134
142
|
/* @__PURE__ */ m("span", {
|
|
135
143
|
className: "w-12 text-sm font-medium",
|
|
@@ -138,7 +146,7 @@ function S() {
|
|
|
138
146
|
/* @__PURE__ */ p("div", {
|
|
139
147
|
className: "flex-1 h-3 overflow-hidden rounded-full bg-bg-sunken",
|
|
140
148
|
children: /* @__PURE__ */ p("div", {
|
|
141
|
-
className: "h-3 rounded-full bg-
|
|
149
|
+
className: "h-3 rounded-full bg-status-warning-text transition-all",
|
|
142
150
|
style: { width: `${n}%` }
|
|
143
151
|
})
|
|
144
152
|
}),
|
|
@@ -161,17 +169,17 @@ function S() {
|
|
|
161
169
|
/* @__PURE__ */ p("div", {
|
|
162
170
|
className: "flex rounded-lg border",
|
|
163
171
|
children: ["received", "given"].map((e) => /* @__PURE__ */ p("button", {
|
|
164
|
-
onClick: () =>
|
|
165
|
-
className: `px-4 py-2 text-sm font-medium transition-colors ${
|
|
172
|
+
onClick: () => k(e),
|
|
173
|
+
className: `px-4 py-2 text-sm font-medium transition-colors ${O === e ? "bg-action-primary-bg text-action-primary-text" : "text-text-secondary hover:bg-bg-sunken/50"}`,
|
|
166
174
|
children: e === "received" ? "Reviews Received" : "Reviews Given"
|
|
167
175
|
}, e))
|
|
168
176
|
}),
|
|
169
177
|
/* @__PURE__ */ m("div", {
|
|
170
178
|
className: "relative flex-1",
|
|
171
|
-
children: [/* @__PURE__ */ p(c, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ p(
|
|
179
|
+
children: [/* @__PURE__ */ p(c, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ p(T, {
|
|
172
180
|
placeholder: "Search reviews...",
|
|
173
|
-
value:
|
|
174
|
-
onChange: (e) =>
|
|
181
|
+
value: A,
|
|
182
|
+
onChange: (e) => j(e.target.value),
|
|
175
183
|
className: "pl-10"
|
|
176
184
|
})]
|
|
177
185
|
}),
|
|
@@ -191,8 +199,8 @@ function S() {
|
|
|
191
199
|
label: "Most Helpful"
|
|
192
200
|
}
|
|
193
201
|
].map(({ field: e, label: t }) => /* @__PURE__ */ m("button", {
|
|
194
|
-
onClick: () =>
|
|
195
|
-
className: `flex items-center gap-1 rounded border px-3 py-1 text-xs ${
|
|
202
|
+
onClick: () => F(e),
|
|
203
|
+
className: `flex items-center gap-1 rounded border px-3 py-1 text-xs ${P === e ? "border-primary bg-action-primary-bg/5 text-primary" : "text-text-secondary hover:bg-bg-sunken/50"}`,
|
|
196
204
|
children: [/* @__PURE__ */ p(r, { className: "h-3 w-3" }), t]
|
|
197
205
|
}, e))]
|
|
198
206
|
})
|
|
@@ -200,10 +208,19 @@ function S() {
|
|
|
200
208
|
}),
|
|
201
209
|
/* @__PURE__ */ p("div", {
|
|
202
210
|
className: "space-y-4",
|
|
203
|
-
children:
|
|
204
|
-
className: "py-
|
|
205
|
-
children:
|
|
206
|
-
|
|
211
|
+
children: U.length === 0 ? /* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, {
|
|
212
|
+
className: "py-6",
|
|
213
|
+
children: /* @__PURE__ */ p(w, {
|
|
214
|
+
illustration: A || M !== null ? "empty-search" : "empty-notifications",
|
|
215
|
+
title: A || M !== null ? "No reviews match your filters" : "No reviews yet",
|
|
216
|
+
description: A || M !== null ? "Try clearing your search or rating filter to see more reviews." : "Reviews you receive and give will appear here as you complete projects.",
|
|
217
|
+
action: /* @__PURE__ */ m(_, {
|
|
218
|
+
size: "sm",
|
|
219
|
+
onClick: () => L(!0),
|
|
220
|
+
children: [/* @__PURE__ */ p(s, { className: "mr-2 h-4 w-4" }), " Write Review"]
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
}) }) : U.map((e) => /* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, {
|
|
207
224
|
className: "pt-6",
|
|
208
225
|
children: /* @__PURE__ */ m("div", {
|
|
209
226
|
className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between",
|
|
@@ -213,7 +230,7 @@ function S() {
|
|
|
213
230
|
/* @__PURE__ */ m("div", {
|
|
214
231
|
className: "flex items-center gap-3",
|
|
215
232
|
children: [/* @__PURE__ */ p("div", {
|
|
216
|
-
className: "flex h-10 w-10 items-center justify-center rounded-full bg-
|
|
233
|
+
className: "flex h-10 w-10 items-center justify-center rounded-full bg-accent-soft font-semibold text-text-primary",
|
|
217
234
|
children: e.clientName.charAt(0)
|
|
218
235
|
}), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("p", {
|
|
219
236
|
className: "font-medium",
|
|
@@ -232,13 +249,13 @@ function S() {
|
|
|
232
249
|
children: [
|
|
233
250
|
/* @__PURE__ */ p("div", {
|
|
234
251
|
className: "flex",
|
|
235
|
-
children:
|
|
252
|
+
children: W(e.rating)
|
|
236
253
|
}),
|
|
237
254
|
/* @__PURE__ */ m("span", {
|
|
238
255
|
className: "text-sm font-medium",
|
|
239
256
|
children: [e.rating, ".0"]
|
|
240
257
|
}),
|
|
241
|
-
e.recommended && /* @__PURE__ */ p(
|
|
258
|
+
e.recommended && /* @__PURE__ */ p(g, {
|
|
242
259
|
variant: "secondary",
|
|
243
260
|
className: "text-xs",
|
|
244
261
|
children: "Recommended"
|
|
@@ -265,29 +282,29 @@ function S() {
|
|
|
265
282
|
}), /* @__PURE__ */ m("div", {
|
|
266
283
|
className: "flex items-center gap-4 text-sm",
|
|
267
284
|
children: [/* @__PURE__ */ m("button", {
|
|
268
|
-
className: "flex items-center gap-1 text-text-secondary hover:text-
|
|
285
|
+
className: "flex items-center gap-1 text-text-secondary hover:text-status-success-text",
|
|
269
286
|
children: [/* @__PURE__ */ p(d, { className: "h-4 w-4" }), /* @__PURE__ */ p("span", { children: e.helpful })]
|
|
270
287
|
}), /* @__PURE__ */ m("button", {
|
|
271
|
-
className: "flex items-center gap-1 text-text-secondary hover:text-
|
|
288
|
+
className: "flex items-center gap-1 text-text-secondary hover:text-status-error-text",
|
|
272
289
|
children: [/* @__PURE__ */ p(u, { className: "h-4 w-4" }), /* @__PURE__ */ p("span", { children: e.notHelpful })]
|
|
273
290
|
})]
|
|
274
291
|
})]
|
|
275
292
|
})
|
|
276
293
|
}) }, e.id))
|
|
277
294
|
}),
|
|
278
|
-
|
|
295
|
+
I && /* @__PURE__ */ p("div", {
|
|
279
296
|
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50",
|
|
280
|
-
children: /* @__PURE__ */ m(
|
|
297
|
+
children: /* @__PURE__ */ m(v, {
|
|
281
298
|
className: "mx-4 w-full max-w-lg",
|
|
282
|
-
children: [/* @__PURE__ */ p(
|
|
299
|
+
children: [/* @__PURE__ */ p(b, { children: /* @__PURE__ */ m("div", {
|
|
283
300
|
className: "flex items-center justify-between",
|
|
284
|
-
children: [/* @__PURE__ */ p(
|
|
301
|
+
children: [/* @__PURE__ */ p(x, { children: "Write a Review" }), /* @__PURE__ */ p(_, {
|
|
285
302
|
variant: "outline",
|
|
286
303
|
size: "sm",
|
|
287
|
-
onClick: () =>
|
|
304
|
+
onClick: () => L(!1),
|
|
288
305
|
children: "Close"
|
|
289
306
|
})]
|
|
290
|
-
}) }), /* @__PURE__ */ m(
|
|
307
|
+
}) }), /* @__PURE__ */ m(y, {
|
|
291
308
|
className: "space-y-4",
|
|
292
309
|
children: [
|
|
293
310
|
/* @__PURE__ */ m("div", {
|
|
@@ -298,7 +315,7 @@ function S() {
|
|
|
298
315
|
}), /* @__PURE__ */ p("div", {
|
|
299
316
|
className: "flex gap-1",
|
|
300
317
|
children: Array.from({ length: 5 }, (e, t) => /* @__PURE__ */ p("button", {
|
|
301
|
-
className: "text-text-secondary hover:text-
|
|
318
|
+
className: "text-text-secondary hover:text-status-warning-text",
|
|
302
319
|
children: /* @__PURE__ */ p(l, { className: "h-8 w-8" })
|
|
303
320
|
}, t))
|
|
304
321
|
})]
|
|
@@ -316,12 +333,12 @@ function S() {
|
|
|
316
333
|
}),
|
|
317
334
|
/* @__PURE__ */ m("div", {
|
|
318
335
|
className: "flex gap-2",
|
|
319
|
-
children: [/* @__PURE__ */ p(
|
|
336
|
+
children: [/* @__PURE__ */ p(_, {
|
|
320
337
|
variant: "outline",
|
|
321
338
|
className: "flex-1",
|
|
322
|
-
onClick: () =>
|
|
339
|
+
onClick: () => L(!1),
|
|
323
340
|
children: "Cancel"
|
|
324
|
-
}), /* @__PURE__ */ p(
|
|
341
|
+
}), /* @__PURE__ */ p(_, {
|
|
325
342
|
className: "flex-1",
|
|
326
343
|
children: "Submit Review"
|
|
327
344
|
})]
|
|
@@ -334,6 +351,6 @@ function S() {
|
|
|
334
351
|
});
|
|
335
352
|
}
|
|
336
353
|
//#endregion
|
|
337
|
-
export {
|
|
354
|
+
export { D as ReviewsPage };
|
|
338
355
|
|
|
339
356
|
//# sourceMappingURL=ReviewsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReviewsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/ReviewsPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Star,\n Search,\n ThumbsUp,\n ThumbsDown,\n Filter,\n ArrowUpDown,\n MessageSquare,\n TrendingUp,\n Award,\n BarChart3,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\n\ntype ViewMode = 'received' | 'given';\ntype SortBy = 'date' | 'rating' | 'helpful';\n\nexport function ReviewsPage() {\n const { reviews } = useProServiceWorldStore();\n const [viewMode, setViewMode] = useState<ViewMode>('received');\n const [searchQuery, setSearchQuery] = useState('');\n const [ratingFilter, setRatingFilter] = useState<number | null>(null);\n const [sortBy, setSortBy] = useState<SortBy>('date');\n const [showWriteModal, setShowWriteModal] = useState(false);\n\n // Stats\n const avgRating =\n reviews.length > 0\n ? (reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length).toFixed(1)\n : '0';\n const totalReviews = reviews.length;\n const positiveReviews = reviews.filter((r) => r.rating >= 4).length;\n const fiveStarCount = reviews.filter((r) => r.rating === 5).length;\n\n // Rating distribution\n const ratingDistribution = useMemo(() => {\n const dist: Record<number, number> = { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 };\n for (const r of reviews) {\n dist[r.rating] = (dist[r.rating] ?? 0) + 1;\n }\n return dist;\n }, [reviews]);\n\n const filtered = useMemo(() => {\n let result = [...reviews];\n\n if (searchQuery.trim()) {\n const q = searchQuery.toLowerCase();\n result = result.filter(\n (r) =>\n r.comment.toLowerCase().includes(q) ||\n r.clientName.toLowerCase().includes(q) ||\n r.serviceName.toLowerCase().includes(q) ||\n r.providerName.toLowerCase().includes(q)\n );\n }\n\n if (ratingFilter !== null) {\n result = result.filter((r) => r.rating === ratingFilter);\n }\n\n result.sort((a, b) => {\n if (sortBy === 'date')\n return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();\n if (sortBy === 'rating') return b.rating - a.rating;\n return b.helpful - a.helpful;\n });\n\n return result;\n }, [reviews, searchQuery, ratingFilter, sortBy]);\n\n const renderStars = (rating: number) => {\n return Array.from({ length: 5 }, (_, i) => (\n <Star\n key={i}\n className={`h-4 w-4 ${i < rating ? 'fill-yellow-400 text-yellow-400' : 'text-text-secondary/30'}`}\n />\n ));\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Reviews</h1>\n <p className=\"text-sm text-text-secondary\">\n View and manage your service reviews and ratings.\n </p>\n </div>\n <Button size=\"sm\" onClick={() => setShowWriteModal(true)}>\n <MessageSquare className=\"mr-2 h-4 w-4\" /> Write Review\n </Button>\n </div>\n\n {/* Stats Overview */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-yellow-100 p-2 dark:bg-yellow-950\">\n <Star className=\"h-5 w-5 text-yellow-600\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold\">{avgRating}</p>\n <p className=\"text-xs text-text-secondary\">Average Rating</p>\n </div>\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-blue-100 p-2 dark:bg-blue-950\">\n <BarChart3 className=\"h-5 w-5 text-blue-600\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold\">{totalReviews}</p>\n <p className=\"text-xs text-text-secondary\">Total Reviews</p>\n </div>\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-green-100 p-2 dark:bg-green-950\">\n <TrendingUp className=\"h-5 w-5 text-green-600\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold\">{positiveReviews}</p>\n <p className=\"text-xs text-text-secondary\">Positive (4-5 Stars)</p>\n </div>\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-purple-100 p-2 dark:bg-purple-950\">\n <Award className=\"h-5 w-5 text-purple-600\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold\">{fiveStarCount}</p>\n <p className=\"text-xs text-text-secondary\">5-Star Reviews</p>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n\n {/* Rating Distribution */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Rating Distribution</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2\">\n {[5, 4, 3, 2, 1].map((star) => {\n const count = ratingDistribution[star] ?? 0;\n const pct = totalReviews > 0 ? Math.round((count / totalReviews) * 100) : 0;\n return (\n <button\n key={star}\n onClick={() => setRatingFilter(ratingFilter === star ? null : star)}\n className={`flex w-full items-center gap-3 rounded px-2 py-1 transition-colors ${\n ratingFilter === star ? 'bg-action-primary-bg/10' : 'hover:bg-bg-sunken/50'\n }`}\n >\n <span className=\"w-12 text-sm font-medium\">{star} star</span>\n <div className=\"flex-1 h-3 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-3 rounded-full bg-yellow-400 transition-all\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"w-16 text-right text-sm text-text-secondary\">\n {count} ({pct}%)\n </span>\n </button>\n );\n })}\n </div>\n </CardContent>\n </Card>\n\n {/* View Toggle + Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center\">\n <div className=\"flex rounded-lg border\">\n {(['received', 'given'] as const).map((mode) => (\n <button\n key={mode}\n onClick={() => setViewMode(mode)}\n className={`px-4 py-2 text-sm font-medium transition-colors ${\n viewMode === mode\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'text-text-secondary hover:bg-bg-sunken/50'\n }`}\n >\n {mode === 'received' ? 'Reviews Received' : 'Reviews Given'}\n </button>\n ))}\n </div>\n\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search reviews...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"pl-10\"\n />\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Filter className=\"h-4 w-4 text-text-secondary\" />\n {[\n { field: 'date' as SortBy, label: 'Newest' },\n { field: 'rating' as SortBy, label: 'Highest Rated' },\n { field: 'helpful' as SortBy, label: 'Most Helpful' },\n ].map(({ field, label }) => (\n <button\n key={field}\n onClick={() => setSortBy(field)}\n className={`flex items-center gap-1 rounded border px-3 py-1 text-xs ${\n sortBy === field\n ? 'border-primary bg-action-primary-bg/5 text-primary'\n : 'text-text-secondary hover:bg-bg-sunken/50'\n }`}\n >\n <ArrowUpDown className=\"h-3 w-3\" />\n {label}\n </button>\n ))}\n </div>\n </div>\n\n {/* Review List */}\n <div className=\"space-y-4\">\n {filtered.length === 0 ? (\n <Card>\n <CardContent className=\"py-12 text-center text-text-secondary\">\n No reviews match your filters.\n </CardContent>\n </Card>\n ) : (\n filtered.map((review) => (\n <Card key={review.id}>\n <CardContent className=\"pt-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex-1 space-y-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-full bg-action-primary-bg/10 font-semibold text-primary\">\n {review.clientName.charAt(0)}\n </div>\n <div>\n <p className=\"font-medium\">{review.clientName}</p>\n <p className=\"text-xs text-text-secondary\">\n {review.serviceName} - {review.providerName}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <div className=\"flex\">{renderStars(review.rating)}</div>\n <span className=\"text-sm font-medium\">{review.rating}.0</span>\n {review.recommended && (\n <Badge variant=\"secondary\" className=\"text-xs\">\n Recommended\n </Badge>\n )}\n </div>\n\n <p className=\"text-sm text-text-secondary leading-relaxed\">\n {review.comment}\n </p>\n\n <div className=\"flex flex-wrap gap-4 text-xs text-text-secondary\">\n <span>Project Value: ${review.projectValue.toLocaleString()}</span>\n <span>Duration: {review.projectDuration} days</span>\n <span>{new Date(review.createdAt).toLocaleDateString()}</span>\n </div>\n </div>\n\n <div className=\"flex items-center gap-4 text-sm\">\n <button className=\"flex items-center gap-1 text-text-secondary hover:text-green-600\">\n <ThumbsUp className=\"h-4 w-4\" />\n <span>{review.helpful}</span>\n </button>\n <button className=\"flex items-center gap-1 text-text-secondary hover:text-red-600\">\n <ThumbsDown className=\"h-4 w-4\" />\n <span>{review.notHelpful}</span>\n </button>\n </div>\n </div>\n </CardContent>\n </Card>\n ))\n )}\n </div>\n\n {/* Write Review Modal */}\n {showWriteModal && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n <Card className=\"mx-4 w-full max-w-lg\">\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>Write a Review</CardTitle>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setShowWriteModal(false)}>\n Close\n </Button>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">Rating</label>\n <div className=\"flex gap-1\">\n {Array.from({ length: 5 }, (_, i) => (\n <button key={i} className=\"text-text-secondary hover:text-yellow-400\">\n <Star className=\"h-8 w-8\" />\n </button>\n ))}\n </div>\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">Your Review</label>\n <textarea\n className=\"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\"\n rows={4}\n placeholder=\"Share your experience with this service...\"\n />\n </div>\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n className=\"flex-1\"\n onClick={() => setShowWriteModal(false)}\n >\n Cancel\n </Button>\n <Button className=\"flex-1\">Submit Review</Button>\n </div>\n </CardContent>\n </Card>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ReviewsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/ReviewsPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n EmphasisPanel,\n AuroraBackground,\n GradientText,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Star,\n Search,\n ThumbsUp,\n ThumbsDown,\n Filter,\n ArrowUpDown,\n MessageSquare,\n TrendingUp,\n Award,\n BarChart3,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\n\ntype ViewMode = 'received' | 'given';\ntype SortBy = 'date' | 'rating' | 'helpful';\n\nexport function ReviewsPage() {\n const { reviews } = useProServiceWorldStore();\n const [viewMode, setViewMode] = useState<ViewMode>('received');\n const [searchQuery, setSearchQuery] = useState('');\n const [ratingFilter, setRatingFilter] = useState<number | null>(null);\n const [sortBy, setSortBy] = useState<SortBy>('date');\n const [showWriteModal, setShowWriteModal] = useState(false);\n\n // Stats\n const avgRating =\n reviews.length > 0\n ? (reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length).toFixed(1)\n : '0';\n const totalReviews = reviews.length;\n const positiveReviews = reviews.filter((r) => r.rating >= 4).length;\n const fiveStarCount = reviews.filter((r) => r.rating === 5).length;\n\n // Rating distribution\n const ratingDistribution = useMemo(() => {\n const dist: Record<number, number> = { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 };\n for (const r of reviews) {\n dist[r.rating] = (dist[r.rating] ?? 0) + 1;\n }\n return dist;\n }, [reviews]);\n\n const filtered = useMemo(() => {\n let result = [...reviews];\n\n if (searchQuery.trim()) {\n const q = searchQuery.toLowerCase();\n result = result.filter(\n (r) =>\n r.comment.toLowerCase().includes(q) ||\n r.clientName.toLowerCase().includes(q) ||\n r.serviceName.toLowerCase().includes(q) ||\n r.providerName.toLowerCase().includes(q)\n );\n }\n\n if (ratingFilter !== null) {\n result = result.filter((r) => r.rating === ratingFilter);\n }\n\n result.sort((a, b) => {\n if (sortBy === 'date')\n return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();\n if (sortBy === 'rating') return b.rating - a.rating;\n return b.helpful - a.helpful;\n });\n\n return result;\n }, [reviews, searchQuery, ratingFilter, sortBy]);\n\n const renderStars = (rating: number) => {\n return Array.from({ length: 5 }, (_, i) => (\n <Star\n key={i}\n className={`h-4 w-4 ${i < rating ? 'fill-status-warning-text text-status-warning-text' : 'text-text-secondary/30'}`}\n />\n ));\n };\n\n return (\n <div className=\"space-y-6 p-6 bg-bg-canvas min-h-screen\">\n {/* Hero header — the ONE aurora backdrop for this page. */}\n <div className=\"relative overflow-hidden rounded-card -mx-2 px-2 pt-1 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight\">\n <GradientText>Reviews</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n View and manage your service reviews and ratings.\n </p>\n </div>\n <Button size=\"sm\" onClick={() => setShowWriteModal(true)}>\n <MessageSquare className=\"mr-2 h-4 w-4\" /> Write Review\n </Button>\n </div>\n\n <PagePurpose icon={<Star className=\"h-4 w-4\" />}>\n Reviews build the trust that wins work on ProServiceWorld. Track the ratings you have\n received and given, see how your scores break down star-by-star, and write thoughtful\n reviews to recognise great providers and clients.\n </PagePurpose>\n\n {/* Stats Overview */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-status-warning-bg-subtle p-2\">\n <Star className=\"h-5 w-5 text-status-warning-text\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold\">{avgRating}</p>\n <p className=\"text-xs text-text-secondary\">Average Rating</p>\n </div>\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-status-info-bg-subtle p-2\">\n <BarChart3 className=\"h-5 w-5 text-accent-blue\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold\">{totalReviews}</p>\n <p className=\"text-xs text-text-secondary\">Total Reviews</p>\n </div>\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-status-success-bg-subtle p-2\">\n <TrendingUp className=\"h-5 w-5 text-status-success-text\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold\">{positiveReviews}</p>\n <p className=\"text-xs text-text-secondary\">Positive (4-5 Stars)</p>\n </div>\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-accent-purple-subtle p-2\">\n <Award className=\"h-5 w-5 text-accent-purple\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold\">{fiveStarCount}</p>\n <p className=\"text-xs text-text-secondary\">5-Star Reviews</p>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n\n {/* Rating Distribution — the single emphasised insight zone */}\n <EmphasisPanel>\n <h2 className=\"mb-4 text-base font-semibold tracking-tight\">Rating Distribution</h2>\n <div>\n <div className=\"space-y-2\">\n {[5, 4, 3, 2, 1].map((star) => {\n const count = ratingDistribution[star] ?? 0;\n const pct = totalReviews > 0 ? Math.round((count / totalReviews) * 100) : 0;\n return (\n <button\n key={star}\n onClick={() => setRatingFilter(ratingFilter === star ? null : star)}\n className={`flex w-full items-center gap-3 rounded px-2 py-1 transition-colors ${\n ratingFilter === star ? 'bg-action-primary-bg/10' : 'hover:bg-bg-sunken/50'\n }`}\n >\n <span className=\"w-12 text-sm font-medium\">{star} star</span>\n <div className=\"flex-1 h-3 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-3 rounded-full bg-status-warning-text transition-all\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"w-16 text-right text-sm text-text-secondary\">\n {count} ({pct}%)\n </span>\n </button>\n );\n })}\n </div>\n </div>\n </EmphasisPanel>\n\n {/* View Toggle + Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center\">\n <div className=\"flex rounded-lg border\">\n {(['received', 'given'] as const).map((mode) => (\n <button\n key={mode}\n onClick={() => setViewMode(mode)}\n className={`px-4 py-2 text-sm font-medium transition-colors ${\n viewMode === mode\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'text-text-secondary hover:bg-bg-sunken/50'\n }`}\n >\n {mode === 'received' ? 'Reviews Received' : 'Reviews Given'}\n </button>\n ))}\n </div>\n\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search reviews...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"pl-10\"\n />\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Filter className=\"h-4 w-4 text-text-secondary\" />\n {[\n { field: 'date' as SortBy, label: 'Newest' },\n { field: 'rating' as SortBy, label: 'Highest Rated' },\n { field: 'helpful' as SortBy, label: 'Most Helpful' },\n ].map(({ field, label }) => (\n <button\n key={field}\n onClick={() => setSortBy(field)}\n className={`flex items-center gap-1 rounded border px-3 py-1 text-xs ${\n sortBy === field\n ? 'border-primary bg-action-primary-bg/5 text-primary'\n : 'text-text-secondary hover:bg-bg-sunken/50'\n }`}\n >\n <ArrowUpDown className=\"h-3 w-3\" />\n {label}\n </button>\n ))}\n </div>\n </div>\n\n {/* Review List */}\n <div className=\"space-y-4\">\n {filtered.length === 0 ? (\n <Card>\n <CardContent className=\"py-6\">\n <IllustratedEmptyState\n illustration={\n searchQuery || ratingFilter !== null ? 'empty-search' : 'empty-notifications'\n }\n title={searchQuery || ratingFilter !== null ? 'No reviews match your filters' : 'No reviews yet'}\n description={\n searchQuery || ratingFilter !== null\n ? 'Try clearing your search or rating filter to see more reviews.'\n : 'Reviews you receive and give will appear here as you complete projects.'\n }\n action={\n <Button size=\"sm\" onClick={() => setShowWriteModal(true)}>\n <MessageSquare className=\"mr-2 h-4 w-4\" /> Write Review\n </Button>\n }\n />\n </CardContent>\n </Card>\n ) : (\n filtered.map((review) => (\n <Card key={review.id}>\n <CardContent className=\"pt-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex-1 space-y-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-full bg-accent-soft font-semibold text-text-primary\">\n {review.clientName.charAt(0)}\n </div>\n <div>\n <p className=\"font-medium\">{review.clientName}</p>\n <p className=\"text-xs text-text-secondary\">\n {review.serviceName} - {review.providerName}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <div className=\"flex\">{renderStars(review.rating)}</div>\n <span className=\"text-sm font-medium\">{review.rating}.0</span>\n {review.recommended && (\n <Badge variant=\"secondary\" className=\"text-xs\">\n Recommended\n </Badge>\n )}\n </div>\n\n <p className=\"text-sm text-text-secondary leading-relaxed\">\n {review.comment}\n </p>\n\n <div className=\"flex flex-wrap gap-4 text-xs text-text-secondary\">\n <span>Project Value: ${review.projectValue.toLocaleString()}</span>\n <span>Duration: {review.projectDuration} days</span>\n <span>{new Date(review.createdAt).toLocaleDateString()}</span>\n </div>\n </div>\n\n <div className=\"flex items-center gap-4 text-sm\">\n <button className=\"flex items-center gap-1 text-text-secondary hover:text-status-success-text\">\n <ThumbsUp className=\"h-4 w-4\" />\n <span>{review.helpful}</span>\n </button>\n <button className=\"flex items-center gap-1 text-text-secondary hover:text-status-error-text\">\n <ThumbsDown className=\"h-4 w-4\" />\n <span>{review.notHelpful}</span>\n </button>\n </div>\n </div>\n </CardContent>\n </Card>\n ))\n )}\n </div>\n\n {/* Write Review Modal */}\n {showWriteModal && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n <Card className=\"mx-4 w-full max-w-lg\">\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>Write a Review</CardTitle>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setShowWriteModal(false)}>\n Close\n </Button>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">Rating</label>\n <div className=\"flex gap-1\">\n {Array.from({ length: 5 }, (_, i) => (\n <button key={i} className=\"text-text-secondary hover:text-status-warning-text\">\n <Star className=\"h-8 w-8\" />\n </button>\n ))}\n </div>\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">Your Review</label>\n <textarea\n className=\"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\"\n rows={4}\n placeholder=\"Share your experience with this service...\"\n />\n </div>\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n className=\"flex-1\"\n onClick={() => setShowWriteModal(false)}\n >\n Cancel\n </Button>\n <Button className=\"flex-1\">Submit Review</Button>\n </div>\n </CardContent>\n </Card>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|