@burdenoff/microfe-coolandlovely 2026.706.2 → 2026.706.3

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.
Files changed (75) hide show
  1. package/dist/coolandlovely/CoolAndLovelyRoot.js +62 -46
  2. package/dist/coolandlovely/CoolAndLovelyRoot.js.map +1 -1
  3. package/dist/coolandlovely/components/AddToLookbookButton.js +22 -22
  4. package/dist/coolandlovely/components/AddToLookbookButton.js.map +1 -1
  5. package/dist/coolandlovely/components/BrandCover.js +39 -0
  6. package/dist/coolandlovely/components/BrandCover.js.map +1 -0
  7. package/dist/coolandlovely/components/BrandCreatorAvatars.js +57 -0
  8. package/dist/coolandlovely/components/BrandCreatorAvatars.js.map +1 -0
  9. package/dist/coolandlovely/components/ColourSwatchRow.js +86 -0
  10. package/dist/coolandlovely/components/ColourSwatchRow.js.map +1 -0
  11. package/dist/coolandlovely/components/Comments.js +4 -4
  12. package/dist/coolandlovely/components/Comments.js.map +1 -1
  13. package/dist/coolandlovely/components/FileImage.js.map +1 -1
  14. package/dist/coolandlovely/components/ImageUploadField.js +139 -57
  15. package/dist/coolandlovely/components/ImageUploadField.js.map +1 -1
  16. package/dist/coolandlovely/components/LookCard.js +77 -0
  17. package/dist/coolandlovely/components/LookCard.js.map +1 -0
  18. package/dist/coolandlovely/components/MetricsCard.js +1 -1
  19. package/dist/coolandlovely/components/MetricsCard.js.map +1 -1
  20. package/dist/coolandlovely/components/ShopItemCard.js +45 -0
  21. package/dist/coolandlovely/components/ShopItemCard.js.map +1 -0
  22. package/dist/coolandlovely/components/StepTracker.js +35 -0
  23. package/dist/coolandlovely/components/StepTracker.js.map +1 -0
  24. package/dist/coolandlovely/components/Thumbnail.js +15 -11
  25. package/dist/coolandlovely/components/Thumbnail.js.map +1 -1
  26. package/dist/coolandlovely/components/VisibilityChip.js +32 -0
  27. package/dist/coolandlovely/components/VisibilityChip.js.map +1 -0
  28. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js +94 -69
  29. package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
  30. package/dist/coolandlovely/hooks/useFiles.js +49 -32
  31. package/dist/coolandlovely/hooks/useFiles.js.map +1 -1
  32. package/dist/coolandlovely/pages/HomePage.js +112 -133
  33. package/dist/coolandlovely/pages/HomePage.js.map +1 -1
  34. package/dist/coolandlovely/pages/brands/BrandDetailPage.js +253 -242
  35. package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -1
  36. package/dist/coolandlovely/pages/brands/BrandFormPage.js +116 -112
  37. package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -1
  38. package/dist/coolandlovely/pages/brands/BrandsListPage.js +13 -13
  39. package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -1
  40. package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js +117 -95
  41. package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js.map +1 -1
  42. package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js +1 -1
  43. package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js +50 -43
  44. package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js.map +1 -1
  45. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +131 -108
  46. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
  47. package/dist/coolandlovely/pages/collections/CollectionFormPage.js +1 -1
  48. package/dist/coolandlovely/pages/collections/CollectionsListPage.js +59 -56
  49. package/dist/coolandlovely/pages/collections/CollectionsListPage.js.map +1 -1
  50. package/dist/coolandlovely/pages/discover/DiscoverPage.js +1 -1
  51. package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -1
  52. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +175 -147
  53. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
  54. package/dist/coolandlovely/pages/items/StyleItemFormPage.js +1 -1
  55. package/dist/coolandlovely/pages/items/StyleItemsListPage.js +71 -62
  56. package/dist/coolandlovely/pages/items/StyleItemsListPage.js.map +1 -1
  57. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +149 -127
  58. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
  59. package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js +69 -53
  60. package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js.map +1 -1
  61. package/dist/coolandlovely/pages/looks/LookDetailPage.js +135 -113
  62. package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
  63. package/dist/coolandlovely/pages/looks/LookFormPage.js +122 -110
  64. package/dist/coolandlovely/pages/looks/LookFormPage.js.map +1 -1
  65. package/dist/coolandlovely/pages/looks/LooksListPage.js +60 -97
  66. package/dist/coolandlovely/pages/looks/LooksListPage.js.map +1 -1
  67. package/dist/coolandlovely/pages/profile/StyleProfilePage.js +372 -210
  68. package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
  69. package/dist/coolandlovely/pages/trends/TrendsPage.js +1 -1
  70. package/dist/coolandlovely/pages/trends/TrendsPage.js.map +1 -1
  71. package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js +130 -94
  72. package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js.map +1 -1
  73. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +75 -76
  74. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
  75. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"StyleProfilePage.js","names":[],"sources":["../../../../src/coolandlovely/pages/profile/StyleProfilePage.tsx"],"sourcesContent":["/**\n * Style Profile Page (self-scoped)\n * @module coolandlovely/pages/profile\n *\n * Views and edits the calling shopper's style profile (`cooMyStyleProfile` +\n * `cooUpsertStyleProfile`). One profile per workspace per shopper.\n */\n\nimport { useEffect, useState } from 'react';\nimport { Loader2, Save, UserCircle, Pencil, X, Users } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { TagList } from '../../components/TagList';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { ActorName } from '../../components/ActorName';\nimport { FollowButton } from '../../components/FollowButton';\nimport { useMyStyleProfile, useUpsertStyleProfile, useMyFollowing } from '../../hooks';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooVisibility } from '../../types';\n\nconst VISIBILITIES: CooVisibility[] = ['PUBLIC', 'FOLLOWERS', 'PRIVATE'];\n\ninterface FormState {\n displayName: string;\n bio: string;\n visibility: CooVisibility;\n stylePreferences: string;\n palette: string;\n favouriteBrands: string;\n}\n\nconst EMPTY: FormState = {\n displayName: '',\n bio: '',\n visibility: 'PRIVATE',\n stylePreferences: '',\n palette: '',\n favouriteBrands: '',\n};\n\nexport function StyleProfilePage() {\n const query = useMyStyleProfile();\n const upsert = useUpsertStyleProfile();\n const following = useMyFollowing();\n\n const [editing, setEditing] = useState(false);\n const [form, setForm] = useState<FormState>(EMPTY);\n\n const profile = query.data;\n const hasProfile = Boolean(profile);\n const followingList = following.data ?? [];\n\n useEffect(() => {\n if (profile) {\n setForm({\n displayName: profile.displayName ?? '',\n bio: profile.bio ?? '',\n visibility: profile.visibility,\n stylePreferences: profile.stylePreferences.join(', '),\n palette: profile.palette.join(', '),\n favouriteBrands: profile.favouriteBrands.join(', '),\n });\n }\n }, [profile]);\n\n // If there is no profile yet, drop straight into edit mode.\n useEffect(() => {\n if (!query.isLoading && !hasProfile) {\n setEditing(true);\n }\n }, [query.isLoading, hasProfile]);\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const visibilityOptions = VISIBILITIES.map((v) => ({ value: v, label: titleCase(v) }));\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n upsert.mutate(\n {\n displayName: form.displayName.trim() || undefined,\n bio: form.bio.trim() || undefined,\n visibility: form.visibility,\n stylePreferences: parseCsv(form.stylePreferences),\n palette: parseCsv(form.palette),\n favouriteBrands: parseCsv(form.favouriteBrands),\n },\n { onSuccess: () => setEditing(false) }\n );\n };\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Style Profile\">\n <LoadingView label=\"Loading your style profile…\" />\n </PageLayout>\n );\n }\n\n if (query.isError) {\n return (\n <PageLayout title=\"Style Profile\">\n <ErrorView onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n // ----- View mode -----\n if (!editing && profile) {\n return (\n <PageLayout\n title=\"Style Profile\"\n description=\"Your taste, palette and sizing — the spine of personalised discovery\"\n actions={\n <button\n type=\"button\"\n onClick={() => setEditing(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n }\n >\n <div className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"flex h-14 w-14 items-center justify-center rounded-full bg-accent-purple-subtle text-text-primary\">\n <UserCircle className=\"h-7 w-7\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary\">\n {profile.displayName || 'Your style profile'}\n </h2>\n <StatusBadge status={profile.visibility} className=\"mt-1\" />\n </div>\n </div>\n {profile.bio && <p className=\"mt-4 text-sm text-text-secondary\">{profile.bio}</p>}\n\n <div className=\"mt-4 flex items-center gap-6\">\n <span className=\"text-sm text-text-secondary\">\n <span className=\"font-semibold text-text-primary tabular-nums\">\n {profile.followerCount ?? 0}\n </span>{' '}\n {profile.followerCount === 1 ? 'follower' : 'followers'}\n </span>\n <span className=\"text-sm text-text-secondary\">\n <span className=\"font-semibold text-text-primary tabular-nums\">\n {profile.followingCount ?? 0}\n </span>{' '}\n following\n </span>\n </div>\n\n <div className=\"mt-6 space-y-4\">\n <div>\n <p className=\"mb-1.5 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Style preferences\n </p>\n {profile.stylePreferences.length ? (\n <TagList tags={profile.stylePreferences} tone=\"accent\" max={20} />\n ) : (\n <p className=\"text-sm text-text-secondary\">—</p>\n )}\n </div>\n <div>\n <p className=\"mb-1.5 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Palette\n </p>\n {profile.palette.length ? (\n <TagList tags={profile.palette} max={20} />\n ) : (\n <p className=\"text-sm text-text-secondary\">—</p>\n )}\n </div>\n <div>\n <p className=\"mb-1.5 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Favourite brands\n </p>\n {profile.favouriteBrands.length ? (\n <TagList tags={profile.favouriteBrands} max={20} />\n ) : (\n <p className=\"text-sm text-text-secondary\">—</p>\n )}\n </div>\n </div>\n </GlassCard>\n\n <section className=\"mt-6\">\n <h3 className=\"mb-3 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Users className=\"h-5 w-5\" /> People you follow ({followingList.length})\n </h3>\n {following.isLoading ? (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" /> Loading…\n </div>\n ) : followingList.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n You are not following anyone yet. Follow creators from their looks to see their\n latest in Discover.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-seam rounded-lg border border-border-seam bg-bg-surface\">\n {followingList.map((p) => (\n <li key={p.id} className=\"flex items-center gap-3 px-4 py-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent-purple-subtle text-text-primary\">\n <UserCircle className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {p.displayName || <ActorName id={p.ownerId} />}\n </p>\n {p.bio && <p className=\"truncate text-xs text-text-secondary\">{p.bio}</p>}\n </div>\n <FollowButton followeeId={p.ownerId} size=\"sm\" />\n </li>\n ))}\n </ul>\n )}\n </section>\n </div>\n </PageLayout>\n );\n }\n\n // ----- Edit / create mode -----\n return (\n <PageLayout\n title={hasProfile ? 'Edit style profile' : 'Create your style profile'}\n description=\"This powers your personalised discovery feed\"\n actions={\n hasProfile ? (\n <button\n type=\"button\"\n onClick={() => setEditing(false)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <X className=\"h-4 w-4\" /> Cancel\n </button>\n ) : undefined\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <TextField\n id=\"displayName\"\n label=\"Display name\"\n value={form.displayName}\n onChange={(v) => setField('displayName', v)}\n placeholder=\"How you want to appear\"\n />\n <TextAreaField\n id=\"bio\"\n label=\"Bio\"\n value={form.bio}\n onChange={(v) => setField('bio', v)}\n placeholder=\"A sentence about your style…\"\n rows={3}\n className=\"mt-4\"\n />\n <SelectField\n id=\"visibility\"\n label=\"Visibility\"\n value={form.visibility}\n onChange={(v) => setField('visibility', v as CooVisibility)}\n options={visibilityOptions}\n className=\"mt-4\"\n />\n <TextField\n id=\"stylePreferences\"\n label=\"Style preferences\"\n value={form.stylePreferences}\n onChange={(v) => setField('stylePreferences', v)}\n placeholder=\"minimal, tailored, vintage\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n <TextField\n id=\"palette\"\n label=\"Palette\"\n value={form.palette}\n onChange={(v) => setField('palette', v)}\n placeholder=\"neutrals, navy, rust\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n <TextField\n id=\"favouriteBrands\"\n label=\"Favourite brands\"\n value={form.favouriteBrands}\n onChange={(v) => setField('favouriteBrands', v)}\n placeholder=\"Aurora Atelier, …\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n\n {upsert.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {upsert.error instanceof Error ? upsert.error.message : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={upsert.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {upsert.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {hasProfile ? 'Save changes' : 'Create profile'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"StyleProfilePage.js","names":[],"sources":["../../../../src/coolandlovely/pages/profile/StyleProfilePage.tsx"],"sourcesContent":["/**\n * Style Profile Page (self-scoped)\n * @module coolandlovely/pages/profile\n *\n * Views and edits the calling shopper's style profile (`cooMyStyleProfile` +\n * `cooUpsertStyleProfile`) as a two-column bento — preferences, palette\n * swatches, favourite-brand monograms and a sizes table on the left; the\n * shopper's wardrobe (`cooListWardrobeItems`) and recent looks\n * (`cooListLooks` by author) on the right. One profile per workspace per shopper.\n */\n\nimport { useEffect, useState } from 'react';\nimport { Loader2, Save, UserCircle, Pencil, X, Users, Sparkles, Shirt } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { ColourSwatchRow } from '../../components/ColourSwatchRow';\nimport { VisibilityChip } from '../../components/VisibilityChip';\nimport { ActorName } from '../../components/ActorName';\nimport { FollowButton } from '../../components/FollowButton';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useMyStyleProfile,\n useUpsertStyleProfile,\n useMyFollowing,\n useWardrobeItems,\n useLooks,\n} from '../../hooks';\nimport { categoryIcon } from '../../utils/icons';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooVisibility } from '../../types';\n\nconst VISIBILITIES: CooVisibility[] = ['PUBLIC', 'FOLLOWERS', 'PRIVATE'];\n\n/** Common size rows offered in the editor (existing custom keys are preserved). */\nconst SIZE_KEYS = ['Tops', 'Bottoms', 'Dresses', 'Shoes', 'Outerwear'];\n\ninterface FormState {\n displayName: string;\n bio: string;\n visibility: CooVisibility;\n stylePreferences: string;\n palette: string;\n favouriteBrands: string;\n sizes: Record<string, string>;\n}\n\nconst EMPTY: FormState = {\n displayName: '',\n bio: '',\n visibility: 'PRIVATE',\n stylePreferences: '',\n palette: '',\n favouriteBrands: '',\n sizes: {},\n};\n\n/** Monogram from a free-text brand name (real brand logos are unavailable). */\nfunction brandInitials(name: string): string {\n const words = name.trim().split(/\\s+/).filter(Boolean);\n if (words.length === 0) return '?';\n if (words.length === 1) return words[0].slice(0, 2).toUpperCase();\n return (words[0][0] + words[1][0]).toUpperCase();\n}\n\nfunction BentoCard({\n title,\n children,\n className,\n}: {\n title: string;\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <GlassCard treatment=\"plain\" className={cn('p-5', className)}>\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">{title}</h3>\n {children}\n </GlassCard>\n );\n}\n\nexport function StyleProfilePage() {\n const { navigateTo, currentUser } = useCoolAndLovely();\n const query = useMyStyleProfile();\n const upsert = useUpsertStyleProfile();\n const following = useMyFollowing();\n const wardrobe = useWardrobeItems();\n const recentLooks = useLooks({\n authorId: query.data?.ownerId,\n pagination: { limit: 6, offset: 0 },\n });\n\n const [editing, setEditing] = useState(false);\n const [form, setForm] = useState<FormState>(EMPTY);\n\n const profile = query.data;\n const hasProfile = Boolean(profile);\n const followingList = following.data ?? [];\n\n useEffect(() => {\n if (profile) {\n setForm({\n displayName: profile.displayName ?? '',\n bio: profile.bio ?? '',\n visibility: profile.visibility,\n stylePreferences: profile.stylePreferences.join(', '),\n palette: profile.palette.join(', '),\n favouriteBrands: profile.favouriteBrands.join(', '),\n sizes: Object.fromEntries(\n Object.entries(profile.sizes ?? {}).map(([k, v]) => [k, String(v ?? '')])\n ),\n });\n }\n }, [profile]);\n\n // If there is no profile yet, drop straight into edit mode.\n useEffect(() => {\n if (!query.isLoading && !hasProfile) {\n setEditing(true);\n }\n }, [query.isLoading, hasProfile]);\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const setSize = (key: string, value: string) =>\n setForm((f) => ({ ...f, sizes: { ...f.sizes, [key]: value } }));\n\n const visibilityOptions = VISIBILITIES.map((v) => ({ value: v, label: titleCase(v) }));\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const cleanedSizes = Object.fromEntries(\n Object.entries(form.sizes)\n .map(([k, v]) => [k, v.trim()] as const)\n .filter(([, v]) => v.length > 0)\n );\n upsert.mutate(\n {\n displayName: form.displayName.trim() || undefined,\n bio: form.bio.trim() || undefined,\n visibility: form.visibility,\n stylePreferences: parseCsv(form.stylePreferences),\n palette: parseCsv(form.palette),\n favouriteBrands: parseCsv(form.favouriteBrands),\n sizes: cleanedSizes,\n },\n { onSuccess: () => setEditing(false) }\n );\n };\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Style Profile\">\n <LoadingView label=\"Loading your style profile…\" />\n </PageLayout>\n );\n }\n\n if (query.isError) {\n return (\n <PageLayout title=\"Style Profile\">\n <ErrorView onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n // ----- View mode -----\n if (!editing && profile) {\n const avatarUrl = currentUser?.profilePicture || currentUser?.avatar;\n const wardrobeItems = (wardrobe.data ?? []).slice(0, 6);\n const looksList = recentLooks.data?.items ?? [];\n const sizeEntries = Object.entries(profile.sizes ?? {});\n\n return (\n <PageLayout\n title=\"Style Profile\"\n description=\"Your taste, palette and sizing — the spine of personalised discovery\"\n actions={\n <button\n type=\"button\"\n onClick={() => setEditing(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n }\n >\n <div className=\"mx-auto max-w-5xl\">\n {/* Header */}\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center\">\n {avatarUrl ? (\n <img\n src={avatarUrl}\n alt={profile.displayName || 'Your avatar'}\n className=\"h-16 w-16 shrink-0 rounded-full object-cover ring-2 ring-bg-surface\"\n />\n ) : (\n <div className=\"flex h-16 w-16 shrink-0 items-center justify-center rounded-full bg-accent-purple-subtle text-text-primary\">\n <UserCircle className=\"h-8 w-8\" />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary\">\n {profile.displayName || currentUser?.name || 'Your style profile'}\n </h2>\n <VisibilityChip visibility={profile.visibility} />\n </div>\n {profile.bio && (\n <p className=\"mt-1 line-clamp-2 text-sm text-text-secondary\">{profile.bio}</p>\n )}\n <div className=\"mt-2 flex items-center gap-6\">\n <span className=\"text-sm text-text-secondary\">\n <span className=\"font-semibold text-text-primary tabular-nums\">\n {profile.followerCount ?? 0}\n </span>{' '}\n {profile.followerCount === 1 ? 'follower' : 'followers'}\n </span>\n <span className=\"text-sm text-text-secondary\">\n <span className=\"font-semibold text-text-primary tabular-nums\">\n {profile.followingCount ?? 0}\n </span>{' '}\n following\n </span>\n </div>\n </div>\n </div>\n </GlassCard>\n\n {/* Bento */}\n <div className=\"mt-6 grid gap-6 lg:grid-cols-5\">\n {/* Left: taste detail */}\n <div className=\"space-y-6 lg:col-span-3\">\n <BentoCard title=\"Style preferences\">\n {profile.stylePreferences.length ? (\n <div className=\"flex flex-wrap gap-2\">\n {profile.stylePreferences.map((pref, i) => (\n <span\n key={`${pref}-${i}`}\n className={cn(\n 'rounded-full px-3 py-1 text-sm font-medium',\n i === 0\n ? 'bg-accent-purple-subtle text-text-primary ring-1 ring-[var(--color-accent-soft)]'\n : 'bg-bg-sunken text-text-secondary'\n )}\n >\n {pref}\n </span>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n No preferences yet — add a few in Edit.\n </p>\n )}\n </BentoCard>\n\n <BentoCard title=\"Palette\">\n {profile.palette.length ? (\n <ColourSwatchRow colours={profile.palette} max={12} className=\"gap-2.5\" />\n ) : (\n <p className=\"text-sm text-text-secondary\">No palette yet.</p>\n )}\n </BentoCard>\n\n <div className=\"grid gap-6 sm:grid-cols-2\">\n <BentoCard title=\"Favourite brands\">\n {profile.favouriteBrands.length ? (\n <div className=\"grid grid-cols-1 gap-2\">\n {profile.favouriteBrands.map((brand, i) => (\n <div\n key={`${brand}-${i}`}\n className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <span className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-accent-purple-subtle text-xs font-bold text-text-primary\">\n {brandInitials(brand)}\n </span>\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {brand}\n </span>\n </div>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-text-secondary\">No brands yet.</p>\n )}\n </BentoCard>\n\n <BentoCard title=\"Sizes\">\n {sizeEntries.length ? (\n <dl className=\"space-y-0\">\n {sizeEntries.map(([key, value]) => (\n <div\n key={key}\n className=\"flex items-center justify-between border-b border-border-seam/60 py-1.5 last:border-0\"\n >\n <dt className=\"text-sm text-text-secondary\">{titleCase(key)}</dt>\n <dd className=\"text-sm font-medium text-text-primary tabular-nums\">\n {String(value)}\n </dd>\n </div>\n ))}\n </dl>\n ) : (\n <p className=\"text-sm text-text-secondary\">No sizes recorded yet.</p>\n )}\n </BentoCard>\n </div>\n </div>\n\n {/* Right: wardrobe + recent looks */}\n <div className=\"space-y-6 lg:col-span-2\">\n <BentoCard title=\"Her wardrobe\">\n {wardrobe.isLoading ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" /> Loading…\n </div>\n ) : wardrobeItems.length ? (\n <div className=\"grid grid-cols-3 gap-2\">\n {wardrobeItems.map((w) => (\n <button\n key={w.id}\n type=\"button\"\n onClick={() => navigateTo('/wardrobe')}\n aria-label={w.name}\n className=\"group overflow-hidden rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Thumbnail\n icon={categoryIcon(w.category, 18)}\n seed={w.id}\n fileId={w.image?.id}\n alt={w.name}\n className=\"aspect-square w-full rounded-lg\"\n />\n </button>\n ))}\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigateTo('/wardrobe/new')}\n className=\"flex w-full items-center gap-2 rounded-lg border border-dashed border-border-seam px-3 py-3 text-left text-sm text-text-secondary transition-colors hover:text-text-primary\"\n >\n <Shirt className=\"h-4 w-4\" /> Add pieces to your wardrobe\n </button>\n )}\n </BentoCard>\n\n <BentoCard title=\"Recent looks\">\n {recentLooks.isLoading ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" /> Loading…\n </div>\n ) : looksList.length ? (\n <div className=\"-mx-1 flex gap-3 overflow-x-auto px-1 pb-1\">\n {looksList.map((look) => (\n <button\n key={look.id}\n type=\"button\"\n onClick={() => navigateTo(`/looks/${look.id}`)}\n className=\"group w-24 shrink-0 text-left focus-visible:outline-none\"\n >\n <Thumbnail\n icon={<Sparkles className=\"h-5 w-5\" />}\n seed={look.id}\n fileId={look.image?.id}\n alt={look.title}\n className=\"aspect-[3/4] w-full rounded-lg\"\n />\n <p className=\"mt-1 truncate text-xs text-text-secondary\">{look.title}</p>\n </button>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n You have not published any looks yet.\n </p>\n )}\n </BentoCard>\n </div>\n </div>\n\n {/* People you follow */}\n <section className=\"mt-6\">\n <h3 className=\"mb-3 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Users className=\"h-5 w-5\" /> People you follow ({followingList.length})\n </h3>\n {following.isLoading ? (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" /> Loading…\n </div>\n ) : followingList.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n You are not following anyone yet. Follow creators from their looks to see their\n latest in Discover.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-seam rounded-lg border border-border-seam bg-bg-surface\">\n {followingList.map((p) => (\n <li key={p.id} className=\"flex items-center gap-3 px-4 py-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent-purple-subtle text-text-primary\">\n <UserCircle className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {p.displayName || <ActorName id={p.ownerId} />}\n </p>\n {p.bio && <p className=\"truncate text-xs text-text-secondary\">{p.bio}</p>}\n </div>\n <FollowButton followeeId={p.ownerId} size=\"sm\" />\n </li>\n ))}\n </ul>\n )}\n </section>\n </div>\n </PageLayout>\n );\n }\n\n // ----- Edit / create mode -----\n const sizeKeys = Array.from(new Set([...SIZE_KEYS, ...Object.keys(form.sizes)]));\n\n return (\n <PageLayout\n title={hasProfile ? 'Edit style profile' : 'Create your style profile'}\n description=\"This powers your personalised discovery feed\"\n actions={\n hasProfile ? (\n <button\n type=\"button\"\n onClick={() => setEditing(false)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <X className=\"h-4 w-4\" /> Cancel\n </button>\n ) : undefined\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <TextField\n id=\"displayName\"\n label=\"Display name\"\n value={form.displayName}\n onChange={(v) => setField('displayName', v)}\n placeholder=\"How you want to appear\"\n />\n <TextAreaField\n id=\"bio\"\n label=\"Bio\"\n value={form.bio}\n onChange={(v) => setField('bio', v)}\n placeholder=\"A sentence about your style…\"\n rows={3}\n className=\"mt-4\"\n />\n <SelectField\n id=\"visibility\"\n label=\"Visibility\"\n value={form.visibility}\n onChange={(v) => setField('visibility', v as CooVisibility)}\n options={visibilityOptions}\n className=\"mt-4\"\n />\n <TextField\n id=\"stylePreferences\"\n label=\"Style preferences\"\n value={form.stylePreferences}\n onChange={(v) => setField('stylePreferences', v)}\n placeholder=\"minimal, tailored, vintage\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n <TextField\n id=\"palette\"\n label=\"Palette\"\n value={form.palette}\n onChange={(v) => setField('palette', v)}\n placeholder=\"neutrals, navy, rust\"\n hint=\"Comma-separated colour names — shown as swatches\"\n className=\"mt-4\"\n />\n <TextField\n id=\"favouriteBrands\"\n label=\"Favourite brands\"\n value={form.favouriteBrands}\n onChange={(v) => setField('favouriteBrands', v)}\n placeholder=\"Aurora Atelier, …\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n\n <div className=\"mt-4\">\n <span className=\"mb-2 block text-sm font-medium text-text-primary\">Sizes</span>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {sizeKeys.map((key) => (\n <TextField\n key={key}\n id={`size-${key}`}\n label={titleCase(key)}\n value={form.sizes[key] ?? ''}\n onChange={(v) => setSize(key, v)}\n placeholder=\"e.g. M · 26 · 39\"\n />\n ))}\n </div>\n </div>\n\n {upsert.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {upsert.error instanceof Error ? upsert.error.message : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={upsert.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {upsert.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {hasProfile ? 'Save changes' : 'Create profile'}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -4,8 +4,8 @@ import { PageLayout as n } from "../../components/PageLayout.js";
4
4
  import { MetricsCard as r } from "../../components/MetricsCard.js";
5
5
  import { ErrorView as i, LoadingView as a } from "../../components/StateViews.js";
6
6
  import { useBrands as o, useLooks as s, useStyleItems as c } from "../../hooks/useCoolAndLovelyApi.js";
7
- import "../../hooks/index.js";
8
7
  import { titleCase as l } from "../../utils/formatters.js";
8
+ import "../../hooks/index.js";
9
9
  import { useMemo as u } from "react";
10
10
  import { CalendarHeart as d, Heart as f, Palette as p, Shapes as m, Shirt as h, Sparkles as g, Store as _, TrendingUp as v } from "lucide-react";
11
11
  import { jsx as y, jsxs as b } from "react/jsx-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"TrendsPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/trends/TrendsPage.tsx"],"sourcesContent":["/**\n * Trends Page\n * @module coolandlovely/pages/trends\n *\n * Client-side analytics computed over the real catalogue + social lists\n * (cooListBrands / cooListStyleItems / cooListLooks). Honest by construction —\n * it only counts and ranks what is actually on the platform. No fabricated\n * metrics. Header labels it \"Signals from platform activity.\"\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { useMemo } from 'react';\nimport {\n TrendingUp,\n Palette,\n Shapes,\n Sparkles,\n Store,\n Shirt,\n Heart,\n CalendarHeart,\n} from 'lucide-react';\nimport { EmphasisPanel, GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useBrands, useStyleItems, useLooks } from '../../hooks';\nimport { titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\n\ninterface Ranked {\n label: string;\n value: number;\n sublabel?: string;\n}\n\nfunction rank(map: Map<string, number>, top = 8): Ranked[] {\n return [...map.entries()]\n .map(([label, value]) => ({ label, value }))\n .sort((a, b) => b.value - a.value)\n .slice(0, top);\n}\n\ninterface StatBarsProps {\n title: string;\n icon: ReactNode;\n items: Ranked[];\n unit: string;\n emptyLabel: string;\n className?: string;\n}\n\nconst StatBars: FC<StatBarsProps> = ({ title, icon, items, unit, emptyLabel, className }) => {\n const max = items.reduce((m, i) => Math.max(m, i.value), 0) || 1;\n return (\n <GlassCard treatment=\"plain\" className={cn('p-5', className)}>\n <h3 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n {icon} {title}\n </h3>\n {items.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">{emptyLabel}</p>\n ) : (\n <ul className=\"space-y-3\">\n {items.map((item) => (\n <li key={item.label}>\n <div className=\"mb-1 flex items-center justify-between gap-2 text-sm\">\n <span className=\"truncate text-text-primary\">{item.label}</span>\n <span className=\"shrink-0 tabular-nums text-text-secondary\">\n {item.value.toLocaleString()} {unit}\n </span>\n </div>\n <div\n className=\"h-2 w-full overflow-hidden rounded-full bg-bg-sunken\"\n role=\"img\"\n aria-label={`${item.label}: ${item.value} ${unit}`}\n >\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${Math.max(4, Math.round((item.value / max) * 100))}%` }}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n );\n};\n\nexport function TrendsPage() {\n useCoolAndLovely();\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const items = useStyleItems({ pagination: { limit: 100, offset: 0 } });\n const looks = useLooks({ pagination: { limit: 100, offset: 0 } });\n\n const isLoading = brands.isLoading || items.isLoading || looks.isLoading;\n const isError = brands.isError && items.isError && looks.isError;\n\n const analysis = useMemo(() => {\n const brandRows = brands.data?.items ?? [];\n const itemRows = items.data?.items ?? [];\n const lookRows = looks.data?.items ?? [];\n\n const categoryMap = new Map<string, number>();\n const colourMap = new Map<string, number>();\n for (const it of itemRows) {\n categoryMap.set(titleCase(it.category), (categoryMap.get(titleCase(it.category)) ?? 0) + 1);\n for (const c of it.colours) {\n const key = titleCase(c);\n colourMap.set(key, (colourMap.get(key) ?? 0) + 1);\n }\n }\n\n const aestheticMap = new Map<string, number>();\n for (const b of brandRows) {\n for (const a of b.aesthetics) {\n const key = titleCase(a);\n aestheticMap.set(key, (aestheticMap.get(key) ?? 0) + 1);\n }\n }\n\n const occasionEngagement = new Map<string, number>();\n let totalLikes = 0;\n let totalSaves = 0;\n for (const l of lookRows) {\n const eng = l.likeCount + l.saveCount;\n const key = titleCase(l.occasion);\n occasionEngagement.set(key, (occasionEngagement.get(key) ?? 0) + eng);\n totalLikes += l.likeCount;\n totalSaves += l.saveCount;\n }\n\n const topLooks = [...lookRows]\n .sort((a, b) => b.likeCount + b.saveCount - (a.likeCount + a.saveCount))\n .slice(0, 5);\n\n return {\n brandCount: brandRows.length,\n itemCount: itemRows.length,\n lookCount: lookRows.length,\n totalLikes,\n totalSaves,\n topCategories: rank(categoryMap, 8),\n topColours: rank(colourMap, 8),\n topAesthetics: rank(aestheticMap, 8),\n risingOccasions: rank(occasionEngagement, 8),\n topLooks,\n };\n }, [brands.data, items.data, looks.data]);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Trends\">\n <LoadingView label=\"Reading platform signals…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Trends\">\n <ErrorView\n onRetry={() => {\n void brands.refetch();\n void items.refetch();\n void looks.refetch();\n }}\n />\n </PageLayout>\n );\n }\n\n const metrics = [\n { icon: <Store className=\"h-5 w-5\" />, value: analysis.brandCount, label: 'Brands sampled' },\n { icon: <Shirt className=\"h-5 w-5\" />, value: analysis.itemCount, label: 'Items sampled' },\n { icon: <Sparkles className=\"h-5 w-5\" />, value: analysis.lookCount, label: 'Looks sampled' },\n {\n icon: <Heart className=\"h-5 w-5\" />,\n value: analysis.totalLikes + analysis.totalSaves,\n label: 'Total engagement',\n },\n ];\n\n return (\n <PageLayout\n title=\"Trends\"\n description=\"Signals from platform activity\"\n hero\n >\n <PagePurpose className=\"mb-6\">\n These are honest, client-side signals computed from the pieces, brands and looks currently\n on CoolAndLovely — the most-listed categories and colours, the aesthetics brands describe\n themselves with, and which occasions are drawing the most likes and saves. Nothing here is\n fabricated; it is a live read of the catalogue and social activity.\n </PagePurpose>\n\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">At a glance</h2>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n {metrics.map((m) => (\n <MetricsCard\n key={m.label}\n icon={m.icon}\n value={m.value.toLocaleString()}\n label={m.label}\n />\n ))}\n </div>\n </EmphasisPanel>\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <StatBars\n title=\"Top categories\"\n icon={<Shapes className=\"h-5 w-5\" />}\n items={analysis.topCategories}\n unit=\"items\"\n emptyLabel=\"No style items yet.\"\n />\n <StatBars\n title=\"Most-listed colours\"\n icon={<Palette className=\"h-5 w-5\" />}\n items={analysis.topColours}\n unit=\"items\"\n emptyLabel=\"No colours recorded on items yet.\"\n />\n <StatBars\n title=\"Brand aesthetics\"\n icon={<Store className=\"h-5 w-5\" />}\n items={analysis.topAesthetics}\n unit=\"brands\"\n emptyLabel=\"No brand aesthetics recorded yet.\"\n />\n <StatBars\n title=\"Rising occasions\"\n icon={<CalendarHeart className=\"h-5 w-5\" />}\n items={analysis.risingOccasions}\n unit=\"engagement\"\n emptyLabel=\"No looks published yet.\"\n />\n </div>\n\n <section className=\"mt-8\">\n <h2 className=\"mb-3 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <TrendingUp className=\"h-5 w-5\" /> Most-loved looks\n </h2>\n {analysis.topLooks.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No looks published yet.\n </p>\n ) : (\n <div className=\"divide-y divide-border-seam rounded-lg border border-border-seam bg-bg-surface\">\n {analysis.topLooks.map((look, idx) => (\n <div key={look.id} className=\"flex items-center gap-4 px-4 py-3\">\n <span className=\"w-6 shrink-0 text-center text-sm font-semibold tabular-nums text-text-secondary\">\n {idx + 1}\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{look.title}</p>\n <p className=\"text-xs text-text-secondary\">{titleCase(look.occasion)}</p>\n </div>\n <span className=\"flex shrink-0 items-center gap-3 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n <Heart className=\"h-3.5 w-3.5\" /> {look.likeCount}\n </span>\n <span className=\"tabular-nums\">{look.saveCount} saves</span>\n </span>\n </div>\n ))}\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAqCA,SAAS,EAAK,GAA0B,IAAM,GAAa;AACzD,QAAO,CAAC,GAAG,EAAI,SAAS,CAAC,CACtB,KAAK,CAAC,GAAO,QAAY;EAAE;EAAO;EAAO,EAAE,CAC3C,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,CACjC,MAAM,GAAG,EAAI;;AAYlB,IAAM,KAA+B,EAAE,UAAO,SAAM,UAAO,SAAM,eAAY,mBAAgB;CAC3F,IAAM,IAAM,EAAM,QAAQ,GAAG,MAAM,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI;AAC/D,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAW,EAAG,OAAO,EAAU;YAA5D,CACE,kBAAC,MAAD;GAAI,WAAU;aAAd;IACG;IAAK;IAAE;IACL;MACJ,EAAM,WAAW,IAChB,kBAAC,KAAD;GAAG,WAAU;aAA+B;GAAe,CAAA,GAE3D,kBAAC,MAAD;GAAI,WAAU;aACX,EAAM,KAAK,MACV,kBAAC,MAAD,EAAA,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAA8B,EAAK;KAAa,CAAA,EAChE,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,EAAK,MAAM,gBAAgB;MAAC;MAAE;MAC1B;OACH;OACN,kBAAC,OAAD;IACE,WAAU;IACV,MAAK;IACL,cAAY,GAAG,EAAK,MAAM,IAAI,EAAK,MAAM,GAAG;cAE5C,kBAAC,OAAD;KACE,WAAU;KACV,OAAO,EAAE,OAAO,GAAG,KAAK,IAAI,GAAG,KAAK,MAAO,EAAK,QAAQ,IAAO,IAAI,CAAC,CAAC,IAAI;KACzE,CAAA;IACE,CAAA,CACH,EAAA,EAjBI,EAAK,MAiBT,CACL;GACC,CAAA,CAEG;;;AAIhB,SAAgB,IAAa;AAC3B,IAAkB;CAClB,IAAM,IAAS,EAAU,EAAE,YAAY;EAAE,OAAO;EAAK,QAAQ;EAAG,EAAE,CAAC,EAC7D,IAAQ,EAAc,EAAE,YAAY;EAAE,OAAO;EAAK,QAAQ;EAAG,EAAE,CAAC,EAChE,IAAQ,EAAS,EAAE,YAAY;EAAE,OAAO;EAAK,QAAQ;EAAG,EAAE,CAAC,EAE3D,IAAY,EAAO,aAAa,EAAM,aAAa,EAAM,WACzD,IAAU,EAAO,WAAW,EAAM,WAAW,EAAM,SAEnD,IAAW,QAAc;EAC7B,IAAM,IAAY,EAAO,MAAM,SAAS,EAAE,EACpC,IAAW,EAAM,MAAM,SAAS,EAAE,EAClC,IAAW,EAAM,MAAM,SAAS,EAAE,EAElC,oBAAc,IAAI,KAAqB,EACvC,oBAAY,IAAI,KAAqB;AAC3C,OAAK,IAAM,KAAM,GAAU;AACzB,KAAY,IAAI,EAAU,EAAG,SAAS,GAAG,EAAY,IAAI,EAAU,EAAG,SAAS,CAAC,IAAI,KAAK,EAAE;AAC3F,QAAK,IAAM,KAAK,EAAG,SAAS;IAC1B,IAAM,IAAM,EAAU,EAAE;AACxB,MAAU,IAAI,IAAM,EAAU,IAAI,EAAI,IAAI,KAAK,EAAE;;;EAIrD,IAAM,oBAAe,IAAI,KAAqB;AAC9C,OAAK,IAAM,KAAK,EACd,MAAK,IAAM,KAAK,EAAE,YAAY;GAC5B,IAAM,IAAM,EAAU,EAAE;AACxB,KAAa,IAAI,IAAM,EAAa,IAAI,EAAI,IAAI,KAAK,EAAE;;EAI3D,IAAM,oBAAqB,IAAI,KAAqB,EAChD,IAAa,GACb,IAAa;AACjB,OAAK,IAAM,KAAK,GAAU;GACxB,IAAM,IAAM,EAAE,YAAY,EAAE,WACtB,IAAM,EAAU,EAAE,SAAS;AAGjC,GAFA,EAAmB,IAAI,IAAM,EAAmB,IAAI,EAAI,IAAI,KAAK,EAAI,EACrE,KAAc,EAAE,WAChB,KAAc,EAAE;;EAGlB,IAAM,IAAW,CAAC,GAAG,EAAS,CAC3B,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,CACvE,MAAM,GAAG,EAAE;AAEd,SAAO;GACL,YAAY,EAAU;GACtB,WAAW,EAAS;GACpB,WAAW,EAAS;GACpB;GACA;GACA,eAAe,EAAK,GAAa,EAAE;GACnC,YAAY,EAAK,GAAW,EAAE;GAC9B,eAAe,EAAK,GAAc,EAAE;GACpC,iBAAiB,EAAK,GAAoB,EAAE;GAC5C;GACD;IACA;EAAC,EAAO;EAAM,EAAM;EAAM,EAAM;EAAK,CAAC;AAEzC,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAa,OAAM,6BAA8B,CAAA;EACtC,CAAA;AAIjB,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EACE,eAAe;AAGR,GAFA,EAAO,SAAS,EAChB,EAAM,SAAS,EACf,EAAM,SAAS;KAEtB,CAAA;EACS,CAAA;CAIjB,IAAM,IAAU;EACd;GAAE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;GAAE,OAAO,EAAS;GAAY,OAAO;GAAkB;EAC5F;GAAE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;GAAE,OAAO,EAAS;GAAW,OAAO;GAAiB;EAC1F;GAAE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;GAAE,OAAO,EAAS;GAAW,OAAO;GAAiB;EAC7F;GACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;GACnC,OAAO,EAAS,aAAa,EAAS;GACtC,OAAO;GACR;EACF;AAED,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,MAAA;YAHF;GAKE,kBAAC,GAAD;IAAa,WAAU;cAAO;IAKhB,CAAA;GAEd,kBAAC,GAAD;IAAe,WAAU;cAAzB,CACE,kBAAC,MAAD;KAAI,WAAU;eAA+C;KAAgB,CAAA,EAC7E,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAQ,KAAK,MACZ,kBAAC,GAAD;MAEE,MAAM,EAAE;MACR,OAAO,EAAE,MAAM,gBAAgB;MAC/B,OAAO,EAAE;MACT,EAJK,EAAE,MAIP,CACF;KACE,CAAA,CACQ;;GAEhB,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,OAAM;MACN,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;MACpC,OAAO,EAAS;MAChB,MAAK;MACL,YAAW;MACX,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,MAAM,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;MACrC,OAAO,EAAS;MAChB,MAAK;MACL,YAAW;MACX,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;MACnC,OAAO,EAAS;MAChB,MAAK;MACL,YAAW;MACX,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,MAAM,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;MAC3C,OAAO,EAAS;MAChB,MAAK;MACL,YAAW;MACX,CAAA;KACE;;GAEN,kBAAC,WAAD;IAAS,WAAU;cAAnB,CACE,kBAAC,MAAD;KAAI,WAAU;eAAd,CACE,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,EAAA,oBAC/B;QACJ,EAAS,SAAS,WAAW,IAC5B,kBAAC,KAAD;KAAG,WAAU;eAAqF;KAE9F,CAAA,GAEJ,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAS,SAAS,KAAK,GAAM,MAC5B,kBAAC,OAAD;MAAmB,WAAU;gBAA7B;OACE,kBAAC,QAAD;QAAM,WAAU;kBACb,IAAM;QACF,CAAA;OACP,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAkD,EAAK;SAAU,CAAA,EAC9E,kBAAC,KAAD;SAAG,WAAU;mBAA+B,EAAU,EAAK,SAAS;SAAK,CAAA,CACrE;;OACN,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAhB;UACE,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA;;UAAE,EAAK;UACnC;YACP,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CAAgC,EAAK,WAAU,SAAa;WACvD;;OACH;QAdI,EAAK,GAcT,CACN;KACE,CAAA,CAEA;;GACC"}
1
+ {"version":3,"file":"TrendsPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/trends/TrendsPage.tsx"],"sourcesContent":["/**\n * Trends Page\n * @module coolandlovely/pages/trends\n *\n * Client-side analytics computed over the real catalogue + social lists\n * (cooListBrands / cooListStyleItems / cooListLooks). Honest by construction —\n * it only counts and ranks what is actually on the platform. No fabricated\n * metrics. Header labels it \"Signals from platform activity.\"\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { useMemo } from 'react';\nimport {\n TrendingUp,\n Palette,\n Shapes,\n Sparkles,\n Store,\n Shirt,\n Heart,\n CalendarHeart,\n} from 'lucide-react';\nimport { EmphasisPanel, GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useBrands, useStyleItems, useLooks } from '../../hooks';\nimport { titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\n\ninterface Ranked {\n label: string;\n value: number;\n sublabel?: string;\n}\n\nfunction rank(map: Map<string, number>, top = 8): Ranked[] {\n return [...map.entries()]\n .map(([label, value]) => ({ label, value }))\n .sort((a, b) => b.value - a.value)\n .slice(0, top);\n}\n\ninterface StatBarsProps {\n title: string;\n icon: ReactNode;\n items: Ranked[];\n unit: string;\n emptyLabel: string;\n className?: string;\n}\n\nconst StatBars: FC<StatBarsProps> = ({ title, icon, items, unit, emptyLabel, className }) => {\n const max = items.reduce((m, i) => Math.max(m, i.value), 0) || 1;\n return (\n <GlassCard treatment=\"plain\" className={cn('p-5', className)}>\n <h3 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n {icon} {title}\n </h3>\n {items.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">{emptyLabel}</p>\n ) : (\n <ul className=\"space-y-3\">\n {items.map((item) => (\n <li key={item.label}>\n <div className=\"mb-1 flex items-center justify-between gap-2 text-sm\">\n <span className=\"truncate text-text-primary\">{item.label}</span>\n <span className=\"shrink-0 tabular-nums text-text-secondary\">\n {item.value.toLocaleString()} {unit}\n </span>\n </div>\n <div\n className=\"h-2 w-full overflow-hidden rounded-full bg-bg-sunken\"\n role=\"img\"\n aria-label={`${item.label}: ${item.value} ${unit}`}\n >\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${Math.max(4, Math.round((item.value / max) * 100))}%` }}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n );\n};\n\nexport function TrendsPage() {\n useCoolAndLovely();\n const brands = useBrands({ pagination: { limit: 100, offset: 0 } });\n const items = useStyleItems({ pagination: { limit: 100, offset: 0 } });\n const looks = useLooks({ pagination: { limit: 100, offset: 0 } });\n\n const isLoading = brands.isLoading || items.isLoading || looks.isLoading;\n const isError = brands.isError && items.isError && looks.isError;\n\n const analysis = useMemo(() => {\n const brandRows = brands.data?.items ?? [];\n const itemRows = items.data?.items ?? [];\n const lookRows = looks.data?.items ?? [];\n\n const categoryMap = new Map<string, number>();\n const colourMap = new Map<string, number>();\n for (const it of itemRows) {\n categoryMap.set(titleCase(it.category), (categoryMap.get(titleCase(it.category)) ?? 0) + 1);\n for (const c of it.colours) {\n const key = titleCase(c);\n colourMap.set(key, (colourMap.get(key) ?? 0) + 1);\n }\n }\n\n const aestheticMap = new Map<string, number>();\n for (const b of brandRows) {\n for (const a of b.aesthetics) {\n const key = titleCase(a);\n aestheticMap.set(key, (aestheticMap.get(key) ?? 0) + 1);\n }\n }\n\n const occasionEngagement = new Map<string, number>();\n let totalLikes = 0;\n let totalSaves = 0;\n for (const l of lookRows) {\n const eng = l.likeCount + l.saveCount;\n const key = titleCase(l.occasion);\n occasionEngagement.set(key, (occasionEngagement.get(key) ?? 0) + eng);\n totalLikes += l.likeCount;\n totalSaves += l.saveCount;\n }\n\n const topLooks = [...lookRows]\n .sort((a, b) => b.likeCount + b.saveCount - (a.likeCount + a.saveCount))\n .slice(0, 5);\n\n return {\n brandCount: brandRows.length,\n itemCount: itemRows.length,\n lookCount: lookRows.length,\n totalLikes,\n totalSaves,\n topCategories: rank(categoryMap, 8),\n topColours: rank(colourMap, 8),\n topAesthetics: rank(aestheticMap, 8),\n risingOccasions: rank(occasionEngagement, 8),\n topLooks,\n };\n }, [brands.data, items.data, looks.data]);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Trends\">\n <LoadingView label=\"Reading platform signals…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Trends\">\n <ErrorView\n onRetry={() => {\n void brands.refetch();\n void items.refetch();\n void looks.refetch();\n }}\n />\n </PageLayout>\n );\n }\n\n const metrics = [\n { icon: <Store className=\"h-5 w-5\" />, value: analysis.brandCount, label: 'Brands sampled' },\n { icon: <Shirt className=\"h-5 w-5\" />, value: analysis.itemCount, label: 'Items sampled' },\n { icon: <Sparkles className=\"h-5 w-5\" />, value: analysis.lookCount, label: 'Looks sampled' },\n {\n icon: <Heart className=\"h-5 w-5\" />,\n value: analysis.totalLikes + analysis.totalSaves,\n label: 'Total engagement',\n },\n ];\n\n return (\n <PageLayout title=\"Trends\" description=\"Signals from platform activity\" hero>\n <PagePurpose className=\"mb-6\">\n These are honest, client-side signals computed from the pieces, brands and looks currently\n on CoolAndLovely — the most-listed categories and colours, the aesthetics brands describe\n themselves with, and which occasions are drawing the most likes and saves. Nothing here is\n fabricated; it is a live read of the catalogue and social activity.\n </PagePurpose>\n\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">At a glance</h2>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n {metrics.map((m) => (\n <MetricsCard\n key={m.label}\n icon={m.icon}\n value={m.value.toLocaleString()}\n label={m.label}\n />\n ))}\n </div>\n </EmphasisPanel>\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <StatBars\n title=\"Top categories\"\n icon={<Shapes className=\"h-5 w-5\" />}\n items={analysis.topCategories}\n unit=\"items\"\n emptyLabel=\"No style items yet.\"\n />\n <StatBars\n title=\"Most-listed colours\"\n icon={<Palette className=\"h-5 w-5\" />}\n items={analysis.topColours}\n unit=\"items\"\n emptyLabel=\"No colours recorded on items yet.\"\n />\n <StatBars\n title=\"Brand aesthetics\"\n icon={<Store className=\"h-5 w-5\" />}\n items={analysis.topAesthetics}\n unit=\"brands\"\n emptyLabel=\"No brand aesthetics recorded yet.\"\n />\n <StatBars\n title=\"Rising occasions\"\n icon={<CalendarHeart className=\"h-5 w-5\" />}\n items={analysis.risingOccasions}\n unit=\"engagement\"\n emptyLabel=\"No looks published yet.\"\n />\n </div>\n\n <section className=\"mt-8\">\n <h2 className=\"mb-3 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <TrendingUp className=\"h-5 w-5\" /> Most-loved looks\n </h2>\n {analysis.topLooks.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n No looks published yet.\n </p>\n ) : (\n <div className=\"divide-y divide-border-seam rounded-lg border border-border-seam bg-bg-surface\">\n {analysis.topLooks.map((look, idx) => (\n <div key={look.id} className=\"flex items-center gap-4 px-4 py-3\">\n <span className=\"w-6 shrink-0 text-center text-sm font-semibold tabular-nums text-text-secondary\">\n {idx + 1}\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{look.title}</p>\n <p className=\"text-xs text-text-secondary\">{titleCase(look.occasion)}</p>\n </div>\n <span className=\"flex shrink-0 items-center gap-3 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n <Heart className=\"h-3.5 w-3.5\" /> {look.likeCount}\n </span>\n <span className=\"tabular-nums\">{look.saveCount} saves</span>\n </span>\n </div>\n ))}\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,23 +1,36 @@
1
1
  import { useCoolAndLovely as e } from "../../context/CoolAndLovelyContext.js";
2
- import { PageLayout as t } from "../../components/PageLayout.js";
3
- import { COO_FILE_FOLDERS as n } from "../../hooks/useFiles.js";
4
- import { useCreateWardrobeItem as r } from "../../hooks/useCoolAndLovelyApi.js";
2
+ import { cn as t } from "../../utils/cn.js";
3
+ import { PageLayout as n } from "../../components/PageLayout.js";
4
+ import { COO_FILE_FOLDERS as r } from "../../hooks/useFiles.js";
5
+ import { useCreateWardrobeItem as i, useStyleItems as a } from "../../hooks/useCoolAndLovelyApi.js";
6
+ import { parseCsv as o, titleCase as s } from "../../utils/formatters.js";
5
7
  import "../../hooks/index.js";
6
- import { parseCsv as i, titleCase as a } from "../../utils/formatters.js";
7
- import { STYLE_CATEGORIES as o } from "../../utils/icons.js";
8
- import { SelectField as s, TextAreaField as c, TextField as l } from "../../components/Field.js";
9
- import { ImageUploadField as u } from "../../components/ImageUploadField.js";
10
- import { useState as d } from "react";
11
- import { ArrowLeft as f, Heart as p, Loader2 as m, Save as h } from "lucide-react";
12
- import { jsx as g, jsxs as _ } from "react/jsx-runtime";
13
- import { GlassCard as v } from "@burdenoff/fe-libs/ui";
8
+ import { STYLE_CATEGORIES as c } from "../../utils/icons.js";
9
+ import { SelectField as l, TextAreaField as u, TextField as d } from "../../components/Field.js";
10
+ import { ImageUploadField as f } from "../../components/ImageUploadField.js";
11
+ import { useMemo as p, useState as m } from "react";
12
+ import { ArrowLeft as h, Heart as g, Loader2 as _, Save as v } from "lucide-react";
13
+ import { jsx as y, jsxs as b } from "react/jsx-runtime";
14
+ import { GlassCard as x } from "@burdenoff/fe-libs/ui";
14
15
  //#region src/coolandlovely/pages/wardrobe/WardrobeFormPage.tsx
15
- var y = [
16
- "OWNED",
17
- "WISHLIST",
18
- "PURCHASED_ON_PLATFORM",
19
- "GIFTED"
20
- ], b = {
16
+ var S = [
17
+ {
18
+ value: "OWNED",
19
+ label: "Owned"
20
+ },
21
+ {
22
+ value: "WISHLIST",
23
+ label: "Wishlist"
24
+ },
25
+ {
26
+ value: "PURCHASED_ON_PLATFORM",
27
+ label: "Purchased"
28
+ },
29
+ {
30
+ value: "GIFTED",
31
+ label: "Gifted"
32
+ }
33
+ ], C = {
21
34
  name: "",
22
35
  category: "OTHER",
23
36
  source: "OWNED",
@@ -26,152 +39,175 @@ var y = [
26
39
  size: "",
27
40
  notes: "",
28
41
  tags: "",
42
+ styleItemId: "",
29
43
  imageFileId: ""
30
44
  };
31
- function x() {
32
- let { navigateTo: x } = e(), S = r(), [C, w] = d(b), [T, E] = d(), D = (e, t) => w((n) => ({
45
+ function w() {
46
+ let { navigateTo: w } = e(), T = i(), E = a({ pagination: {
47
+ limit: 100,
48
+ offset: 0
49
+ } }), [D, O] = m(C), [k, A] = m(), j = (e, t) => O((n) => ({
33
50
  ...n,
34
51
  [e]: t
35
- })), O = o.map((e) => ({
52
+ })), M = c.map((e) => ({
36
53
  value: e,
37
- label: a(e)
38
- })), k = y.map((e) => ({
39
- value: e,
40
- label: a(e)
41
- }));
42
- return /* @__PURE__ */ g(t, {
54
+ label: s(e)
55
+ })), N = p(() => (E.data?.items ?? []).filter((e) => e.status !== "RETIRED"), [E.data]), P = p(() => N.map((e) => ({
56
+ value: e.id,
57
+ label: `${e.name}${e.brand?.name ? ` · ${e.brand.name}` : ""}`
58
+ })), [N]);
59
+ return /* @__PURE__ */ y(n, {
43
60
  title: "Add a wardrobe piece",
44
61
  description: "Catalogue something you own or want",
45
- actions: /* @__PURE__ */ _("button", {
62
+ actions: /* @__PURE__ */ b("button", {
46
63
  type: "button",
47
- onClick: () => x("/wardrobe"),
64
+ onClick: () => w("/wardrobe"),
48
65
  className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
49
- children: [/* @__PURE__ */ g(f, { className: "h-4 w-4" }), " Cancel"]
66
+ children: [/* @__PURE__ */ y(h, { className: "h-4 w-4" }), " Cancel"]
50
67
  }),
51
- children: /* @__PURE__ */ g("form", {
68
+ children: /* @__PURE__ */ y("form", {
52
69
  onSubmit: (e) => {
53
- if (e.preventDefault(), !C.name.trim()) {
54
- E("Name is required");
70
+ if (e.preventDefault(), !D.name.trim()) {
71
+ A("Name is required");
55
72
  return;
56
73
  }
57
- E(void 0), S.mutate({
58
- name: C.name.trim(),
59
- category: C.category,
60
- source: C.source,
61
- brandName: C.brandName.trim() || void 0,
62
- colour: C.colour.trim() || void 0,
63
- size: C.size.trim() || void 0,
64
- notes: C.notes.trim() || void 0,
65
- tags: i(C.tags),
66
- imageFileId: C.imageFileId || void 0
67
- }, { onSuccess: () => x("/wardrobe") });
74
+ A(void 0), T.mutate({
75
+ name: D.name.trim(),
76
+ category: D.category,
77
+ source: D.source,
78
+ brandName: D.brandName.trim() || void 0,
79
+ colour: D.colour.trim() || void 0,
80
+ size: D.size.trim() || void 0,
81
+ notes: D.notes.trim() || void 0,
82
+ tags: o(D.tags),
83
+ styleItemId: D.styleItemId || void 0,
84
+ imageFileId: D.imageFileId || void 0
85
+ }, { onSuccess: () => w("/wardrobe") });
68
86
  },
69
87
  className: "mx-auto max-w-2xl",
70
- children: /* @__PURE__ */ _(v, {
88
+ children: /* @__PURE__ */ b(x, {
71
89
  treatment: "plain",
72
90
  className: "p-6",
73
91
  children: [
74
- /* @__PURE__ */ _("div", {
92
+ /* @__PURE__ */ b("div", {
75
93
  className: "mb-6 flex items-center gap-3",
76
- children: [/* @__PURE__ */ g("div", {
94
+ children: [/* @__PURE__ */ y("div", {
77
95
  className: "flex h-11 w-11 items-center justify-center rounded-lg bg-status-error-bg-subtle text-status-error-text",
78
- children: /* @__PURE__ */ g(p, { className: "h-5 w-5" })
79
- }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("h2", {
96
+ children: /* @__PURE__ */ y(g, { className: "h-5 w-5" })
97
+ }), /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("h2", {
80
98
  className: "text-base font-semibold text-text-primary",
81
99
  children: "Piece details"
82
- }), /* @__PURE__ */ g("p", {
100
+ }), /* @__PURE__ */ y("p", {
83
101
  className: "text-xs text-text-secondary",
84
102
  children: "Only you can see your wardrobe."
85
103
  })] })]
86
104
  }),
87
- /* @__PURE__ */ g(l, {
105
+ /* @__PURE__ */ y(d, {
88
106
  id: "name",
89
107
  label: "Name",
90
- value: C.name,
91
- onChange: (e) => D("name", e),
108
+ value: D.name,
109
+ onChange: (e) => j("name", e),
92
110
  placeholder: "Vintage denim jacket",
93
111
  required: !0,
94
- error: T
112
+ error: k
113
+ }),
114
+ /* @__PURE__ */ y(l, {
115
+ id: "category",
116
+ label: "Category",
117
+ value: D.category,
118
+ onChange: (e) => j("category", e),
119
+ options: M,
120
+ className: "mt-4"
95
121
  }),
96
- /* @__PURE__ */ _("div", {
97
- className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2",
98
- children: [/* @__PURE__ */ g(s, {
99
- id: "category",
100
- label: "Category",
101
- value: C.category,
102
- onChange: (e) => D("category", e),
103
- options: O
104
- }), /* @__PURE__ */ g(s, {
105
- id: "source",
106
- label: "Source",
107
- value: C.source,
108
- onChange: (e) => D("source", e),
109
- options: k
122
+ /* @__PURE__ */ b("div", {
123
+ className: "mt-4",
124
+ children: [/* @__PURE__ */ y("span", {
125
+ className: "mb-1.5 block text-sm font-medium text-text-primary",
126
+ children: "Source"
127
+ }), /* @__PURE__ */ y("div", {
128
+ className: "flex flex-wrap gap-2",
129
+ children: S.map((e) => /* @__PURE__ */ y("button", {
130
+ type: "button",
131
+ onClick: () => j("source", e.value),
132
+ "aria-pressed": D.source === e.value,
133
+ className: t("rounded-full px-3 py-1.5 text-sm font-medium transition-colors", D.source === e.value ? "bg-action-primary-bg text-action-primary-text" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
134
+ children: e.label
135
+ }, e.value))
110
136
  })]
111
137
  }),
112
- /* @__PURE__ */ _("div", {
138
+ /* @__PURE__ */ b("div", {
113
139
  className: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3",
114
140
  children: [
115
- /* @__PURE__ */ g(l, {
141
+ /* @__PURE__ */ y(d, {
116
142
  id: "brandName",
117
143
  label: "Brand",
118
- value: C.brandName,
119
- onChange: (e) => D("brandName", e),
144
+ value: D.brandName,
145
+ onChange: (e) => j("brandName", e),
120
146
  placeholder: "Levi's"
121
147
  }),
122
- /* @__PURE__ */ g(l, {
148
+ /* @__PURE__ */ y(d, {
123
149
  id: "colour",
124
150
  label: "Colour",
125
- value: C.colour,
126
- onChange: (e) => D("colour", e),
151
+ value: D.colour,
152
+ onChange: (e) => j("colour", e),
127
153
  placeholder: "Indigo"
128
154
  }),
129
- /* @__PURE__ */ g(l, {
155
+ /* @__PURE__ */ y(d, {
130
156
  id: "size",
131
157
  label: "Size",
132
- value: C.size,
133
- onChange: (e) => D("size", e),
158
+ value: D.size,
159
+ onChange: (e) => j("size", e),
134
160
  placeholder: "M"
135
161
  })
136
162
  ]
137
163
  }),
138
- /* @__PURE__ */ g(c, {
164
+ /* @__PURE__ */ y(l, {
165
+ id: "styleItemId",
166
+ label: "Link to a style item (optional)",
167
+ value: D.styleItemId,
168
+ onChange: (e) => j("styleItemId", e),
169
+ options: P,
170
+ placeholder: E.isLoading ? "Loading catalogue…" : "None — describe it yourself above",
171
+ hint: "Connect this piece to a catalogue item to keep its brand and imagery in sync.",
172
+ className: "mt-4"
173
+ }),
174
+ /* @__PURE__ */ y(u, {
139
175
  id: "notes",
140
176
  label: "Notes",
141
- value: C.notes,
142
- onChange: (e) => D("notes", e),
177
+ value: D.notes,
178
+ onChange: (e) => j("notes", e),
143
179
  placeholder: "Where you got it, how you wear it…",
144
180
  rows: 3,
145
181
  className: "mt-4"
146
182
  }),
147
- /* @__PURE__ */ g(l, {
183
+ /* @__PURE__ */ y(d, {
148
184
  id: "tags",
149
185
  label: "Tags",
150
- value: C.tags,
151
- onChange: (e) => D("tags", e),
186
+ value: D.tags,
187
+ onChange: (e) => j("tags", e),
152
188
  placeholder: "denim, layering",
153
189
  hint: "Comma-separated",
154
190
  className: "mt-4"
155
191
  }),
156
- /* @__PURE__ */ g(u, {
192
+ /* @__PURE__ */ y(f, {
157
193
  label: "Photo",
158
- value: C.imageFileId,
159
- onChange: (e) => D("imageFileId", e),
160
- folder: n.wardrobe,
194
+ value: D.imageFileId,
195
+ onChange: (e) => j("imageFileId", e),
196
+ folder: r.wardrobe,
161
197
  hint: "Your photo of the piece (private to you)",
162
198
  className: "mt-4"
163
199
  }),
164
- S.isError && /* @__PURE__ */ g("p", {
200
+ T.isError && /* @__PURE__ */ y("p", {
165
201
  className: "mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
166
- children: S.error instanceof Error ? S.error.message : "Something went wrong"
202
+ children: T.error instanceof Error ? T.error.message : "Something went wrong"
167
203
  }),
168
- /* @__PURE__ */ g("div", {
204
+ /* @__PURE__ */ y("div", {
169
205
  className: "mt-6 flex justify-end gap-2",
170
- children: /* @__PURE__ */ _("button", {
206
+ children: /* @__PURE__ */ b("button", {
171
207
  type: "submit",
172
- disabled: S.isPending,
208
+ disabled: T.isPending,
173
209
  className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50",
174
- children: [S.isPending ? /* @__PURE__ */ g(m, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ g(h, { className: "h-4 w-4" }), "Add to wardrobe"]
210
+ children: [T.isPending ? /* @__PURE__ */ y(_, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ y(v, { className: "h-4 w-4" }), "Add to wardrobe"]
175
211
  })
176
212
  })
177
213
  ]
@@ -180,6 +216,6 @@ function x() {
180
216
  });
181
217
  }
182
218
  //#endregion
183
- export { x as WardrobeFormPage };
219
+ export { w as WardrobeFormPage };
184
220
 
185
221
  //# sourceMappingURL=WardrobeFormPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WardrobeFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/wardrobe/WardrobeFormPage.tsx"],"sourcesContent":["/**\n * Wardrobe Item Create Page (self-scoped)\n * @module coolandlovely/pages/wardrobe\n *\n * Drives `cooCreateWardrobeItem`. Belongs to the calling shopper's profile.\n */\n\nimport { useState } from 'react';\nimport { ArrowLeft, Loader2, Save, Heart } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCreateWardrobeItem } from '../../hooks';\nimport { COO_FILE_FOLDERS } from '../../hooks/useFiles';\nimport { STYLE_CATEGORIES } from '../../utils/icons';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport type { CooStyleCategory, CooWardrobeSource } from '../../types';\n\nconst SOURCES: CooWardrobeSource[] = ['OWNED', 'WISHLIST', 'PURCHASED_ON_PLATFORM', 'GIFTED'];\n\ninterface FormState {\n name: string;\n category: CooStyleCategory;\n source: CooWardrobeSource;\n brandName: string;\n colour: string;\n size: string;\n notes: string;\n tags: string;\n imageFileId: string;\n}\n\nconst EMPTY: FormState = {\n name: '',\n category: 'OTHER',\n source: 'OWNED',\n brandName: '',\n colour: '',\n size: '',\n notes: '',\n tags: '',\n imageFileId: '',\n};\n\nexport function WardrobeFormPage() {\n const { navigateTo } = useCoolAndLovely();\n const createItem = useCreateWardrobeItem();\n\n const [form, setForm] = useState<FormState>(EMPTY);\n const [nameError, setNameError] = useState<string | undefined>();\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const categoryOptions = STYLE_CATEGORIES.map((c) => ({ value: c, label: titleCase(c) }));\n const sourceOptions = SOURCES.map((s) => ({ value: s, label: titleCase(s) }));\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!form.name.trim()) {\n setNameError('Name is required');\n return;\n }\n setNameError(undefined);\n\n createItem.mutate(\n {\n name: form.name.trim(),\n category: form.category,\n source: form.source,\n brandName: form.brandName.trim() || undefined,\n colour: form.colour.trim() || undefined,\n size: form.size.trim() || undefined,\n notes: form.notes.trim() || undefined,\n tags: parseCsv(form.tags),\n imageFileId: form.imageFileId || undefined,\n },\n { onSuccess: () => navigateTo('/wardrobe') }\n );\n };\n\n return (\n <PageLayout\n title=\"Add a wardrobe piece\"\n description=\"Catalogue something you own or want\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/wardrobe')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-11 w-11 items-center justify-center rounded-lg bg-status-error-bg-subtle text-status-error-text\">\n <Heart className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Piece details</h2>\n <p className=\"text-xs text-text-secondary\">Only you can see your wardrobe.</p>\n </div>\n </div>\n\n <TextField\n id=\"name\"\n label=\"Name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Vintage denim jacket\"\n required\n error={nameError}\n />\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <SelectField\n id=\"category\"\n label=\"Category\"\n value={form.category}\n onChange={(v) => setField('category', v as CooStyleCategory)}\n options={categoryOptions}\n />\n <SelectField\n id=\"source\"\n label=\"Source\"\n value={form.source}\n onChange={(v) => setField('source', v as CooWardrobeSource)}\n options={sourceOptions}\n />\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <TextField\n id=\"brandName\"\n label=\"Brand\"\n value={form.brandName}\n onChange={(v) => setField('brandName', v)}\n placeholder=\"Levi's\"\n />\n <TextField\n id=\"colour\"\n label=\"Colour\"\n value={form.colour}\n onChange={(v) => setField('colour', v)}\n placeholder=\"Indigo\"\n />\n <TextField\n id=\"size\"\n label=\"Size\"\n value={form.size}\n onChange={(v) => setField('size', v)}\n placeholder=\"M\"\n />\n </div>\n\n <TextAreaField\n id=\"notes\"\n label=\"Notes\"\n value={form.notes}\n onChange={(v) => setField('notes', v)}\n placeholder=\"Where you got it, how you wear it…\"\n rows={3}\n className=\"mt-4\"\n />\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"denim, layering\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n\n <ImageUploadField\n label=\"Photo\"\n value={form.imageFileId}\n onChange={(id) => setField('imageFileId', id)}\n folder={COO_FILE_FOLDERS.wardrobe}\n hint=\"Your photo of the piece (private to you)\"\n className=\"mt-4\"\n />\n\n {createItem.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {createItem.error instanceof Error\n ? createItem.error.message\n : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={createItem.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {createItem.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n Add to wardrobe\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"WardrobeFormPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/wardrobe/WardrobeFormPage.tsx"],"sourcesContent":["/**\n * Wardrobe Item Create Page (self-scoped)\n * @module coolandlovely/pages/wardrobe\n *\n * Drives `cooCreateWardrobeItem`. Belongs to the calling shopper's profile. A\n * piece can optionally be linked to a catalogue style item (`styleItemId`), and\n * its source is chosen with a segmented control.\n */\n\nimport { useMemo, useState } from 'react';\nimport { ArrowLeft, Loader2, Save, Heart } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { TextField, TextAreaField, SelectField } from '../../components/Field';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useCreateWardrobeItem, useStyleItems } from '../../hooks';\nimport { COO_FILE_FOLDERS } from '../../hooks/useFiles';\nimport { STYLE_CATEGORIES } from '../../utils/icons';\nimport { parseCsv, titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooStyleCategory, CooWardrobeSource } from '../../types';\n\nconst SOURCES: Array<{ value: CooWardrobeSource; label: string }> = [\n { value: 'OWNED', label: 'Owned' },\n { value: 'WISHLIST', label: 'Wishlist' },\n { value: 'PURCHASED_ON_PLATFORM', label: 'Purchased' },\n { value: 'GIFTED', label: 'Gifted' },\n];\n\ninterface FormState {\n name: string;\n category: CooStyleCategory;\n source: CooWardrobeSource;\n brandName: string;\n colour: string;\n size: string;\n notes: string;\n tags: string;\n styleItemId: string;\n imageFileId: string;\n}\n\nconst EMPTY: FormState = {\n name: '',\n category: 'OTHER',\n source: 'OWNED',\n brandName: '',\n colour: '',\n size: '',\n notes: '',\n tags: '',\n styleItemId: '',\n imageFileId: '',\n};\n\nexport function WardrobeFormPage() {\n const { navigateTo } = useCoolAndLovely();\n const createItem = useCreateWardrobeItem();\n const styleItems = useStyleItems({ pagination: { limit: 100, offset: 0 } });\n\n const [form, setForm] = useState<FormState>(EMPTY);\n const [nameError, setNameError] = useState<string | undefined>();\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) =>\n setForm((f) => ({ ...f, [key]: value }));\n\n const categoryOptions = STYLE_CATEGORIES.map((c) => ({ value: c, label: titleCase(c) }));\n\n const usableStyleItems = useMemo(\n () => (styleItems.data?.items ?? []).filter((i) => i.status !== 'RETIRED'),\n [styleItems.data]\n );\n\n const styleItemOptions = useMemo(\n () =>\n usableStyleItems.map((i) => ({\n value: i.id,\n label: `${i.name}${i.brand?.name ? ` · ${i.brand.name}` : ''}`,\n })),\n [usableStyleItems]\n );\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!form.name.trim()) {\n setNameError('Name is required');\n return;\n }\n setNameError(undefined);\n\n createItem.mutate(\n {\n name: form.name.trim(),\n category: form.category,\n source: form.source,\n brandName: form.brandName.trim() || undefined,\n colour: form.colour.trim() || undefined,\n size: form.size.trim() || undefined,\n notes: form.notes.trim() || undefined,\n tags: parseCsv(form.tags),\n styleItemId: form.styleItemId || undefined,\n imageFileId: form.imageFileId || undefined,\n },\n { onSuccess: () => navigateTo('/wardrobe') }\n );\n };\n\n return (\n <PageLayout\n title=\"Add a wardrobe piece\"\n description=\"Catalogue something you own or want\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/wardrobe')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-2xl\">\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-11 w-11 items-center justify-center rounded-lg bg-status-error-bg-subtle text-status-error-text\">\n <Heart className=\"h-5 w-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Piece details</h2>\n <p className=\"text-xs text-text-secondary\">Only you can see your wardrobe.</p>\n </div>\n </div>\n\n <TextField\n id=\"name\"\n label=\"Name\"\n value={form.name}\n onChange={(v) => setField('name', v)}\n placeholder=\"Vintage denim jacket\"\n required\n error={nameError}\n />\n\n <SelectField\n id=\"category\"\n label=\"Category\"\n value={form.category}\n onChange={(v) => setField('category', v as CooStyleCategory)}\n options={categoryOptions}\n className=\"mt-4\"\n />\n\n {/* Segmented source control */}\n <div className=\"mt-4\">\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Source</span>\n <div className=\"flex flex-wrap gap-2\">\n {SOURCES.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => setField('source', opt.value)}\n aria-pressed={form.source === opt.value}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n form.source === opt.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n </div>\n\n <div className=\"mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <TextField\n id=\"brandName\"\n label=\"Brand\"\n value={form.brandName}\n onChange={(v) => setField('brandName', v)}\n placeholder=\"Levi's\"\n />\n <TextField\n id=\"colour\"\n label=\"Colour\"\n value={form.colour}\n onChange={(v) => setField('colour', v)}\n placeholder=\"Indigo\"\n />\n <TextField\n id=\"size\"\n label=\"Size\"\n value={form.size}\n onChange={(v) => setField('size', v)}\n placeholder=\"M\"\n />\n </div>\n\n <SelectField\n id=\"styleItemId\"\n label=\"Link to a style item (optional)\"\n value={form.styleItemId}\n onChange={(v) => setField('styleItemId', v)}\n options={styleItemOptions}\n placeholder={\n styleItems.isLoading ? 'Loading catalogue…' : 'None — describe it yourself above'\n }\n hint=\"Connect this piece to a catalogue item to keep its brand and imagery in sync.\"\n className=\"mt-4\"\n />\n\n <TextAreaField\n id=\"notes\"\n label=\"Notes\"\n value={form.notes}\n onChange={(v) => setField('notes', v)}\n placeholder=\"Where you got it, how you wear it…\"\n rows={3}\n className=\"mt-4\"\n />\n\n <TextField\n id=\"tags\"\n label=\"Tags\"\n value={form.tags}\n onChange={(v) => setField('tags', v)}\n placeholder=\"denim, layering\"\n hint=\"Comma-separated\"\n className=\"mt-4\"\n />\n\n <ImageUploadField\n label=\"Photo\"\n value={form.imageFileId}\n onChange={(id) => setField('imageFileId', id)}\n folder={COO_FILE_FOLDERS.wardrobe}\n hint=\"Your photo of the piece (private to you)\"\n className=\"mt-4\"\n />\n\n {createItem.isError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {createItem.error instanceof Error\n ? createItem.error.message\n : 'Something went wrong'}\n </p>\n )}\n\n <div className=\"mt-6 flex justify-end gap-2\">\n <button\n type=\"submit\"\n disabled={createItem.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {createItem.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n Add to wardrobe\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}