@burdenoff/microfe-coolandlovely 2026.820.3 → 2026.820.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/coolandlovely/CoolAndLovelyRoot.d.ts.map +1 -1
- package/dist/coolandlovely/CoolAndLovelyRoot.js +90 -54
- package/dist/coolandlovely/CoolAndLovelyRoot.js.map +1 -1
- package/dist/coolandlovely/CoolAndLovelyRoutes.d.ts.map +1 -1
- package/dist/coolandlovely/CoolAndLovelyRoutes.js +25 -6
- package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
- package/dist/coolandlovely/hooks/index.d.ts +2 -2
- package/dist/coolandlovely/hooks/index.d.ts.map +1 -1
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.d.ts +19 -1
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.d.ts.map +1 -1
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js +121 -14
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
- package/dist/coolandlovely/index.d.ts +1 -1
- package/dist/coolandlovely/index.d.ts.map +1 -1
- package/dist/coolandlovely/pages/admin/AdminBrandsPage.d.ts +9 -0
- package/dist/coolandlovely/pages/admin/AdminBrandsPage.d.ts.map +1 -0
- package/dist/coolandlovely/pages/admin/AdminBrandsPage.js +82 -0
- package/dist/coolandlovely/pages/admin/AdminBrandsPage.js.map +1 -0
- package/dist/coolandlovely/pages/admin/AdminCollaborationsPage.d.ts +9 -0
- package/dist/coolandlovely/pages/admin/AdminCollaborationsPage.d.ts.map +1 -0
- package/dist/coolandlovely/pages/admin/AdminCollaborationsPage.js +142 -0
- package/dist/coolandlovely/pages/admin/AdminCollaborationsPage.js.map +1 -0
- package/dist/coolandlovely/pages/admin/AdminFeaturedItemsPage.d.ts +9 -0
- package/dist/coolandlovely/pages/admin/AdminFeaturedItemsPage.d.ts.map +1 -0
- package/dist/coolandlovely/pages/admin/AdminFeaturedItemsPage.js +139 -0
- package/dist/coolandlovely/pages/admin/AdminFeaturedItemsPage.js.map +1 -0
- package/dist/coolandlovely/pages/admin/index.d.ts +8 -0
- package/dist/coolandlovely/pages/admin/index.d.ts.map +1 -0
- package/dist/coolandlovely/pages/discover/DiscoverPage.d.ts +7 -6
- package/dist/coolandlovely/pages/discover/DiscoverPage.d.ts.map +1 -1
- package/dist/coolandlovely/pages/discover/DiscoverPage.js +480 -165
- package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -1
- package/dist/coolandlovely/types/index.d.ts +24 -0
- package/dist/coolandlovely/types/index.d.ts.map +1 -1
- package/dist/stories/DiscoverPage.stories.d.ts +11 -0
- package/dist/stories/DiscoverPage.stories.d.ts.map +1 -0
- package/dist/stories/__fixtures__/coolandlovely.d.ts +3 -1
- package/dist/stories/__fixtures__/coolandlovely.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
+
import { StatusBadge as n } from "../../components/StatusBadge.js";
|
|
4
|
+
import { ErrorView as r, LoadingView as i } from "../../components/StateViews.js";
|
|
5
|
+
import { Thumbnail as a } from "../../components/Thumbnail.js";
|
|
6
|
+
import { useApproveBrand as o, useBrands as s, useRejectBrand as c } from "../../hooks/useCoolAndLovelyApi.js";
|
|
7
|
+
import "../../hooks/index.js";
|
|
8
|
+
import { EmptyState as l } from "../../components/EmptyState.js";
|
|
9
|
+
import { Check as u, Store as d, X as f } from "lucide-react";
|
|
10
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
11
|
+
//#region src/coolandlovely/pages/admin/AdminBrandsPage.tsx
|
|
12
|
+
function h({ brand: t }) {
|
|
13
|
+
let r = o(), i = c(), s = r.isPending || i.isPending;
|
|
14
|
+
return /* @__PURE__ */ m("div", {
|
|
15
|
+
className: "flex items-center gap-4 rounded-lg border border-border-seam bg-bg-surface p-4",
|
|
16
|
+
children: [
|
|
17
|
+
/* @__PURE__ */ p(a, {
|
|
18
|
+
icon: /* @__PURE__ */ p(d, { className: "h-5 w-5" }),
|
|
19
|
+
seed: t.id,
|
|
20
|
+
fileId: t.logo?.id ?? t.cover?.id,
|
|
21
|
+
alt: t.name,
|
|
22
|
+
className: "h-12 w-12 shrink-0 rounded-lg"
|
|
23
|
+
}),
|
|
24
|
+
/* @__PURE__ */ m("div", {
|
|
25
|
+
className: "min-w-0 flex-1",
|
|
26
|
+
children: [
|
|
27
|
+
/* @__PURE__ */ m("div", {
|
|
28
|
+
className: "flex items-center gap-2",
|
|
29
|
+
children: [/* @__PURE__ */ p("h3", {
|
|
30
|
+
className: "truncate text-sm font-semibold text-text-primary",
|
|
31
|
+
children: t.name
|
|
32
|
+
}), /* @__PURE__ */ p(n, { status: t.status })]
|
|
33
|
+
}),
|
|
34
|
+
/* @__PURE__ */ m("p", {
|
|
35
|
+
className: "truncate text-xs text-text-secondary",
|
|
36
|
+
children: ["@", t.handle]
|
|
37
|
+
}),
|
|
38
|
+
t.tagline && /* @__PURE__ */ p("p", {
|
|
39
|
+
className: "mt-1 truncate text-xs text-text-secondary",
|
|
40
|
+
children: t.tagline
|
|
41
|
+
})
|
|
42
|
+
]
|
|
43
|
+
}),
|
|
44
|
+
/* @__PURE__ */ m("div", {
|
|
45
|
+
className: "flex shrink-0 items-center gap-2",
|
|
46
|
+
children: [/* @__PURE__ */ m("button", {
|
|
47
|
+
type: "button",
|
|
48
|
+
disabled: s,
|
|
49
|
+
onClick: () => r.mutate(t.id),
|
|
50
|
+
className: e("inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium", "bg-status-success-bg-subtle text-status-success-text hover:bg-status-success-bg-subtle/80", "disabled:cursor-not-allowed disabled:opacity-50"),
|
|
51
|
+
children: [/* @__PURE__ */ p(u, { className: "h-4 w-4" }), " Approve"]
|
|
52
|
+
}), /* @__PURE__ */ m("button", {
|
|
53
|
+
type: "button",
|
|
54
|
+
disabled: s,
|
|
55
|
+
onClick: () => i.mutate(t.id),
|
|
56
|
+
className: e("inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium", "bg-status-error-bg-subtle text-status-error-text hover:bg-status-error-bg-subtle/80", "disabled:cursor-not-allowed disabled:opacity-50"),
|
|
57
|
+
children: [/* @__PURE__ */ p(f, { className: "h-4 w-4" }), " Reject"]
|
|
58
|
+
})]
|
|
59
|
+
})
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function g() {
|
|
64
|
+
let e = s({ status: "PENDING_REVIEW" }), n = e.data?.items ?? [];
|
|
65
|
+
return /* @__PURE__ */ p(t, {
|
|
66
|
+
title: "Admin — Brands",
|
|
67
|
+
description: "Approve or reject storefronts pending review",
|
|
68
|
+
children: e.isLoading ? /* @__PURE__ */ p(i, { label: "Loading pending brands…" }) : e.isError ? /* @__PURE__ */ p(r, { onRetry: () => void e.refetch() }) : n.length === 0 ? /* @__PURE__ */ p(l, {
|
|
69
|
+
illustration: "empty-data",
|
|
70
|
+
icon: /* @__PURE__ */ p(d, { className: "h-10 w-10" }),
|
|
71
|
+
title: "No pending brands",
|
|
72
|
+
description: "All brand storefronts have been reviewed."
|
|
73
|
+
}) : /* @__PURE__ */ p("div", {
|
|
74
|
+
className: "space-y-3",
|
|
75
|
+
children: n.map((e) => /* @__PURE__ */ p(h, { brand: e }, e.id))
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
export { g as AdminBrandsPage };
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=AdminBrandsPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AdminBrandsPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/admin/AdminBrandsPage.tsx"],"sourcesContent":["/**\n * Admin Brands Page\n * @module coolandlovely/pages/admin\n *\n * Curation surface for brand storefront approvals. Lists brands pending review\n * and lets admins approve (PUBLISHED) or reject (DRAFT) them.\n */\n\nimport { Check, Store, X } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { useBrands, useApproveBrand, useRejectBrand } from '../../hooks';\nimport { cn } from '../../utils/cn';\nimport type { CooBrand } from '../../types';\n\nfunction BrandReviewRow({ brand }: { brand: CooBrand }) {\n const approve = useApproveBrand();\n const reject = useRejectBrand();\n const isBusy = approve.isPending || reject.isPending;\n\n return (\n <div className=\"flex items-center gap-4 rounded-lg border border-border-seam bg-bg-surface p-4\">\n <Thumbnail\n icon={<Store className=\"h-5 w-5\" />}\n seed={brand.id}\n fileId={brand.logo?.id ?? brand.cover?.id}\n alt={brand.name}\n className=\"h-12 w-12 shrink-0 rounded-lg\"\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">{brand.name}</h3>\n <StatusBadge status={brand.status} />\n </div>\n <p className=\"truncate text-xs text-text-secondary\">@{brand.handle}</p>\n {brand.tagline && (\n <p className=\"mt-1 truncate text-xs text-text-secondary\">{brand.tagline}</p>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <button\n type=\"button\"\n disabled={isBusy}\n onClick={() => approve.mutate(brand.id)}\n className={cn(\n 'inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium',\n 'bg-status-success-bg-subtle text-status-success-text hover:bg-status-success-bg-subtle/80',\n 'disabled:cursor-not-allowed disabled:opacity-50'\n )}\n >\n <Check className=\"h-4 w-4\" /> Approve\n </button>\n <button\n type=\"button\"\n disabled={isBusy}\n onClick={() => reject.mutate(brand.id)}\n className={cn(\n 'inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium',\n 'bg-status-error-bg-subtle text-status-error-text hover:bg-status-error-bg-subtle/80',\n 'disabled:cursor-not-allowed disabled:opacity-50'\n )}\n >\n <X className=\"h-4 w-4\" /> Reject\n </button>\n </div>\n </div>\n );\n}\n\nexport function AdminBrandsPage() {\n const query = useBrands({ status: 'PENDING_REVIEW' });\n const brands = query.data?.items ?? [];\n\n return (\n <PageLayout title=\"Admin — Brands\" description=\"Approve or reject storefronts pending review\">\n {query.isLoading ? (\n <LoadingView label=\"Loading pending brands…\" />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : brands.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Store className=\"h-10 w-10\" />}\n title=\"No pending brands\"\n description=\"All brand storefronts have been reviewed.\"\n />\n ) : (\n <div className=\"space-y-3\">\n {brands.map((brand) => (\n <BrandReviewRow key={brand.id} brand={brand} />\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;AAkBA,SAAS,EAAe,EAAE,YAA8B;CACtD,IAAM,IAAU,GAAiB,EAC3B,IAAS,GAAgB,EACzB,IAAS,EAAQ,aAAa,EAAO;AAE3C,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;IACnC,MAAM,EAAM;IACZ,QAAQ,EAAM,MAAM,MAAM,EAAM,OAAO;IACvC,KAAK,EAAM;IACX,WAAU;IACV,CAAA;GACF,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAoD,EAAM;OAAU,CAAA,EAClF,kBAAC,GAAD,EAAa,QAAQ,EAAM,QAAU,CAAA,CACjC;;KACN,kBAAC,KAAD;MAAG,WAAU;gBAAb,CAAoD,KAAE,EAAM,OAAW;;KACtE,EAAM,WACL,kBAAC,KAAD;MAAG,WAAU;gBAA6C,EAAM;MAAY,CAAA;KAE1E;;GACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,UAAU;KACV,eAAe,EAAQ,OAAO,EAAM,GAAG;KACvC,WAAW,EACT,6EACA,6FACA,kDACD;eARH,CAUE,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA,EAAA,WACtB;QACT,kBAAC,UAAD;KACE,MAAK;KACL,UAAU;KACV,eAAe,EAAO,OAAO,EAAM,GAAG;KACtC,WAAW,EACT,6EACA,uFACA,kDACD;eARH,CAUE,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA,EAAA,UAClB;OACL;;GACF;;;AAIV,SAAgB,IAAkB;CAChC,IAAM,IAAQ,EAAU,EAAE,QAAQ,kBAAkB,CAAC,EAC/C,IAAS,EAAM,MAAM,SAAS,EAAE;AAEtC,QACE,kBAAC,GAAD;EAAY,OAAM;EAAiB,aAAY;YAC5C,EAAM,YACL,kBAAC,GAAD,EAAa,OAAM,2BAA4B,CAAA,GAC7C,EAAM,UACR,kBAAC,GAAD,EAAW,eAAe,KAAK,EAAM,SAAS,EAAI,CAAA,GAChD,EAAO,WAAW,IACpB,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;GACrC,OAAM;GACN,aAAY;GACZ,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAO,KAAK,MACX,kBAAC,GAAD,EAAsC,UAAS,EAA1B,EAAM,GAAoB,CAC/C;GACE,CAAA;EAEG,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Collaborations Page
|
|
3
|
+
* @module coolandlovely/pages/admin
|
|
4
|
+
*
|
|
5
|
+
* Curation surface for brand × creator collaborations. Lists collaborations that
|
|
6
|
+
* can be advanced through their lifecycle via APPROVE/REJECT admin decisions.
|
|
7
|
+
*/
|
|
8
|
+
export declare function AdminCollaborationsPage(): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=AdminCollaborationsPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AdminCollaborationsPage.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/pages/admin/AdminCollaborationsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAwGH,wBAAgB,uBAAuB,gCAgDtC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
+
import { StatusBadge as n } from "../../components/StatusBadge.js";
|
|
4
|
+
import { ErrorView as r, LoadingView as i } from "../../components/StateViews.js";
|
|
5
|
+
import { useCollaborations as a, useReviewCollaboration as o } from "../../hooks/useCoolAndLovelyApi.js";
|
|
6
|
+
import { formatPrice as s, titleCase as c } from "../../utils/formatters.js";
|
|
7
|
+
import "../../hooks/index.js";
|
|
8
|
+
import { EmptyState as l } from "../../components/EmptyState.js";
|
|
9
|
+
import { ActorName as u } from "../../components/ActorName.js";
|
|
10
|
+
import { useState as d } from "react";
|
|
11
|
+
import { Check as f, FileText as p, Handshake as m, X as h } from "lucide-react";
|
|
12
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
13
|
+
//#region src/coolandlovely/pages/admin/AdminCollaborationsPage.tsx
|
|
14
|
+
var v = [
|
|
15
|
+
"PROPOSED",
|
|
16
|
+
"ACCEPTED",
|
|
17
|
+
"IN_PROGRESS"
|
|
18
|
+
], y = [
|
|
19
|
+
{
|
|
20
|
+
value: "ALL",
|
|
21
|
+
label: "All"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
value: "PROPOSED",
|
|
25
|
+
label: "Proposed"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
value: "ACCEPTED",
|
|
29
|
+
label: "Accepted"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
value: "IN_PROGRESS",
|
|
33
|
+
label: "In progress"
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
function b({ collaboration: t }) {
|
|
37
|
+
let r = o(), [i, a] = d(""), l = v.includes(t.status), m = r.isPending;
|
|
38
|
+
return /* @__PURE__ */ _("div", {
|
|
39
|
+
className: "rounded-lg border border-border-seam bg-bg-surface p-4",
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ g("div", {
|
|
42
|
+
className: "flex items-start gap-3",
|
|
43
|
+
children: /* @__PURE__ */ _("div", {
|
|
44
|
+
className: "min-w-0 flex-1",
|
|
45
|
+
children: [/* @__PURE__ */ _("div", {
|
|
46
|
+
className: "flex items-center gap-2",
|
|
47
|
+
children: [/* @__PURE__ */ g("h3", {
|
|
48
|
+
className: "truncate text-sm font-semibold text-text-primary",
|
|
49
|
+
children: t.title
|
|
50
|
+
}), /* @__PURE__ */ g(n, { status: t.status })]
|
|
51
|
+
}), /* @__PURE__ */ _("p", {
|
|
52
|
+
className: "mt-0.5 text-xs text-text-secondary",
|
|
53
|
+
children: [
|
|
54
|
+
t.brand?.name ?? "Brand",
|
|
55
|
+
" × ",
|
|
56
|
+
/* @__PURE__ */ g(u, { id: t.creatorId })
|
|
57
|
+
]
|
|
58
|
+
})]
|
|
59
|
+
})
|
|
60
|
+
}),
|
|
61
|
+
t.brief && /* @__PURE__ */ g("p", {
|
|
62
|
+
className: "mt-3 line-clamp-2 text-sm text-text-secondary",
|
|
63
|
+
children: t.brief
|
|
64
|
+
}),
|
|
65
|
+
/* @__PURE__ */ _("div", {
|
|
66
|
+
className: "mt-3 flex flex-wrap items-center gap-2 text-xs text-text-secondary",
|
|
67
|
+
children: [/* @__PURE__ */ g("span", {
|
|
68
|
+
className: "rounded-full bg-bg-sunken px-2 py-0.5",
|
|
69
|
+
children: c(t.role)
|
|
70
|
+
}), /* @__PURE__ */ g("span", { children: s(t.compensation, t.currency) })]
|
|
71
|
+
}),
|
|
72
|
+
l && /* @__PURE__ */ _("div", {
|
|
73
|
+
className: "mt-4 space-y-3 border-t border-border-seam pt-3",
|
|
74
|
+
children: [/* @__PURE__ */ _("div", {
|
|
75
|
+
className: "flex items-center gap-2",
|
|
76
|
+
children: [/* @__PURE__ */ g(p, { className: "h-4 w-4 text-text-secondary" }), /* @__PURE__ */ g("input", {
|
|
77
|
+
type: "text",
|
|
78
|
+
value: i,
|
|
79
|
+
onChange: (e) => a(e.target.value),
|
|
80
|
+
placeholder: "Optional review notes",
|
|
81
|
+
className: "flex-1 rounded-md border border-border-seam bg-bg-surface px-3 py-1.5 text-sm text-text-primary placeholder:text-text-tertiary focus:outline-none focus:ring-1 focus:ring-primary"
|
|
82
|
+
})]
|
|
83
|
+
}), /* @__PURE__ */ _("div", {
|
|
84
|
+
className: "flex items-center justify-end gap-2",
|
|
85
|
+
children: [/* @__PURE__ */ _("button", {
|
|
86
|
+
type: "button",
|
|
87
|
+
disabled: m,
|
|
88
|
+
onClick: () => r.mutate({
|
|
89
|
+
id: t.id,
|
|
90
|
+
input: {
|
|
91
|
+
decision: "REJECT",
|
|
92
|
+
notes: i
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
className: e("inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium", "bg-status-error-bg-subtle text-status-error-text hover:bg-status-error-bg-subtle/80", "disabled:cursor-not-allowed disabled:opacity-50"),
|
|
96
|
+
children: [/* @__PURE__ */ g(h, { className: "h-4 w-4" }), " Reject"]
|
|
97
|
+
}), /* @__PURE__ */ _("button", {
|
|
98
|
+
type: "button",
|
|
99
|
+
disabled: m,
|
|
100
|
+
onClick: () => r.mutate({
|
|
101
|
+
id: t.id,
|
|
102
|
+
input: {
|
|
103
|
+
decision: "APPROVE",
|
|
104
|
+
notes: i
|
|
105
|
+
}
|
|
106
|
+
}),
|
|
107
|
+
className: e("inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium", "bg-status-success-bg-subtle text-status-success-text hover:bg-status-success-bg-subtle/80", "disabled:cursor-not-allowed disabled:opacity-50"),
|
|
108
|
+
children: [/* @__PURE__ */ g(f, { className: "h-4 w-4" }), " Approve"]
|
|
109
|
+
})]
|
|
110
|
+
})]
|
|
111
|
+
})
|
|
112
|
+
]
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function x() {
|
|
116
|
+
let [n, o] = d("ALL"), s = a({ status: n === "ALL" ? void 0 : n }), c = s.data ?? [];
|
|
117
|
+
return /* @__PURE__ */ _(t, {
|
|
118
|
+
title: "Admin — Collaborations",
|
|
119
|
+
description: "Review and advance brand × creator partnerships",
|
|
120
|
+
children: [/* @__PURE__ */ g("div", {
|
|
121
|
+
className: "mb-6 flex flex-wrap items-center gap-2",
|
|
122
|
+
children: y.map((t) => /* @__PURE__ */ g("button", {
|
|
123
|
+
type: "button",
|
|
124
|
+
onClick: () => o(t.value),
|
|
125
|
+
className: e("rounded-full px-3 py-1.5 text-sm font-medium transition-colors", n === t.value ? "bg-action-primary-bg text-action-primary-text" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
|
|
126
|
+
children: t.label
|
|
127
|
+
}, t.value))
|
|
128
|
+
}), s.isLoading ? /* @__PURE__ */ g(i, { label: "Loading collaborations…" }) : s.isError ? /* @__PURE__ */ g(r, { onRetry: () => void s.refetch() }) : c.length === 0 ? /* @__PURE__ */ g(l, {
|
|
129
|
+
illustration: "empty-data",
|
|
130
|
+
icon: /* @__PURE__ */ g(m, { className: "h-10 w-10" }),
|
|
131
|
+
title: "No collaborations to review",
|
|
132
|
+
description: "There are no collaborations matching this filter."
|
|
133
|
+
}) : /* @__PURE__ */ g("div", {
|
|
134
|
+
className: "space-y-3",
|
|
135
|
+
children: c.map((e) => /* @__PURE__ */ g(b, { collaboration: e }, e.id))
|
|
136
|
+
})]
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
export { x as AdminCollaborationsPage };
|
|
141
|
+
|
|
142
|
+
//# sourceMappingURL=AdminCollaborationsPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AdminCollaborationsPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/admin/AdminCollaborationsPage.tsx"],"sourcesContent":["/**\n * Admin Collaborations Page\n * @module coolandlovely/pages/admin\n *\n * Curation surface for brand × creator collaborations. Lists collaborations that\n * can be advanced through their lifecycle via APPROVE/REJECT admin decisions.\n */\n\nimport { useState } from 'react';\nimport { Check, FileText, Handshake, X } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { ActorName } from '../../components/ActorName';\nimport { useCollaborations, useReviewCollaboration } from '../../hooks';\nimport { formatPrice, titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooCollaboration, CooCollaborationStatus } from '../../types';\n\nconst REVIEWABLE_STATUSES: CooCollaborationStatus[] = ['PROPOSED', 'ACCEPTED', 'IN_PROGRESS'];\n\nconst STATUS_OPTIONS: Array<{ value: CooCollaborationStatus | 'ALL'; label: string }> = [\n { value: 'ALL', label: 'All' },\n { value: 'PROPOSED', label: 'Proposed' },\n { value: 'ACCEPTED', label: 'Accepted' },\n { value: 'IN_PROGRESS', label: 'In progress' },\n];\n\nfunction CollaborationReviewRow({ collaboration }: { collaboration: CooCollaboration }) {\n const review = useReviewCollaboration();\n const [notes, setNotes] = useState('');\n const isReviewable = REVIEWABLE_STATUSES.includes(collaboration.status);\n const isBusy = review.isPending;\n\n return (\n <div className=\"rounded-lg border border-border-seam bg-bg-surface p-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">\n {collaboration.title}\n </h3>\n <StatusBadge status={collaboration.status} />\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {collaboration.brand?.name ?? 'Brand'} × <ActorName id={collaboration.creatorId} />\n </p>\n </div>\n </div>\n\n {collaboration.brief && (\n <p className=\"mt-3 line-clamp-2 text-sm text-text-secondary\">{collaboration.brief}</p>\n )}\n\n <div className=\"mt-3 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5\">\n {titleCase(collaboration.role)}\n </span>\n <span>{formatPrice(collaboration.compensation, collaboration.currency)}</span>\n </div>\n\n {isReviewable && (\n <div className=\"mt-4 space-y-3 border-t border-border-seam pt-3\">\n <div className=\"flex items-center gap-2\">\n <FileText className=\"h-4 w-4 text-text-secondary\" />\n <input\n type=\"text\"\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder=\"Optional review notes\"\n className=\"flex-1 rounded-md border border-border-seam bg-bg-surface px-3 py-1.5 text-sm text-text-primary placeholder:text-text-tertiary focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n </div>\n <div className=\"flex items-center justify-end gap-2\">\n <button\n type=\"button\"\n disabled={isBusy}\n onClick={() =>\n review.mutate({ id: collaboration.id, input: { decision: 'REJECT', notes } })\n }\n className={cn(\n 'inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium',\n 'bg-status-error-bg-subtle text-status-error-text hover:bg-status-error-bg-subtle/80',\n 'disabled:cursor-not-allowed disabled:opacity-50'\n )}\n >\n <X className=\"h-4 w-4\" /> Reject\n </button>\n <button\n type=\"button\"\n disabled={isBusy}\n onClick={() =>\n review.mutate({ id: collaboration.id, input: { decision: 'APPROVE', notes } })\n }\n className={cn(\n 'inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium',\n 'bg-status-success-bg-subtle text-status-success-text hover:bg-status-success-bg-subtle/80',\n 'disabled:cursor-not-allowed disabled:opacity-50'\n )}\n >\n <Check className=\"h-4 w-4\" /> Approve\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nexport function AdminCollaborationsPage() {\n const [status, setStatus] = useState<CooCollaborationStatus | 'ALL'>('ALL');\n const query = useCollaborations({ status: status === 'ALL' ? undefined : status });\n const collaborations = query.data ?? [];\n\n return (\n <PageLayout\n title=\"Admin — Collaborations\"\n description=\"Review and advance brand × creator partnerships\"\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n {STATUS_OPTIONS.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => setStatus(opt.value)}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n status === 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\n {query.isLoading ? (\n <LoadingView label=\"Loading collaborations…\" />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : collaborations.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Handshake className=\"h-10 w-10\" />}\n title=\"No collaborations to review\"\n description=\"There are no collaborations matching this filter.\"\n />\n ) : (\n <div className=\"space-y-3\">\n {collaborations.map((collab) => (\n <CollaborationReviewRow key={collab.id} collaboration={collab} />\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Featured Items Page
|
|
3
|
+
* @module coolandlovely/pages/admin
|
|
4
|
+
*
|
|
5
|
+
* Curation surface for featured style items. Lists the catalogue and lets admins
|
|
6
|
+
* feature or unfeature individual items for curated discovery surfaces.
|
|
7
|
+
*/
|
|
8
|
+
export declare function AdminFeaturedItemsPage(): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=AdminFeaturedItemsPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AdminFeaturedItemsPage.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/pages/admin/AdminFeaturedItemsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA+EH,wBAAgB,sBAAsB,gCAgGrC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
+
import { StatusBadge as n } from "../../components/StatusBadge.js";
|
|
4
|
+
import { ErrorView as r, LoadingView as i } from "../../components/StateViews.js";
|
|
5
|
+
import { Thumbnail as a } from "../../components/Thumbnail.js";
|
|
6
|
+
import { useFeatureStyleItem as o, useStyleItems as s, useUnfeatureStyleItem as c } from "../../hooks/useCoolAndLovelyApi.js";
|
|
7
|
+
import { formatPrice as l, titleCase as u } from "../../utils/formatters.js";
|
|
8
|
+
import "../../hooks/index.js";
|
|
9
|
+
import { STYLE_CATEGORIES as d, categoryIcon as f } from "../../utils/icons.js";
|
|
10
|
+
import { EmptyState as p } from "../../components/EmptyState.js";
|
|
11
|
+
import { useState as m } from "react";
|
|
12
|
+
import { Shirt as h, Star as g } from "lucide-react";
|
|
13
|
+
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
14
|
+
//#region src/coolandlovely/pages/admin/AdminFeaturedItemsPage.tsx
|
|
15
|
+
var b = 24;
|
|
16
|
+
function x({ item: t }) {
|
|
17
|
+
let r = o(), i = c(), s = r.isPending || i.isPending;
|
|
18
|
+
return /* @__PURE__ */ y("div", {
|
|
19
|
+
className: "flex items-center gap-4 rounded-lg border border-border-seam bg-bg-surface p-3",
|
|
20
|
+
children: [
|
|
21
|
+
/* @__PURE__ */ v(a, {
|
|
22
|
+
icon: f(t.category, 20),
|
|
23
|
+
seed: t.id,
|
|
24
|
+
fileId: t.image?.id,
|
|
25
|
+
alt: t.name,
|
|
26
|
+
className: "h-14 w-14 shrink-0 rounded-lg"
|
|
27
|
+
}),
|
|
28
|
+
/* @__PURE__ */ y("div", {
|
|
29
|
+
className: "min-w-0 flex-1",
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ y("div", {
|
|
32
|
+
className: "flex items-center gap-2",
|
|
33
|
+
children: [/* @__PURE__ */ v("h3", {
|
|
34
|
+
className: "truncate text-sm font-semibold text-text-primary",
|
|
35
|
+
children: t.name
|
|
36
|
+
}), t.isFeatured && /* @__PURE__ */ v(g, { className: "h-3.5 w-3.5 shrink-0 fill-status-warning-text text-status-warning-text" })]
|
|
37
|
+
}),
|
|
38
|
+
/* @__PURE__ */ y("p", {
|
|
39
|
+
className: "truncate text-xs text-text-secondary",
|
|
40
|
+
children: [
|
|
41
|
+
t.brand?.name ?? "Unknown brand",
|
|
42
|
+
" · ",
|
|
43
|
+
u(t.category)
|
|
44
|
+
]
|
|
45
|
+
}),
|
|
46
|
+
/* @__PURE__ */ v("p", {
|
|
47
|
+
className: "mt-0.5 text-sm font-medium text-text-primary",
|
|
48
|
+
children: l(t.price, t.currency)
|
|
49
|
+
})
|
|
50
|
+
]
|
|
51
|
+
}),
|
|
52
|
+
/* @__PURE__ */ y("div", {
|
|
53
|
+
className: "flex shrink-0 items-center gap-2",
|
|
54
|
+
children: [/* @__PURE__ */ v(n, { status: t.status }), t.isFeatured ? /* @__PURE__ */ v("button", {
|
|
55
|
+
type: "button",
|
|
56
|
+
disabled: s,
|
|
57
|
+
onClick: () => i.mutate(t.id),
|
|
58
|
+
className: e("inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium", "bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80", "disabled:cursor-not-allowed disabled:opacity-50"),
|
|
59
|
+
children: "Unfeature"
|
|
60
|
+
}) : /* @__PURE__ */ y("button", {
|
|
61
|
+
type: "button",
|
|
62
|
+
disabled: s,
|
|
63
|
+
onClick: () => r.mutate(t.id),
|
|
64
|
+
className: e("inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium", "bg-status-warning-bg-subtle text-status-warning-text hover:bg-status-warning-bg-subtle/80", "disabled:cursor-not-allowed disabled:opacity-50"),
|
|
65
|
+
children: [/* @__PURE__ */ v(g, { className: "h-4 w-4" }), " Feature"]
|
|
66
|
+
})]
|
|
67
|
+
})
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function S() {
|
|
72
|
+
let [n, a] = m("ALL"), [o, c] = m(0), l = s({
|
|
73
|
+
category: n === "ALL" ? void 0 : n,
|
|
74
|
+
pagination: {
|
|
75
|
+
limit: b,
|
|
76
|
+
offset: o
|
|
77
|
+
}
|
|
78
|
+
}), g = l.data?.items ?? [], S = l.data?.pageInfo.hasMore ?? !1;
|
|
79
|
+
return /* @__PURE__ */ y(t, {
|
|
80
|
+
title: "Admin — Featured Items",
|
|
81
|
+
description: "Curate style items for discovery surfaces",
|
|
82
|
+
children: [/* @__PURE__ */ y("div", {
|
|
83
|
+
className: "mb-6 flex flex-wrap items-center gap-2",
|
|
84
|
+
children: [/* @__PURE__ */ v("button", {
|
|
85
|
+
type: "button",
|
|
86
|
+
onClick: () => {
|
|
87
|
+
a("ALL"), c(0);
|
|
88
|
+
},
|
|
89
|
+
className: e("rounded-full px-3 py-1.5 text-xs font-medium transition-colors", n === "ALL" ? "bg-accent-purple-subtle text-text-primary" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
|
|
90
|
+
children: "All categories"
|
|
91
|
+
}), d.map((t) => /* @__PURE__ */ y("button", {
|
|
92
|
+
type: "button",
|
|
93
|
+
onClick: () => {
|
|
94
|
+
a(t), c(0);
|
|
95
|
+
},
|
|
96
|
+
className: e("inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium transition-colors", n === t ? "bg-accent-purple-subtle text-text-primary" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
|
|
97
|
+
children: [
|
|
98
|
+
f(t, 14),
|
|
99
|
+
" ",
|
|
100
|
+
u(t)
|
|
101
|
+
]
|
|
102
|
+
}, t))]
|
|
103
|
+
}), l.isLoading ? /* @__PURE__ */ v(i, { label: "Loading style items…" }) : l.isError ? /* @__PURE__ */ v(r, { onRetry: () => void l.refetch() }) : g.length === 0 ? /* @__PURE__ */ v(p, {
|
|
104
|
+
illustration: "empty-data",
|
|
105
|
+
icon: /* @__PURE__ */ v(h, { className: "h-10 w-10" }),
|
|
106
|
+
title: "No style items found",
|
|
107
|
+
description: "No items match this filter."
|
|
108
|
+
}) : /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("div", {
|
|
109
|
+
className: "space-y-3",
|
|
110
|
+
children: g.map((e) => /* @__PURE__ */ v(x, { item: e }, e.id))
|
|
111
|
+
}), (o > 0 || S) && /* @__PURE__ */ y("div", {
|
|
112
|
+
className: "mt-6 flex items-center justify-center gap-2",
|
|
113
|
+
children: [
|
|
114
|
+
/* @__PURE__ */ v("button", {
|
|
115
|
+
type: "button",
|
|
116
|
+
disabled: o === 0,
|
|
117
|
+
onClick: () => c((e) => Math.max(0, e - b)),
|
|
118
|
+
className: "inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40",
|
|
119
|
+
children: "Previous"
|
|
120
|
+
}),
|
|
121
|
+
/* @__PURE__ */ y("span", {
|
|
122
|
+
className: "text-sm text-text-secondary",
|
|
123
|
+
children: ["Page ", o / b + 1]
|
|
124
|
+
}),
|
|
125
|
+
/* @__PURE__ */ v("button", {
|
|
126
|
+
type: "button",
|
|
127
|
+
disabled: !S,
|
|
128
|
+
onClick: () => c((e) => e + b),
|
|
129
|
+
className: "inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40",
|
|
130
|
+
children: "Next"
|
|
131
|
+
})
|
|
132
|
+
]
|
|
133
|
+
})] })]
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { S as AdminFeaturedItemsPage };
|
|
138
|
+
|
|
139
|
+
//# sourceMappingURL=AdminFeaturedItemsPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AdminFeaturedItemsPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/admin/AdminFeaturedItemsPage.tsx"],"sourcesContent":["/**\n * Admin Featured Items Page\n * @module coolandlovely/pages/admin\n *\n * Curation surface for featured style items. Lists the catalogue and lets admins\n * feature or unfeature individual items for curated discovery surfaces.\n */\n\nimport { useState } from 'react';\nimport { Shirt, Star } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { useStyleItems, useFeatureStyleItem, useUnfeatureStyleItem } from '../../hooks';\nimport { categoryIcon, STYLE_CATEGORIES } from '../../utils/icons';\nimport { formatPrice, titleCase } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { CooStyleCategory, CooStyleItem } from '../../types';\n\nconst PAGE_SIZE = 24;\n\nfunction FeaturedItemRow({ item }: { item: CooStyleItem }) {\n const feature = useFeatureStyleItem();\n const unfeature = useUnfeatureStyleItem();\n const isBusy = feature.isPending || unfeature.isPending;\n\n return (\n <div className=\"flex items-center gap-4 rounded-lg border border-border-seam bg-bg-surface p-3\">\n <Thumbnail\n icon={categoryIcon(item.category, 20)}\n seed={item.id}\n fileId={item.image?.id}\n alt={item.name}\n className=\"h-14 w-14 shrink-0 rounded-lg\"\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">{item.name}</h3>\n {item.isFeatured && (\n <Star className=\"h-3.5 w-3.5 shrink-0 fill-status-warning-text text-status-warning-text\" />\n )}\n </div>\n <p className=\"truncate text-xs text-text-secondary\">\n {item.brand?.name ?? 'Unknown brand'} · {titleCase(item.category)}\n </p>\n <p className=\"mt-0.5 text-sm font-medium text-text-primary\">\n {formatPrice(item.price, item.currency)}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <StatusBadge status={item.status} />\n {item.isFeatured ? (\n <button\n type=\"button\"\n disabled={isBusy}\n onClick={() => unfeature.mutate(item.id)}\n className={cn(\n 'inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium',\n 'bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80',\n 'disabled:cursor-not-allowed disabled:opacity-50'\n )}\n >\n Unfeature\n </button>\n ) : (\n <button\n type=\"button\"\n disabled={isBusy}\n onClick={() => feature.mutate(item.id)}\n className={cn(\n 'inline-flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium',\n 'bg-status-warning-bg-subtle text-status-warning-text hover:bg-status-warning-bg-subtle/80',\n 'disabled:cursor-not-allowed disabled:opacity-50'\n )}\n >\n <Star className=\"h-4 w-4\" /> Feature\n </button>\n )}\n </div>\n </div>\n );\n}\n\nexport function AdminFeaturedItemsPage() {\n const [category, setCategory] = useState<CooStyleCategory | 'ALL'>('ALL');\n const [offset, setOffset] = useState(0);\n\n const query = useStyleItems({\n category: category === 'ALL' ? undefined : category,\n pagination: { limit: PAGE_SIZE, offset },\n });\n const items = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n return (\n <PageLayout\n title=\"Admin — Featured Items\"\n description=\"Curate style items for discovery surfaces\"\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setCategory('ALL');\n setOffset(0);\n }}\n className={cn(\n 'rounded-full px-3 py-1.5 text-xs font-medium transition-colors',\n category === 'ALL'\n ? 'bg-accent-purple-subtle text-text-primary'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n All categories\n </button>\n {STYLE_CATEGORIES.map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => {\n setCategory(c);\n setOffset(0);\n }}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium transition-colors',\n category === c\n ? 'bg-accent-purple-subtle text-text-primary'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {categoryIcon(c, 14)} {titleCase(c)}\n </button>\n ))}\n </div>\n\n {query.isLoading ? (\n <LoadingView label=\"Loading style items…\" />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Shirt className=\"h-10 w-10\" />}\n title=\"No style items found\"\n description=\"No items match this filter.\"\n />\n ) : (\n <>\n <div className=\"space-y-3\">\n {items.map((item) => (\n <FeaturedItemRow key={item.id} item={item} />\n ))}\n </div>\n\n {(offset > 0 || hasMore) && (\n <div className=\"mt-6 flex items-center justify-center gap-2\">\n <button\n type=\"button\"\n disabled={offset === 0}\n onClick={() => setOffset((o) => Math.max(0, o - PAGE_SIZE))}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n Previous\n </button>\n <span className=\"text-sm text-text-secondary\">Page {offset / PAGE_SIZE + 1}</span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setOffset((o) => o + PAGE_SIZE)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n Next\n </button>\n </div>\n )}\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin pages index
|
|
3
|
+
* @module coolandlovely/pages/admin
|
|
4
|
+
*/
|
|
5
|
+
export { AdminBrandsPage } from './AdminBrandsPage';
|
|
6
|
+
export { AdminCollaborationsPage } from './AdminCollaborationsPage';
|
|
7
|
+
export { AdminFeaturedItemsPage } from './AdminFeaturedItemsPage';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/pages/admin/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* Discover Page
|
|
3
3
|
* @module coolandlovely/pages/discover
|
|
4
4
|
*
|
|
5
|
-
* A profile-tuned discovery feed
|
|
6
|
-
* catalogue
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* — no fabricated recommendations, no black-box "AI"
|
|
10
|
-
*
|
|
5
|
+
* A profile-tuned discovery feed plus a first-class search experience over the
|
|
6
|
+
* catalogue (`cooSearchStyleItems`) and published looks (`cooSearchLooks`).
|
|
7
|
+
*
|
|
8
|
+
* The "For you" tab ranks the real catalogue + social lists against the
|
|
9
|
+
* caller's style profile — no fabricated recommendations, no black-box "AI"
|
|
10
|
+
* (that is roadmap). The "Items" / "Looks" tabs drive server-side search with
|
|
11
|
+
* filters and pagination.
|
|
11
12
|
*/
|
|
12
13
|
export declare function DiscoverPage(): import("react").JSX.Element;
|
|
13
14
|
//# sourceMappingURL=DiscoverPage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DiscoverPage.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/pages/discover/DiscoverPage.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"DiscoverPage.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/pages/discover/DiscoverPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA4GH,wBAAgB,YAAY,gCA4jB3B"}
|