@burdenoff/microfe-coolandlovely 2026.827.1 → 2026.829.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.
Files changed (71) hide show
  1. package/dist/coolandlovely/components/ActivityTrail.js +7 -7
  2. package/dist/coolandlovely/components/Comments.js +4 -4
  3. package/dist/coolandlovely/components/FeedbackWidget.d.ts.map +1 -1
  4. package/dist/coolandlovely/components/FeedbackWidget.js +72 -70
  5. package/dist/coolandlovely/components/FeedbackWidget.js.map +1 -1
  6. package/dist/coolandlovely/components/LookCard.js +24 -24
  7. package/dist/coolandlovely/components/StateViews.d.ts.map +1 -1
  8. package/dist/coolandlovely/components/StateViews.js +35 -26
  9. package/dist/coolandlovely/components/StateViews.js.map +1 -1
  10. package/dist/coolandlovely/components/StatusBadge.d.ts.map +1 -1
  11. package/dist/coolandlovely/components/StatusBadge.js +45 -15
  12. package/dist/coolandlovely/components/StatusBadge.js.map +1 -1
  13. package/dist/coolandlovely/components/VisibilityChip.d.ts.map +1 -1
  14. package/dist/coolandlovely/components/VisibilityChip.js +22 -17
  15. package/dist/coolandlovely/components/VisibilityChip.js.map +1 -1
  16. package/dist/coolandlovely/hooks/index.d.ts +1 -0
  17. package/dist/coolandlovely/hooks/index.d.ts.map +1 -1
  18. package/dist/coolandlovely/hooks/index.js +2 -1
  19. package/dist/coolandlovely/hooks/useTr.d.ts +2 -0
  20. package/dist/coolandlovely/hooks/useTr.d.ts.map +1 -0
  21. package/dist/coolandlovely/hooks/useTr.js +14 -0
  22. package/dist/coolandlovely/hooks/useTr.js.map +1 -0
  23. package/dist/coolandlovely/pages/HomePage.d.ts.map +1 -1
  24. package/dist/coolandlovely/pages/HomePage.js +172 -132
  25. package/dist/coolandlovely/pages/HomePage.js.map +1 -1
  26. package/dist/coolandlovely/pages/admin/AdminBrandsPage.js +10 -10
  27. package/dist/coolandlovely/pages/admin/AdminCollaborationsPage.js +16 -16
  28. package/dist/coolandlovely/pages/admin/AdminFeaturedItemsPage.js +17 -17
  29. package/dist/coolandlovely/pages/brands/BrandDetailPage.js +22 -22
  30. package/dist/coolandlovely/pages/brands/BrandFormPage.js +9 -9
  31. package/dist/coolandlovely/pages/brands/BrandsListPage.d.ts.map +1 -1
  32. package/dist/coolandlovely/pages/brands/BrandsListPage.js +80 -76
  33. package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -1
  34. package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js +21 -21
  35. package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js +7 -7
  36. package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.d.ts.map +1 -1
  37. package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js +82 -81
  38. package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js.map +1 -1
  39. package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +19 -19
  40. package/dist/coolandlovely/pages/collections/CollectionFormPage.js +9 -9
  41. package/dist/coolandlovely/pages/collections/CollectionsListPage.d.ts.map +1 -1
  42. package/dist/coolandlovely/pages/collections/CollectionsListPage.js +54 -55
  43. package/dist/coolandlovely/pages/collections/CollectionsListPage.js.map +1 -1
  44. package/dist/coolandlovely/pages/discover/DiscoverPage.d.ts.map +1 -1
  45. package/dist/coolandlovely/pages/discover/DiscoverPage.js +322 -315
  46. package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -1
  47. package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +21 -21
  48. package/dist/coolandlovely/pages/items/StyleItemFormPage.js +10 -10
  49. package/dist/coolandlovely/pages/items/StyleItemsListPage.d.ts.map +1 -1
  50. package/dist/coolandlovely/pages/items/StyleItemsListPage.js +105 -95
  51. package/dist/coolandlovely/pages/items/StyleItemsListPage.js.map +1 -1
  52. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +20 -20
  53. package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
  54. package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.d.ts.map +1 -1
  55. package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js +88 -79
  56. package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js.map +1 -1
  57. package/dist/coolandlovely/pages/looks/LookDetailPage.js +18 -18
  58. package/dist/coolandlovely/pages/looks/LookFormPage.js +6 -6
  59. package/dist/coolandlovely/pages/looks/LookFormPage.js.map +1 -1
  60. package/dist/coolandlovely/pages/looks/LooksListPage.d.ts.map +1 -1
  61. package/dist/coolandlovely/pages/looks/LooksListPage.js +94 -94
  62. package/dist/coolandlovely/pages/looks/LooksListPage.js.map +1 -1
  63. package/dist/coolandlovely/pages/profile/StyleProfilePage.d.ts.map +1 -1
  64. package/dist/coolandlovely/pages/profile/StyleProfilePage.js +226 -199
  65. package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
  66. package/dist/coolandlovely/pages/trends/TrendsPage.js +8 -8
  67. package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js +5 -5
  68. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.d.ts.map +1 -1
  69. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +71 -69
  70. package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
  71. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { cn as e } from "../utils/cn.js";
2
- import { formatDate as t, formatRelativeTime as n, titleCase as r } from "../utils/formatters.js";
3
- import { useEntityActivity as i } from "../hooks/useActivity.js";
2
+ import { useEntityActivity as t } from "../hooks/useActivity.js";
3
+ import { formatDate as n, formatRelativeTime as r, titleCase as i } from "../utils/formatters.js";
4
4
  import { ActorName as a } from "./ActorName.js";
5
5
  import { History as o, Loader2 as s, RotateCw as c } from "lucide-react";
6
6
  import { jsx as l, jsxs as u } from "react/jsx-runtime";
@@ -9,11 +9,11 @@ import { GlassCard as d } from "@burdenoff/fe-libs/ui";
9
9
  function f(e) {
10
10
  let t = e.includes(".") ? e.slice(e.lastIndexOf(".") + 1) : e;
11
11
  if (!/^[a-z0-9_]+$/.test(t)) return e;
12
- let n = r(t);
12
+ let n = i(t);
13
13
  return n.charAt(0) + n.slice(1).toLowerCase();
14
14
  }
15
- var p = ({ resourceType: r, resourceId: p, title: m = "Audit trail", limit: h = 20, className: g }) => {
16
- let _ = i(r, p, h), v = _.data ?? [];
15
+ var p = ({ resourceType: i, resourceId: p, title: m = "Audit trail", limit: h = 20, className: g }) => {
16
+ let _ = t(i, p, h), v = _.data ?? [];
17
17
  return /* @__PURE__ */ u(d, {
18
18
  treatment: "plain",
19
19
  className: e("p-5", g),
@@ -53,9 +53,9 @@ var p = ({ resourceType: r, resourceId: p, title: m = "Audit trail", limit: h =
53
53
  })]
54
54
  }), /* @__PURE__ */ l("time", {
55
55
  dateTime: e.timestamp,
56
- title: t(e.timestamp),
56
+ title: n(e.timestamp),
57
57
  className: "shrink-0 whitespace-nowrap text-xs text-text-secondary",
58
- children: n(e.timestamp)
58
+ children: r(e.timestamp)
59
59
  })]
60
60
  }, e.id))
61
61
  }) : /* @__PURE__ */ u("p", {
@@ -1,6 +1,6 @@
1
1
  import { cn as e } from "../utils/cn.js";
2
- import { formatRelativeTime as t } from "../utils/formatters.js";
3
- import { useAddComment as n, useEntityComments as r } from "../hooks/useComments.js";
2
+ import { useAddComment as t, useEntityComments as n } from "../hooks/useComments.js";
3
+ import { formatRelativeTime as r } from "../utils/formatters.js";
4
4
  import { ActorName as i } from "./ActorName.js";
5
5
  import { useState as a } from "react";
6
6
  import { Loader2 as o, MessageSquare as s, Send as c } from "lucide-react";
@@ -8,7 +8,7 @@ import { jsx as l, jsxs as u } from "react/jsx-runtime";
8
8
  import { GlassCard as d } from "@burdenoff/fe-libs/ui";
9
9
  //#region src/coolandlovely/components/Comments.tsx
10
10
  var f = ({ entityType: f, entityId: p, title: m = "Reviews & feedback", className: h }) => {
11
- let g = r(f, p), _ = n(), [v, y] = a(""), b = g.data?.comments ?? [], x = g.data?.total ?? b.length;
11
+ let g = n(f, p), _ = t(), [v, y] = a(""), b = g.data?.comments ?? [], x = g.data?.total ?? b.length;
12
12
  return /* @__PURE__ */ u("section", {
13
13
  className: e("mt-6", h),
14
14
  children: [
@@ -91,7 +91,7 @@ var f = ({ entityType: f, entityId: p, title: m = "Reviews & feedback", classNam
91
91
  className: "truncate text-sm font-medium text-text-primary"
92
92
  }), /* @__PURE__ */ u("span", {
93
93
  className: "shrink-0 text-xs text-text-secondary",
94
- children: [t(e.createdAt), e.edited ? " · edited" : ""]
94
+ children: [r(e.createdAt), e.edited ? " · edited" : ""]
95
95
  })]
96
96
  }), /* @__PURE__ */ l("p", {
97
97
  className: "mt-1 whitespace-pre-wrap break-words text-sm text-text-secondary",
@@ -1 +1 @@
1
- {"version":3,"file":"FeedbackWidget.d.ts","sourceRoot":"","sources":["../../../src/coolandlovely/components/FeedbackWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AA6ChC,eAAO,MAAM,cAAc,EAAE,EAwJ5B,CAAC"}
1
+ {"version":3,"file":"FeedbackWidget.d.ts","sourceRoot":"","sources":["../../../src/coolandlovely/components/FeedbackWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AA8ChC,eAAO,MAAM,cAAc,EAAE,EA6K5B,CAAC"}
@@ -1,127 +1,129 @@
1
1
  import { cn as e } from "../utils/cn.js";
2
- import { TextAreaField as t } from "./Field.js";
3
- import { useCallback as n, useEffect as r, useState as i } from "react";
4
- import { MessageSquarePlus as a, Send as o, X as s } from "lucide-react";
5
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
- import { GlassCard as u } from "@burdenoff/fe-libs/ui";
2
+ import { useTr as t } from "../hooks/useTr.js";
3
+ import "../hooks/index.js";
4
+ import { TextAreaField as n } from "./Field.js";
5
+ import { useCallback as r, useEffect as i, useState as a } from "react";
6
+ import { MessageSquarePlus as o, Send as s, X as c } from "lucide-react";
7
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
8
+ import { GlassCard as d } from "@burdenoff/fe-libs/ui";
7
9
  //#region src/coolandlovely/components/FeedbackWidget.tsx
8
- var d = "coolandlovely-feedback-submitted-at", f = 10080 * 60 * 1e3;
9
- function p() {
10
+ var f = "coolandlovely-feedback-submitted-at", p = 10080 * 60 * 1e3;
11
+ function m() {
10
12
  if (typeof window > "u") return !1;
11
- let e = window.localStorage.getItem(d);
13
+ let e = window.localStorage.getItem(f);
12
14
  if (!e) return !1;
13
15
  try {
14
16
  let t = parseInt(e, 10);
15
- return Date.now() - t < f;
17
+ return Date.now() - t < p;
16
18
  } catch {
17
19
  return !1;
18
20
  }
19
21
  }
20
- function m() {
21
- typeof window > "u" || window.localStorage.setItem(d, String(Date.now()));
22
+ function h() {
23
+ typeof window > "u" || window.localStorage.setItem(f, String(Date.now()));
22
24
  }
23
- function h(e) {
25
+ function g(e) {
24
26
  typeof window > "u" || window.dispatchEvent(new CustomEvent("coolandlovely:feedback_submitted", {
25
27
  detail: e,
26
28
  bubbles: !0
27
29
  }));
28
30
  }
29
- var g = () => {
30
- let [d, f] = i(!1), [g, _] = i(null), [v, y] = i(""), [b, x] = i(!1), [S, C] = i(!1);
31
- r(() => {
32
- C(p());
31
+ var _ = () => {
32
+ let f = t(), [p, _] = a(!1), [v, y] = a(null), [b, x] = a(""), [S, C] = a(!1), [w, T] = a(!1);
33
+ i(() => {
34
+ T(m());
33
35
  }, []);
34
- let w = n(() => {
35
- f((e) => !e);
36
- }, []), T = n(() => {
37
- f(!1);
38
- }, []), E = n(() => {
39
- g !== null && (h({
40
- score: g,
41
- comment: v.trim(),
36
+ let E = r(() => {
37
+ _((e) => !e);
38
+ }, []), D = r(() => {
39
+ _(!1);
40
+ }, []), O = r(() => {
41
+ v !== null && (g({
42
+ score: v,
43
+ comment: b.trim(),
42
44
  path: typeof window < "u" ? window.location.pathname : "",
43
45
  submittedAt: (/* @__PURE__ */ new Date()).toISOString()
44
- }), m(), x(!0), C(!0), window.setTimeout(() => {
45
- f(!1), _(null), y(""), x(!1);
46
+ }), h(), C(!0), T(!0), window.setTimeout(() => {
47
+ _(!1), y(null), x(""), C(!1);
46
48
  }, 2500));
47
- }, [g, v]);
48
- return S && !d ? null : /* @__PURE__ */ l("div", {
49
+ }, [v, b]);
50
+ return w && !p ? null : /* @__PURE__ */ u("div", {
49
51
  className: "fixed bottom-4 right-4 z-50 flex flex-col items-end gap-3",
50
- children: [d && /* @__PURE__ */ l(u, {
52
+ children: [p && /* @__PURE__ */ u(d, {
51
53
  className: "w-[min(92vw,360px)] overflow-hidden p-0 shadow-xl",
52
- children: [/* @__PURE__ */ l("div", {
54
+ children: [/* @__PURE__ */ u("div", {
53
55
  className: "flex items-center justify-between border-b border-border-seam px-4 py-3",
54
- children: [/* @__PURE__ */ c("h2", {
56
+ children: [/* @__PURE__ */ l("h2", {
55
57
  className: "text-sm font-semibold text-text-primary",
56
- children: "How is CoolAndLovely working for you?"
57
- }), /* @__PURE__ */ c("button", {
58
+ children: f("coolandlovely.feedback.title", "How is CoolAndLovely working for you?")
59
+ }), /* @__PURE__ */ l("button", {
58
60
  type: "button",
59
- onClick: T,
60
- "aria-label": "Close feedback",
61
+ onClick: D,
62
+ "aria-label": f("coolandlovely.feedback.close", "Close feedback"),
61
63
  className: "rounded-md p-1 text-text-secondary transition-colors hover:bg-bg-surface hover:text-text-primary",
62
- children: /* @__PURE__ */ c(s, {
64
+ children: /* @__PURE__ */ l(c, {
63
65
  className: "h-4 w-4",
64
66
  "aria-hidden": "true"
65
67
  })
66
68
  })]
67
- }), /* @__PURE__ */ c("div", {
69
+ }), /* @__PURE__ */ l("div", {
68
70
  className: "p-4",
69
- children: b ? /* @__PURE__ */ l("div", {
71
+ children: S ? /* @__PURE__ */ u("div", {
70
72
  className: "py-6 text-center",
71
- children: [/* @__PURE__ */ c("p", {
73
+ children: [/* @__PURE__ */ l("p", {
72
74
  className: "text-sm font-medium text-status-success-text",
73
- children: "Thank you for your feedback!"
74
- }), /* @__PURE__ */ c("p", {
75
+ children: f("coolandlovely.feedback.thankYou", "Thank you for your feedback!")
76
+ }), /* @__PURE__ */ l("p", {
75
77
  className: "mt-1 text-xs text-text-secondary",
76
- children: "Your input helps us improve CoolAndLovely."
78
+ children: f("coolandlovely.feedback.helpText", "Your input helps us improve CoolAndLovely.")
77
79
  })]
78
- }) : /* @__PURE__ */ l("div", {
80
+ }) : /* @__PURE__ */ u("div", {
79
81
  className: "space-y-4",
80
82
  children: [
81
- /* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("p", {
83
+ /* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("p", {
82
84
  className: "mb-2 text-xs font-medium text-text-secondary",
83
- children: "Rate your experience (0 = not likely, 10 = very likely)"
84
- }), /* @__PURE__ */ c("div", {
85
+ children: f("coolandlovely.feedback.ratePrompt", "Rate your experience (0 = not likely, 10 = very likely)")
86
+ }), /* @__PURE__ */ l("div", {
85
87
  className: "flex justify-between gap-1",
86
88
  role: "group",
87
- "aria-label": "NPS score",
88
- children: Array.from({ length: 11 }, (e, t) => t).map((t) => /* @__PURE__ */ c("button", {
89
+ "aria-label": f("coolandlovely.feedback.npsLabel", "NPS score"),
90
+ children: Array.from({ length: 11 }, (e, t) => t).map((t) => /* @__PURE__ */ l("button", {
89
91
  type: "button",
90
- onClick: () => _(t),
91
- "aria-pressed": g === t,
92
- className: e("flex h-8 w-7 items-center justify-center rounded-md text-xs font-medium transition-colors", g === t ? "bg-action-primary-bg text-action-primary-text" : "border border-border-seam bg-bg-surface text-text-secondary hover:border-action-primary-bg hover:text-text-primary"),
92
+ onClick: () => y(t),
93
+ "aria-pressed": v === t,
94
+ className: e("flex h-8 w-7 items-center justify-center rounded-md text-xs font-medium transition-colors", v === t ? "bg-action-primary-bg text-action-primary-text" : "border border-border-seam bg-bg-surface text-text-secondary hover:border-action-primary-bg hover:text-text-primary"),
93
95
  children: t
94
96
  }, t))
95
97
  })] }),
96
- /* @__PURE__ */ c(t, {
98
+ /* @__PURE__ */ l(n, {
97
99
  id: "feedback-comment",
98
- label: "What could be better?",
99
- value: v,
100
- onChange: y,
101
- placeholder: "Tell us what’s working or what we should improve…",
100
+ label: f("coolandlovely.feedback.commentLabel", "What could be better?"),
101
+ value: b,
102
+ onChange: x,
103
+ placeholder: f("coolandlovely.feedback.commentPlaceholder", "Tell us what’s working or what we should improve…"),
102
104
  rows: 3,
103
- hint: "Optional — your comment is stored with your score and current page."
105
+ hint: f("coolandlovely.feedback.commentHint", "Optional — your comment is stored with your score and current page.")
104
106
  }),
105
- /* @__PURE__ */ l("button", {
107
+ /* @__PURE__ */ u("button", {
106
108
  type: "button",
107
- onClick: E,
108
- disabled: g === null,
109
- className: e("inline-flex w-full items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors", g === null ? "cursor-not-allowed bg-bg-surface text-text-secondary" : "bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90"),
110
- children: [/* @__PURE__ */ c(o, {
109
+ onClick: O,
110
+ disabled: v === null,
111
+ className: e("inline-flex w-full items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors", v === null ? "cursor-not-allowed bg-bg-surface text-text-secondary" : "bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90"),
112
+ children: [/* @__PURE__ */ l(s, {
111
113
  className: "h-4 w-4",
112
114
  "aria-hidden": "true"
113
- }), "Submit feedback"]
115
+ }), f("coolandlovely.feedback.submit", "Submit feedback")]
114
116
  })
115
117
  ]
116
118
  })
117
119
  })]
118
- }), /* @__PURE__ */ c("button", {
120
+ }), /* @__PURE__ */ l("button", {
119
121
  type: "button",
120
- onClick: w,
121
- "aria-label": d ? "Close feedback" : "Give feedback",
122
- "aria-expanded": d,
123
- className: e("flex h-12 w-12 items-center justify-center rounded-full shadow-lg transition-transform hover:scale-105 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", d ? "bg-bg-surface text-text-primary" : "bg-action-primary-bg text-action-primary-text"),
124
- children: c(d ? s : a, {
122
+ onClick: E,
123
+ "aria-label": p ? f("coolandlovely.feedback.close", "Close feedback") : f("coolandlovely.feedback.open", "Give feedback"),
124
+ "aria-expanded": p,
125
+ className: e("flex h-12 w-12 items-center justify-center rounded-full shadow-lg transition-transform hover:scale-105 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", p ? "bg-bg-surface text-text-primary" : "bg-action-primary-bg text-action-primary-text"),
126
+ children: l(p ? c : o, {
125
127
  className: "h-5 w-5",
126
128
  "aria-hidden": "true"
127
129
  })
@@ -129,6 +131,6 @@ var g = () => {
129
131
  });
130
132
  };
131
133
  //#endregion
132
- export { g as FeedbackWidget };
134
+ export { _ as FeedbackWidget };
133
135
 
134
136
  //# sourceMappingURL=FeedbackWidget.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"FeedbackWidget.js","names":[],"sources":["../../../src/coolandlovely/components/FeedbackWidget.tsx"],"sourcesContent":["/**\n * Lightweight NPS / feedback widget\n * @module coolandlovely/components\n *\n * Floating feedback capture mounted across all CoolAndLovely surfaces.\n * Submissions are stored locally and emitted as analytics events so the\n * product can gather signals without blocking on a backend integration.\n */\n\nimport type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport { MessageSquarePlus, X, Send } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { TextAreaField } from './Field';\nimport { cn } from '../utils/cn';\n\nconst STORAGE_KEY = 'coolandlovely-feedback-submitted-at';\nconst COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000; // one week\n\ninterface FeedbackSubmission {\n score: number;\n comment: string;\n path: string;\n submittedAt: string;\n}\n\nfunction hasRecentSubmission(): boolean {\n if (typeof window === 'undefined') return false;\n const raw = window.localStorage.getItem(STORAGE_KEY);\n if (!raw) return false;\n try {\n const ts = parseInt(raw, 10);\n return Date.now() - ts < COOLDOWN_MS;\n } catch {\n return false;\n }\n}\n\nfunction recordSubmission() {\n if (typeof window === 'undefined') return;\n window.localStorage.setItem(STORAGE_KEY, String(Date.now()));\n}\n\nfunction emitFeedbackEvent(submission: FeedbackSubmission) {\n // Emit a structured event that the host shell's analytics pipeline can pick up.\n if (typeof window === 'undefined') return;\n window.dispatchEvent(\n new CustomEvent('coolandlovely:feedback_submitted', {\n detail: submission,\n bubbles: true,\n })\n );\n}\n\nexport const FeedbackWidget: FC = () => {\n const [isOpen, setIsOpen] = useState(false);\n const [score, setScore] = useState<number | null>(null);\n const [comment, setComment] = useState('');\n const [submitted, setSubmitted] = useState(false);\n const [recentlySubmitted, setRecentlySubmitted] = useState(false);\n\n useEffect(() => {\n setRecentlySubmitted(hasRecentSubmission());\n }, []);\n\n const handleToggle = useCallback(() => {\n setIsOpen((open) => !open);\n }, []);\n\n const handleClose = useCallback(() => {\n setIsOpen(false);\n }, []);\n\n const handleSubmit = useCallback(() => {\n if (score === null) return;\n\n const submission: FeedbackSubmission = {\n score,\n comment: comment.trim(),\n path: typeof window !== 'undefined' ? window.location.pathname : '',\n submittedAt: new Date().toISOString(),\n };\n\n emitFeedbackEvent(submission);\n recordSubmission();\n setSubmitted(true);\n setRecentlySubmitted(true);\n\n // Auto-close after a short delay so the user sees the thank-you state.\n window.setTimeout(() => {\n setIsOpen(false);\n setScore(null);\n setComment('');\n setSubmitted(false);\n }, 2500);\n }, [score, comment]);\n\n if (recentlySubmitted && !isOpen) {\n // Do not show the launcher at all for a week after submission.\n return null;\n }\n\n return (\n <div className=\"fixed bottom-4 right-4 z-50 flex flex-col items-end gap-3\">\n {isOpen && (\n <GlassCard className=\"w-[min(92vw,360px)] overflow-hidden p-0 shadow-xl\">\n <div className=\"flex items-center justify-between border-b border-border-seam px-4 py-3\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n How is CoolAndLovely working for you?\n </h2>\n <button\n type=\"button\"\n onClick={handleClose}\n aria-label=\"Close feedback\"\n className=\"rounded-md p-1 text-text-secondary transition-colors hover:bg-bg-surface hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <div className=\"p-4\">\n {submitted ? (\n <div className=\"py-6 text-center\">\n <p className=\"text-sm font-medium text-status-success-text\">\n Thank you for your feedback!\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Your input helps us improve CoolAndLovely.\n </p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n <div>\n <p className=\"mb-2 text-xs font-medium text-text-secondary\">\n Rate your experience (0 = not likely, 10 = very likely)\n </p>\n <div className=\"flex justify-between gap-1\" role=\"group\" aria-label=\"NPS score\">\n {Array.from({ length: 11 }, (_, i) => i).map((value) => (\n <button\n key={value}\n type=\"button\"\n onClick={() => setScore(value)}\n aria-pressed={score === value}\n className={cn(\n 'flex h-8 w-7 items-center justify-center rounded-md text-xs font-medium transition-colors',\n score === value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'border border-border-seam bg-bg-surface text-text-secondary hover:border-action-primary-bg hover:text-text-primary'\n )}\n >\n {value}\n </button>\n ))}\n </div>\n </div>\n\n <TextAreaField\n id=\"feedback-comment\"\n label=\"What could be better?\"\n value={comment}\n onChange={setComment}\n placeholder=\"Tell us what’s working or what we should improve…\"\n rows={3}\n hint=\"Optional — your comment is stored with your score and current page.\"\n />\n\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={score === null}\n className={cn(\n 'inline-flex w-full items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors',\n score === null\n ? 'cursor-not-allowed bg-bg-surface text-text-secondary'\n : 'bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90'\n )}\n >\n <Send className=\"h-4 w-4\" aria-hidden=\"true\" />\n Submit feedback\n </button>\n </div>\n )}\n </div>\n </GlassCard>\n )}\n\n <button\n type=\"button\"\n onClick={handleToggle}\n aria-label={isOpen ? 'Close feedback' : 'Give feedback'}\n aria-expanded={isOpen}\n className={cn(\n 'flex h-12 w-12 items-center justify-center rounded-full shadow-lg transition-transform hover:scale-105 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n isOpen\n ? 'bg-bg-surface text-text-primary'\n : 'bg-action-primary-bg text-action-primary-text'\n )}\n >\n {isOpen ? (\n <X className=\"h-5 w-5\" aria-hidden=\"true\" />\n ) : (\n <MessageSquarePlus className=\"h-5 w-5\" aria-hidden=\"true\" />\n )}\n </button>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"FeedbackWidget.js","names":[],"sources":["../../../src/coolandlovely/components/FeedbackWidget.tsx"],"sourcesContent":["/**\n * Lightweight NPS / feedback widget\n * @module coolandlovely/components\n *\n * Floating feedback capture mounted across all CoolAndLovely surfaces.\n * Submissions are stored locally and emitted as analytics events so the\n * product can gather signals without blocking on a backend integration.\n */\n\nimport type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport { MessageSquarePlus, X, Send } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { TextAreaField } from './Field';\nimport { cn } from '../utils/cn';\nimport { useTr } from '../hooks';\n\nconst STORAGE_KEY = 'coolandlovely-feedback-submitted-at';\nconst COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000; // one week\n\ninterface FeedbackSubmission {\n score: number;\n comment: string;\n path: string;\n submittedAt: string;\n}\n\nfunction hasRecentSubmission(): boolean {\n if (typeof window === 'undefined') return false;\n const raw = window.localStorage.getItem(STORAGE_KEY);\n if (!raw) return false;\n try {\n const ts = parseInt(raw, 10);\n return Date.now() - ts < COOLDOWN_MS;\n } catch {\n return false;\n }\n}\n\nfunction recordSubmission() {\n if (typeof window === 'undefined') return;\n window.localStorage.setItem(STORAGE_KEY, String(Date.now()));\n}\n\nfunction emitFeedbackEvent(submission: FeedbackSubmission) {\n // Emit a structured event that the host shell's analytics pipeline can pick up.\n if (typeof window === 'undefined') return;\n window.dispatchEvent(\n new CustomEvent('coolandlovely:feedback_submitted', {\n detail: submission,\n bubbles: true,\n })\n );\n}\n\nexport const FeedbackWidget: FC = () => {\n const tr = useTr();\n const [isOpen, setIsOpen] = useState(false);\n const [score, setScore] = useState<number | null>(null);\n const [comment, setComment] = useState('');\n const [submitted, setSubmitted] = useState(false);\n const [recentlySubmitted, setRecentlySubmitted] = useState(false);\n\n useEffect(() => {\n setRecentlySubmitted(hasRecentSubmission());\n }, []);\n\n const handleToggle = useCallback(() => {\n setIsOpen((open) => !open);\n }, []);\n\n const handleClose = useCallback(() => {\n setIsOpen(false);\n }, []);\n\n const handleSubmit = useCallback(() => {\n if (score === null) return;\n\n const submission: FeedbackSubmission = {\n score,\n comment: comment.trim(),\n path: typeof window !== 'undefined' ? window.location.pathname : '',\n submittedAt: new Date().toISOString(),\n };\n\n emitFeedbackEvent(submission);\n recordSubmission();\n setSubmitted(true);\n setRecentlySubmitted(true);\n\n // Auto-close after a short delay so the user sees the thank-you state.\n window.setTimeout(() => {\n setIsOpen(false);\n setScore(null);\n setComment('');\n setSubmitted(false);\n }, 2500);\n }, [score, comment]);\n\n if (recentlySubmitted && !isOpen) {\n // Do not show the launcher at all for a week after submission.\n return null;\n }\n\n return (\n <div className=\"fixed bottom-4 right-4 z-50 flex flex-col items-end gap-3\">\n {isOpen && (\n <GlassCard className=\"w-[min(92vw,360px)] overflow-hidden p-0 shadow-xl\">\n <div className=\"flex items-center justify-between border-b border-border-seam px-4 py-3\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n {tr('coolandlovely.feedback.title', 'How is CoolAndLovely working for you?')}\n </h2>\n <button\n type=\"button\"\n onClick={handleClose}\n aria-label={tr('coolandlovely.feedback.close', 'Close feedback')}\n className=\"rounded-md p-1 text-text-secondary transition-colors hover:bg-bg-surface hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <div className=\"p-4\">\n {submitted ? (\n <div className=\"py-6 text-center\">\n <p className=\"text-sm font-medium text-status-success-text\">\n {tr('coolandlovely.feedback.thankYou', 'Thank you for your feedback!')}\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {tr(\n 'coolandlovely.feedback.helpText',\n 'Your input helps us improve CoolAndLovely.'\n )}\n </p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n <div>\n <p className=\"mb-2 text-xs font-medium text-text-secondary\">\n {tr(\n 'coolandlovely.feedback.ratePrompt',\n 'Rate your experience (0 = not likely, 10 = very likely)'\n )}\n </p>\n <div\n className=\"flex justify-between gap-1\"\n role=\"group\"\n aria-label={tr('coolandlovely.feedback.npsLabel', 'NPS score')}\n >\n {Array.from({ length: 11 }, (_, i) => i).map((value) => (\n <button\n key={value}\n type=\"button\"\n onClick={() => setScore(value)}\n aria-pressed={score === value}\n className={cn(\n 'flex h-8 w-7 items-center justify-center rounded-md text-xs font-medium transition-colors',\n score === value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'border border-border-seam bg-bg-surface text-text-secondary hover:border-action-primary-bg hover:text-text-primary'\n )}\n >\n {value}\n </button>\n ))}\n </div>\n </div>\n\n <TextAreaField\n id=\"feedback-comment\"\n label={tr('coolandlovely.feedback.commentLabel', 'What could be better?')}\n value={comment}\n onChange={setComment}\n placeholder={tr(\n 'coolandlovely.feedback.commentPlaceholder',\n 'Tell us what’s working or what we should improve…'\n )}\n rows={3}\n hint={tr(\n 'coolandlovely.feedback.commentHint',\n 'Optional — your comment is stored with your score and current page.'\n )}\n />\n\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={score === null}\n className={cn(\n 'inline-flex w-full items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors',\n score === null\n ? 'cursor-not-allowed bg-bg-surface text-text-secondary'\n : 'bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90'\n )}\n >\n <Send className=\"h-4 w-4\" aria-hidden=\"true\" />\n {tr('coolandlovely.feedback.submit', 'Submit feedback')}\n </button>\n </div>\n )}\n </div>\n </GlassCard>\n )}\n\n <button\n type=\"button\"\n onClick={handleToggle}\n aria-label={\n isOpen\n ? tr('coolandlovely.feedback.close', 'Close feedback')\n : tr('coolandlovely.feedback.open', 'Give feedback')\n }\n aria-expanded={isOpen}\n className={cn(\n 'flex h-12 w-12 items-center justify-center rounded-full shadow-lg transition-transform hover:scale-105 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n isOpen\n ? 'bg-bg-surface text-text-primary'\n : 'bg-action-primary-bg text-action-primary-text'\n )}\n >\n {isOpen ? (\n <X className=\"h-5 w-5\" aria-hidden=\"true\" />\n ) : (\n <MessageSquarePlus className=\"h-5 w-5\" aria-hidden=\"true\" />\n )}\n </button>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;AAiBA,IAAM,IAAc,uCACd,IAAc,QAAc,KAAK;AASvC,SAAS,IAA+B;AACtC,KAAI,OAAO,SAAW,IAAa,QAAO;CAC1C,IAAM,IAAM,OAAO,aAAa,QAAQ,EAAY;AACpD,KAAI,CAAC,EAAK,QAAO;AACjB,KAAI;EACF,IAAM,IAAK,SAAS,GAAK,GAAG;AAC5B,SAAO,KAAK,KAAK,GAAG,IAAK;SACnB;AACN,SAAO;;;AAIX,SAAS,IAAmB;AACtB,QAAO,SAAW,OACtB,OAAO,aAAa,QAAQ,GAAa,OAAO,KAAK,KAAK,CAAC,CAAC;;AAG9D,SAAS,EAAkB,GAAgC;AAErD,QAAO,SAAW,OACtB,OAAO,cACL,IAAI,YAAY,oCAAoC;EAClD,QAAQ;EACR,SAAS;EACV,CAAC,CACH;;AAGH,IAAa,UAA2B;CACtC,IAAM,IAAK,GAAO,EACZ,CAAC,GAAQ,KAAa,EAAS,GAAM,EACrC,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAS,KAAc,EAAS,GAAG,EACpC,CAAC,GAAW,KAAgB,EAAS,GAAM,EAC3C,CAAC,GAAmB,KAAwB,EAAS,GAAM;AAEjE,SAAgB;AACd,IAAqB,GAAqB,CAAC;IAC1C,EAAE,CAAC;CAEN,IAAM,IAAe,QAAkB;AACrC,KAAW,MAAS,CAAC,EAAK;IACzB,EAAE,CAAC,EAEA,IAAc,QAAkB;AACpC,IAAU,GAAM;IACf,EAAE,CAAC,EAEA,IAAe,QAAkB;AACjC,QAAU,SASd,EAPuC;GACrC;GACA,SAAS,EAAQ,MAAM;GACvB,MAAM,OAAO,SAAW,MAAc,OAAO,SAAS,WAAW;GACjE,8BAAa,IAAI,MAAM,EAAC,aAAa;GACtC,CAE4B,EAC7B,GAAkB,EAClB,EAAa,GAAK,EAClB,EAAqB,GAAK,EAG1B,OAAO,iBAAiB;AAItB,GAHA,EAAU,GAAM,EAChB,EAAS,KAAK,EACd,EAAW,GAAG,EACd,EAAa,GAAM;KAClB,KAAK;IACP,CAAC,GAAO,EAAQ,CAAC;AAOpB,QALI,KAAqB,CAAC,IAEjB,OAIP,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,KACC,kBAAC,GAAD;GAAW,WAAU;aAArB,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,gCAAgC,wCAAwC;KACzE,CAAA,EACL,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,cAAY,EAAG,gCAAgC,iBAAiB;KAChE,WAAU;eAEV,kBAAC,GAAD;MAAG,WAAU;MAAU,eAAY;MAAS,CAAA;KACrC,CAAA,CACL;OAEN,kBAAC,OAAD;IAAK,WAAU;cACZ,IACC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAG,mCAAmC,+BAA+B;MACpE,CAAA,EACJ,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,mCACA,6CACD;MACC,CAAA,CACA;SAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,qCACA,0DACD;OACC,CAAA,EACJ,kBAAC,OAAD;OACE,WAAU;OACV,MAAK;OACL,cAAY,EAAG,mCAAmC,YAAY;iBAE7D,MAAM,KAAK,EAAE,QAAQ,IAAI,GAAG,GAAG,MAAM,EAAE,CAAC,KAAK,MAC5C,kBAAC,UAAD;QAEE,MAAK;QACL,eAAe,EAAS,EAAM;QAC9B,gBAAc,MAAU;QACxB,WAAW,EACT,6FACA,MAAU,IACN,kDACA,qHACL;kBAEA;QACM,EAZF,EAYE,CACT;OACE,CAAA,CACF,EAAA,CAAA;MAEN,kBAAC,GAAD;OACE,IAAG;OACH,OAAO,EAAG,uCAAuC,wBAAwB;OACzE,OAAO;OACP,UAAU;OACV,aAAa,EACX,6CACA,oDACD;OACD,MAAM;OACN,MAAM,EACJ,sCACA,sEACD;OACD,CAAA;MAEF,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,UAAU,MAAU;OACpB,WAAW,EACT,mHACA,MAAU,OACN,yDACA,8EACL;iBATH,CAWE,kBAAC,GAAD;QAAM,WAAU;QAAU,eAAY;QAAS,CAAA,EAC9C,EAAG,iCAAiC,kBAAkB,CAChD;;MACL;;IAEJ,CAAA,CACI;MAGd,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,cACE,IACI,EAAG,gCAAgC,iBAAiB,GACpD,EAAG,+BAA+B,gBAAgB;GAExD,iBAAe;GACf,WAAW,EACT,uMACA,IACI,oCACA,gDACL;aAGC,EADD,IACE,IAEA,GAFD;IAAG,WAAU;IAAU,eAAY;IAAS,CAEgB;GAEvD,CAAA,CACL"}
@@ -1,18 +1,18 @@
1
1
  import { cn as e } from "../utils/cn.js";
2
- import { Thumbnail as t } from "./Thumbnail.js";
3
- import { useLikeLook as n, useSaveLook as r, useUnlikeLook as i, useUnsaveLook as a } from "../hooks/useCoolAndLovelyApi.js";
2
+ import { useLikeLook as t, useSaveLook as n, useUnlikeLook as r, useUnsaveLook as i } from "../hooks/useCoolAndLovelyApi.js";
3
+ import { Thumbnail as a } from "./Thumbnail.js";
4
4
  import { AddToLookbookButton as o } from "./AddToLookbookButton.js";
5
5
  import { titleCase as s } from "../utils/formatters.js";
6
6
  import { useEffect as c, useState as l } from "react";
7
7
  import { Bookmark as u, Heart as d, Sparkles as f } from "lucide-react";
8
8
  import { jsx as p, jsxs as m } from "react/jsx-runtime";
9
9
  //#region src/coolandlovely/components/LookCard.tsx
10
- var h = ({ lookId: t, likeCount: r, isLikedByMe: a }) => {
11
- let o = n(), s = i(), [u, f] = l(!!a), [h, g] = l(r), _ = o.isPending || s.isPending;
10
+ var h = ({ lookId: n, likeCount: i, isLikedByMe: a }) => {
11
+ let o = t(), s = r(), [u, f] = l(!!a), [h, g] = l(i), _ = o.isPending || s.isPending;
12
12
  return c(() => {
13
- _ || (f(!!a), g(r));
13
+ _ || (f(!!a), g(i));
14
14
  }, [
15
- r,
15
+ i,
16
16
  a,
17
17
  _
18
18
  ]), /* @__PURE__ */ m("button", {
@@ -20,12 +20,12 @@ var h = ({ lookId: t, likeCount: r, isLikedByMe: a }) => {
20
20
  onClick: () => {
21
21
  if (_) return;
22
22
  let e = !u;
23
- f(e), g((t) => t + (e ? 1 : -1)), (e ? o : s).mutate(t, {
23
+ f(e), g((t) => t + (e ? 1 : -1)), (e ? o : s).mutate(n, {
24
24
  onSuccess: (e) => {
25
25
  f(!!e.isLikedByMe), g(e.likeCount);
26
26
  },
27
27
  onError: () => {
28
- f(!e), g(r);
28
+ f(!e), g(i);
29
29
  }
30
30
  });
31
31
  },
@@ -38,16 +38,16 @@ var h = ({ lookId: t, likeCount: r, isLikedByMe: a }) => {
38
38
  children: h
39
39
  })]
40
40
  });
41
- }, g = ({ lookId: t, isSavedByMe: n }) => {
42
- let i = r(), o = a(), [s, d] = l(!!n), f = i.isPending || o.isPending;
41
+ }, g = ({ lookId: t, isSavedByMe: r }) => {
42
+ let a = n(), o = i(), [s, d] = l(!!r), f = a.isPending || o.isPending;
43
43
  return c(() => {
44
- f || d(!!n);
45
- }, [n, f]), /* @__PURE__ */ p("button", {
44
+ f || d(!!r);
45
+ }, [r, f]), /* @__PURE__ */ p("button", {
46
46
  type: "button",
47
47
  onClick: () => {
48
48
  if (f) return;
49
49
  let e = !s;
50
- d(e), (e ? i : o).mutate(t, {
50
+ d(e), (e ? a : o).mutate(t, {
51
51
  onSuccess: (e) => d(!!e.isSavedByMe),
52
52
  onError: () => d(!e)
53
53
  });
@@ -58,29 +58,29 @@ var h = ({ lookId: t, likeCount: r, isLikedByMe: a }) => {
58
58
  className: "inline-flex items-center justify-center rounded-full bg-bg-surface/90 p-1.5 text-text-primary shadow-sm backdrop-blur transition-colors hover:bg-bg-surface disabled:opacity-60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
59
59
  children: /* @__PURE__ */ p(u, { className: e("h-3.5 w-3.5", s ? "fill-current text-accent-primary" : "text-text-secondary") })
60
60
  });
61
- }, _ = ({ id: n, title: r, occasion: i, imageFileId: a, likeCount: c, isLikedByMe: l, isSavedByMe: u, onOpen: _, showActions: v = !1, className: y }) => /* @__PURE__ */ m("div", {
61
+ }, _ = ({ id: t, title: n, occasion: r, imageFileId: i, likeCount: c, isLikedByMe: l, isSavedByMe: u, onOpen: _, showActions: v = !1, className: y }) => /* @__PURE__ */ m("div", {
62
62
  className: e("group relative overflow-hidden rounded-xl border border-border-seam bg-bg-surface transition-shadow hover:shadow-lg", y),
63
63
  children: [/* @__PURE__ */ p("button", {
64
64
  type: "button",
65
65
  onClick: _,
66
- "aria-label": `Open look ${r}`,
66
+ "aria-label": `Open look ${n}`,
67
67
  className: "block w-full text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--color-focus-ring)]",
68
- children: /* @__PURE__ */ p(t, {
68
+ children: /* @__PURE__ */ p(a, {
69
69
  icon: /* @__PURE__ */ p(f, { className: "h-8 w-8" }),
70
- seed: n,
71
- fileId: a,
72
- alt: r,
70
+ seed: t,
71
+ fileId: i,
72
+ alt: n,
73
73
  className: "aspect-[3/4] w-full rounded-none",
74
74
  overlay: /* @__PURE__ */ m("div", {
75
75
  className: "pointer-events-none absolute inset-x-0 bottom-0 flex flex-col gap-1.5 bg-gradient-to-t from-bg-canvas/90 via-bg-canvas/45 to-transparent p-3 pt-10",
76
76
  children: [/* @__PURE__ */ p("p", {
77
77
  className: "truncate text-sm font-semibold text-text-primary",
78
- children: r
78
+ children: n
79
79
  }), /* @__PURE__ */ m("div", {
80
80
  className: "flex items-center justify-between gap-2",
81
81
  children: [/* @__PURE__ */ p("span", {
82
82
  className: "inline-flex items-center rounded-full bg-bg-surface/85 px-2 py-0.5 text-[11px] font-medium text-text-primary backdrop-blur",
83
- children: s(i)
83
+ children: s(r)
84
84
  }), !v && typeof c == "number" && c > 0 ? /* @__PURE__ */ m("span", {
85
85
  className: "inline-flex items-center gap-1 text-xs text-text-secondary",
86
86
  children: [/* @__PURE__ */ p(d, { className: "h-3.5 w-3.5 fill-current text-accent-primary" }), /* @__PURE__ */ p("span", {
@@ -95,15 +95,15 @@ var h = ({ lookId: t, likeCount: r, isLikedByMe: a }) => {
95
95
  className: "absolute right-2 top-2 flex items-center gap-1.5 opacity-100 transition-opacity duration-200 sm:opacity-0 sm:focus-within:opacity-100 sm:group-hover:opacity-100",
96
96
  children: [
97
97
  /* @__PURE__ */ p(h, {
98
- lookId: n,
98
+ lookId: t,
99
99
  likeCount: c ?? 0,
100
100
  isLikedByMe: l
101
101
  }),
102
102
  /* @__PURE__ */ p(g, {
103
- lookId: n,
103
+ lookId: t,
104
104
  isSavedByMe: u
105
105
  }),
106
- /* @__PURE__ */ p(o, { lookId: n })
106
+ /* @__PURE__ */ p(o, { lookId: t })
107
107
  ]
108
108
  }) : null]
109
109
  });
@@ -1 +1 @@
1
- {"version":3,"file":"StateViews.d.ts","sourceRoot":"","sources":["../../../src/coolandlovely/components/StateViews.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAK5C,CAAC;AAEF,UAAU,cAAc;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,CAyBxC,CAAC"}
1
+ {"version":3,"file":"StateViews.d.ts","sourceRoot":"","sources":["../../../src/coolandlovely/components/StateViews.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAKhC,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAK5C,CAAC;AAEF,UAAU,cAAc;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,CA4BxC,CAAC"}
@@ -1,34 +1,43 @@
1
1
  import { cn as e } from "../utils/cn.js";
2
- import { AlertCircle as t, Loader2 as n, RotateCw as r } from "lucide-react";
3
- import { jsx as i, jsxs as a } from "react/jsx-runtime";
2
+ import { useTr as t } from "../hooks/useTr.js";
3
+ import "../hooks/index.js";
4
+ import { AlertCircle as n, Loader2 as r, RotateCw as i } from "lucide-react";
5
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
4
6
  //#region src/coolandlovely/components/StateViews.tsx
5
- var o = ({ label: t = "Loading…", className: r }) => /* @__PURE__ */ a("div", {
6
- className: e("flex min-h-[40vh] flex-col items-center justify-center gap-3", r),
7
- children: [/* @__PURE__ */ i(n, { className: "h-8 w-8 animate-spin text-primary" }), /* @__PURE__ */ i("p", {
7
+ var s = ({ label: t = "Loading…", className: n }) => /* @__PURE__ */ o("div", {
8
+ className: e("flex min-h-[40vh] flex-col items-center justify-center gap-3", n),
9
+ children: [/* @__PURE__ */ a(r, { className: "h-8 w-8 animate-spin text-primary" }), /* @__PURE__ */ a("p", {
8
10
  className: "text-sm text-text-secondary",
9
11
  children: t
10
12
  })]
11
- }), s = ({ title: n = "Something went wrong", message: o = "We could not load this data. Please try again.", onRetry: s, className: c }) => /* @__PURE__ */ a("div", {
12
- className: e("flex min-h-[40vh] flex-col items-center justify-center gap-3 text-center", c),
13
- children: [
14
- /* @__PURE__ */ i(t, { className: "h-10 w-10 text-status-error-text" }),
15
- /* @__PURE__ */ i("h2", {
16
- className: "text-lg font-semibold text-text-primary",
17
- children: n
18
- }),
19
- /* @__PURE__ */ i("p", {
20
- className: "max-w-sm text-sm text-text-secondary",
21
- children: o
22
- }),
23
- s && /* @__PURE__ */ a("button", {
24
- type: "button",
25
- onClick: s,
26
- className: "mt-1 inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
27
- children: [/* @__PURE__ */ i(r, { className: "h-4 w-4" }), " Retry"]
28
- })
29
- ]
30
- });
13
+ }), c = ({ title: r, message: s, onRetry: c, className: l }) => {
14
+ let u = t();
15
+ return /* @__PURE__ */ o("div", {
16
+ className: e("flex min-h-[40vh] flex-col items-center justify-center gap-3 text-center", l),
17
+ children: [
18
+ /* @__PURE__ */ a(n, { className: "h-10 w-10 text-status-error-text" }),
19
+ /* @__PURE__ */ a("h2", {
20
+ className: "text-lg font-semibold text-text-primary",
21
+ children: r ?? u("coolandlovely.error.title", "Something went wrong")
22
+ }),
23
+ /* @__PURE__ */ a("p", {
24
+ className: "max-w-sm text-sm text-text-secondary",
25
+ children: s ?? u("coolandlovely.error.message", "We could not load this data. Please try again.")
26
+ }),
27
+ c && /* @__PURE__ */ o("button", {
28
+ type: "button",
29
+ onClick: c,
30
+ className: "mt-1 inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
31
+ children: [
32
+ /* @__PURE__ */ a(i, { className: "h-4 w-4" }),
33
+ " ",
34
+ u("coolandlovely.error.retry", "Retry")
35
+ ]
36
+ })
37
+ ]
38
+ });
39
+ };
31
40
  //#endregion
32
- export { s as ErrorView, o as LoadingView };
41
+ export { c as ErrorView, s as LoadingView };
33
42
 
34
43
  //# sourceMappingURL=StateViews.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StateViews.js","names":[],"sources":["../../../src/coolandlovely/components/StateViews.tsx"],"sourcesContent":["/**\n * Loading / Error state views\n * @module coolandlovely/components\n *\n * Calm, on-token loading and error panels reused across the domain pages so\n * every screen has consistent loading/empty/error treatment.\n */\n\nimport type { FC } from 'react';\nimport { AlertCircle, Loader2, RotateCw } from 'lucide-react';\nimport { cn } from '../utils/cn';\n\ninterface LoadingViewProps {\n label?: string;\n className?: string;\n}\n\nexport const LoadingView: FC<LoadingViewProps> = ({ label = 'Loading…', className }) => (\n <div className={cn('flex min-h-[40vh] flex-col items-center justify-center gap-3', className)}>\n <Loader2 className=\"h-8 w-8 animate-spin text-primary\" />\n <p className=\"text-sm text-text-secondary\">{label}</p>\n </div>\n);\n\ninterface ErrorViewProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n className?: string;\n}\n\nexport const ErrorView: FC<ErrorViewProps> = ({\n title = 'Something went wrong',\n message = 'We could not load this data. Please try again.',\n onRetry,\n className,\n}) => (\n <div\n className={cn(\n 'flex min-h-[40vh] flex-col items-center justify-center gap-3 text-center',\n className\n )}\n >\n <AlertCircle className=\"h-10 w-10 text-status-error-text\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n <p className=\"max-w-sm text-sm text-text-secondary\">{message}</p>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"mt-1 inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <RotateCw className=\"h-4 w-4\" /> Retry\n </button>\n )}\n </div>\n);\n"],"mappings":";;;;AAiBA,IAAa,KAAqC,EAAE,WAAQ,YAAY,mBACtE,kBAAC,OAAD;CAAK,WAAW,EAAG,gEAAgE,EAAU;WAA7F,CACE,kBAAC,GAAD,EAAS,WAAU,qCAAsC,CAAA,EACzD,kBAAC,KAAD;EAAG,WAAU;YAA+B;EAAU,CAAA,CAClD;IAUK,KAAiC,EAC5C,WAAQ,wBACR,aAAU,kDACV,YACA,mBAEA,kBAAC,OAAD;CACE,WAAW,EACT,4EACA,EACD;WAJH;EAME,kBAAC,GAAD,EAAa,WAAU,oCAAqC,CAAA;EAC5D,kBAAC,MAAD;GAAI,WAAU;aAA2C;GAAW,CAAA;EACpE,kBAAC,KAAD;GAAG,WAAU;aAAwC;GAAY,CAAA;EAChE,KACC,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,SACzB;;EAEP"}
1
+ {"version":3,"file":"StateViews.js","names":[],"sources":["../../../src/coolandlovely/components/StateViews.tsx"],"sourcesContent":["/**\n * Loading / Error state views\n * @module coolandlovely/components\n *\n * Calm, on-token loading and error panels reused across the domain pages so\n * every screen has consistent loading/empty/error treatment.\n */\n\nimport type { FC } from 'react';\nimport { AlertCircle, Loader2, RotateCw } from 'lucide-react';\nimport { cn } from '../utils/cn';\nimport { useTr } from '../hooks';\n\ninterface LoadingViewProps {\n label?: string;\n className?: string;\n}\n\nexport const LoadingView: FC<LoadingViewProps> = ({ label = 'Loading…', className }) => (\n <div className={cn('flex min-h-[40vh] flex-col items-center justify-center gap-3', className)}>\n <Loader2 className=\"h-8 w-8 animate-spin text-primary\" />\n <p className=\"text-sm text-text-secondary\">{label}</p>\n </div>\n);\n\ninterface ErrorViewProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n className?: string;\n}\n\nexport const ErrorView: FC<ErrorViewProps> = ({ title, message, onRetry, className }) => {\n const tr = useTr();\n return (\n <div\n className={cn(\n 'flex min-h-[40vh] flex-col items-center justify-center gap-3 text-center',\n className\n )}\n >\n <AlertCircle className=\"h-10 w-10 text-status-error-text\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {title ?? tr('coolandlovely.error.title', 'Something went wrong')}\n </h2>\n <p className=\"max-w-sm text-sm text-text-secondary\">\n {message ??\n tr('coolandlovely.error.message', 'We could not load this data. Please try again.')}\n </p>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"mt-1 inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <RotateCw className=\"h-4 w-4\" /> {tr('coolandlovely.error.retry', 'Retry')}\n </button>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;AAkBA,IAAa,KAAqC,EAAE,WAAQ,YAAY,mBACtE,kBAAC,OAAD;CAAK,WAAW,EAAG,gEAAgE,EAAU;WAA7F,CACE,kBAAC,GAAD,EAAS,WAAU,qCAAsC,CAAA,EACzD,kBAAC,KAAD;EAAG,WAAU;YAA+B;EAAU,CAAA,CAClD;IAUK,KAAiC,EAAE,UAAO,YAAS,YAAS,mBAAgB;CACvF,IAAM,IAAK,GAAO;AAClB,QACE,kBAAC,OAAD;EACE,WAAW,EACT,4EACA,EACD;YAJH;GAME,kBAAC,GAAD,EAAa,WAAU,oCAAqC,CAAA;GAC5D,kBAAC,MAAD;IAAI,WAAU;cACX,KAAS,EAAG,6BAA6B,uBAAuB;IAC9D,CAAA;GACL,kBAAC,KAAD;IAAG,WAAU;cACV,KACC,EAAG,+BAA+B,iDAAiD;IACnF,CAAA;GACH,KACC,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cAHZ;KAKE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;;KAAE,EAAG,6BAA6B,QAAQ;KACnE;;GAEP"}
@@ -1 +1 @@
1
- {"version":3,"file":"StatusBadge.d.ts","sourceRoot":"","sources":["../../../src/coolandlovely/components/StatusBadge.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAGhC,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAwED,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAwB5C,CAAC"}
1
+ {"version":3,"file":"StatusBadge.d.ts","sourceRoot":"","sources":["../../../src/coolandlovely/components/StatusBadge.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsGD,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CA2B5C,CAAC"}