@burdenoff/microfe-ggnomad 2026.1029.2 → 2026.1030.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.
@@ -11,6 +11,12 @@ interface AuditTrailDialogProps {
11
11
  * by GgnomadAuditLog. Every listing-verification and booking-lifecycle
12
12
  * mutation writes one row here; this dialog is the operator-facing view of
13
13
  * that append-only trail.
14
+ *
15
+ * BOFF-10248 — `ggnomadAuditLog` answers only for the people involved in the
16
+ * entity (a booking's traveler or host, a listing's owner). Marketplace
17
+ * operators (`ggnomad:manage`) read any entity's history through
18
+ * `ggnomadWorkspaceAuditLog`, so the dialog picks the field from the caller's
19
+ * workspace permissions. The gateway still enforces both fields.
14
20
  */
15
21
  export declare function AuditTrailDialog({ open, onOpenChange, entityType, entityId, label, }: AuditTrailDialogProps): import("react").JSX.Element;
16
22
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"AuditTrailDialog.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/AuditTrailDialog.tsx"],"names":[],"mappings":"AA2BA,UAAU,qBAAqB;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;CACf;AAUD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,KAAK,GACN,EAAE,qBAAqB,+BAuFvB"}
1
+ {"version":3,"file":"AuditTrailDialog.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/AuditTrailDialog.tsx"],"names":[],"mappings":"AAmCA,UAAU,qBAAqB;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;CACf;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,KAAK,GACN,EAAE,qBAAqB,+BA2GvB"}
@@ -1,59 +1,64 @@
1
- import { GGNOMAD_AUDIT_LOG_QUERY as e } from "../graphql.js";
2
- import { History as t } from "lucide-react";
3
- import { useI18n as n } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
- import { useQuery as r } from "@apollo/client/react";
5
- import { AlertDialog as i, AlertDialogCancel as a, AlertDialogContent as o, AlertDialogDescription as s, AlertDialogFooter as c, AlertDialogHeader as l, AlertDialogTitle as u, IllustratedEmptyState as d } from "@burdenoff/fe-libs/ui";
6
- import { jsx as f, jsxs as p } from "react/jsx-runtime";
1
+ import { GGNOMAD_AUDIT_LOG_QUERY as e, GGNOMAD_WORKSPACE_AUDIT_LOG_QUERY as t } from "../graphql.js";
2
+ import { History as n } from "lucide-react";
3
+ import { useI18n as r } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
+ import { useQuery as i } from "@apollo/client/react";
5
+ import { AlertDialog as a, AlertDialogCancel as o, AlertDialogContent as s, AlertDialogDescription as c, AlertDialogFooter as l, AlertDialogHeader as u, AlertDialogTitle as d, IllustratedEmptyState as f } from "@burdenoff/fe-libs/ui";
6
+ import { jsx as p, jsxs as m } from "react/jsx-runtime";
7
+ import { useSafePermissions as h } from "@burdenoff/fe-libs/shared/providers/shell";
7
8
  //#region src/ggnomad/components/AuditTrailDialog.tsx
8
- function m(e) {
9
+ function g(e) {
9
10
  return e.toLowerCase().split("_").map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(" ");
10
11
  }
11
- function h({ open: h, onOpenChange: g, entityType: _, entityId: v, label: y }) {
12
- let { t: b } = n(), { data: x, loading: S } = r(e, {
13
- variables: {
14
- entityType: _,
15
- entityId: v,
16
- limit: 50,
17
- offset: 0
18
- },
19
- skip: !h,
12
+ function _({ open: _, onOpenChange: v, entityType: y, entityId: b, label: x }) {
13
+ let { t: S } = r(), C = h(), w = C?.isLoading ?? !1, T = !w && (C?.hasPermission("ggnomad:manage") ?? !1), E = {
14
+ entityType: y,
15
+ entityId: b,
16
+ limit: 50,
17
+ offset: 0
18
+ }, D = i(e, {
19
+ variables: E,
20
+ skip: !_ || w || T,
20
21
  fetchPolicy: "network-only"
21
- }), C = x?.ggnomadAuditLog ?? [];
22
- return /* @__PURE__ */ f(i, {
23
- open: h,
24
- onOpenChange: g,
25
- children: /* @__PURE__ */ p(o, {
22
+ }), O = i(t, {
23
+ variables: E,
24
+ skip: !_ || !T,
25
+ fetchPolicy: "network-only"
26
+ }), k = w || (T ? O.loading : D.loading), A = (T ? O.data?.ggnomadWorkspaceAuditLog : D.data?.ggnomadAuditLog) ?? [];
27
+ return /* @__PURE__ */ p(a, {
28
+ open: _,
29
+ onOpenChange: v,
30
+ children: /* @__PURE__ */ m(s, {
26
31
  className: "max-w-lg",
27
32
  children: [
28
- /* @__PURE__ */ p(l, { children: [/* @__PURE__ */ p(u, {
33
+ /* @__PURE__ */ m(u, { children: [/* @__PURE__ */ m(d, {
29
34
  className: "flex items-center gap-2",
30
- children: [/* @__PURE__ */ f(t, { className: "size-4" }), b("ggnomad.auditTrail.title", "Transition history")]
31
- }), /* @__PURE__ */ f(s, { children: b("ggnomad.auditTrail.description", "Every verification or lifecycle change for {{label}}.").replace("{{label}}", y) })] }),
32
- /* @__PURE__ */ f("div", {
35
+ children: [/* @__PURE__ */ p(n, { className: "size-4" }), S("ggnomad.auditTrail.title", "Transition history")]
36
+ }), /* @__PURE__ */ p(c, { children: S("ggnomad.auditTrail.description", "Every verification or lifecycle change for {{label}}.").replace("{{label}}", x) })] }),
37
+ /* @__PURE__ */ p("div", {
33
38
  className: "max-h-[60vh] space-y-2 overflow-y-auto",
34
- children: S ? /* @__PURE__ */ f("p", {
39
+ children: k ? /* @__PURE__ */ p("p", {
35
40
  className: "py-6 text-center text-sm text-text-secondary",
36
- children: b("ggnomad.auditTrail.loading", "Loading history…")
37
- }) : C.length === 0 ? /* @__PURE__ */ f(d, {
41
+ children: S("ggnomad.auditTrail.loading", "Loading history…")
42
+ }) : A.length === 0 ? /* @__PURE__ */ p(f, {
38
43
  illustration: "empty-data",
39
- title: b("ggnomad.auditTrail.emptyTitle", "No transitions recorded yet"),
40
- description: b("ggnomad.auditTrail.emptyDescription", "Approvals, rejections, check-ins and other lifecycle changes will show up here.")
41
- }) : /* @__PURE__ */ f("ol", {
44
+ title: S("ggnomad.auditTrail.emptyTitle", "No transitions recorded yet"),
45
+ description: S("ggnomad.auditTrail.emptyDescription", "Approvals, rejections, check-ins and other lifecycle changes will show up here.")
46
+ }) : /* @__PURE__ */ p("ol", {
42
47
  className: "space-y-2",
43
- children: C.map((e) => /* @__PURE__ */ p("li", {
48
+ children: A.map((e) => /* @__PURE__ */ m("li", {
44
49
  className: "rounded-lg border border-border-seam bg-bg-surface/40 p-3 text-sm",
45
50
  children: [
46
- /* @__PURE__ */ p("div", {
51
+ /* @__PURE__ */ m("div", {
47
52
  className: "flex items-center justify-between gap-2",
48
- children: [/* @__PURE__ */ f("span", {
53
+ children: [/* @__PURE__ */ p("span", {
49
54
  className: "font-medium text-text-primary",
50
- children: m(e.action)
51
- }), /* @__PURE__ */ f("span", {
55
+ children: g(e.action)
56
+ }), /* @__PURE__ */ p("span", {
52
57
  className: "text-xs tabular-nums text-text-secondary",
53
58
  children: new Date(e.createdAt).toLocaleString()
54
59
  })]
55
60
  }),
56
- e.fromStatus || e.toStatus ? /* @__PURE__ */ p("p", {
61
+ e.fromStatus || e.toStatus ? /* @__PURE__ */ m("p", {
57
62
  className: "mt-1 text-xs text-text-secondary",
58
63
  children: [
59
64
  e.fromStatus ?? "—",
@@ -61,7 +66,7 @@ function h({ open: h, onOpenChange: g, entityType: _, entityId: v, label: y }) {
61
66
  e.toStatus ?? "—"
62
67
  ]
63
68
  }) : null,
64
- e.reason ? /* @__PURE__ */ p("p", {
69
+ e.reason ? /* @__PURE__ */ m("p", {
65
70
  className: "mt-1 text-xs italic text-text-secondary",
66
71
  children: [
67
72
  "“",
@@ -69,20 +74,20 @@ function h({ open: h, onOpenChange: g, entityType: _, entityId: v, label: y }) {
69
74
  "”"
70
75
  ]
71
76
  }) : null,
72
- /* @__PURE__ */ f("p", {
77
+ /* @__PURE__ */ p("p", {
73
78
  className: "mt-1 text-xs text-text-secondary",
74
- children: b("ggnomad.auditTrail.by", "by {{actor}}").replace("{{actor}}", e.actorId)
79
+ children: S("ggnomad.auditTrail.by", "by {{actor}}").replace("{{actor}}", e.actorId)
75
80
  })
76
81
  ]
77
82
  }, e.id))
78
83
  })
79
84
  }),
80
- /* @__PURE__ */ f(c, { children: /* @__PURE__ */ f(a, { children: b("ggnomad.auditTrail.close", "Close") }) })
85
+ /* @__PURE__ */ p(l, { children: /* @__PURE__ */ p(o, { children: S("ggnomad.auditTrail.close", "Close") }) })
81
86
  ]
82
87
  })
83
88
  });
84
89
  }
85
90
  //#endregion
86
- export { h as AuditTrailDialog };
91
+ export { _ as AuditTrailDialog };
87
92
 
88
93
  //# sourceMappingURL=AuditTrailDialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AuditTrailDialog.js","names":[],"sources":["../../../src/ggnomad/components/AuditTrailDialog.tsx"],"sourcesContent":["import { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { History } from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_AUDIT_LOG_QUERY } from '../graphql';\n\ninterface AuditLogEntry {\n id: string;\n action: string;\n fromStatus: string | null;\n toStatus: string | null;\n reason: string | null;\n actorId: string;\n createdAt: string;\n}\n\ninterface AuditTrailDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n entityType: string;\n entityId: string;\n /** Shown in the dialog title, e.g. a reference code or listing name. */\n label: string;\n}\n\nfunction formatAction(action: string): string {\n return action\n .toLowerCase()\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n/**\n * BOFF-4388 — read-only transition history for one listing or booking, backed\n * by GgnomadAuditLog. Every listing-verification and booking-lifecycle\n * mutation writes one row here; this dialog is the operator-facing view of\n * that append-only trail.\n */\nexport function AuditTrailDialog({\n open,\n onOpenChange,\n entityType,\n entityId,\n label,\n}: AuditTrailDialogProps) {\n const { t } = useI18n();\n\n const { data, loading } = useQuery(\n GGNOMAD_AUDIT_LOG_QUERY as TypedDocumentNode<\n { ggnomadAuditLog: AuditLogEntry[] },\n { entityType: string; entityId: string; limit?: number; offset?: number }\n >,\n {\n variables: { entityType, entityId, limit: 50, offset: 0 },\n skip: !open,\n fetchPolicy: 'network-only',\n }\n );\n\n const entries = data?.ggnomadAuditLog ?? [];\n\n return (\n <AlertDialog open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent className=\"max-w-lg\">\n <AlertDialogHeader>\n <AlertDialogTitle className=\"flex items-center gap-2\">\n <History className=\"size-4\" />\n {t('ggnomad.auditTrail.title', 'Transition history')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {t(\n 'ggnomad.auditTrail.description',\n 'Every verification or lifecycle change for {{label}}.'\n ).replace('{{label}}', label)}\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n <div className=\"max-h-[60vh] space-y-2 overflow-y-auto\">\n {loading ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {t('ggnomad.auditTrail.loading', 'Loading history…')}\n </p>\n ) : entries.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.auditTrail.emptyTitle', 'No transitions recorded yet')}\n description={t(\n 'ggnomad.auditTrail.emptyDescription',\n 'Approvals, rejections, check-ins and other lifecycle changes will show up here.'\n )}\n />\n ) : (\n <ol className=\"space-y-2\">\n {entries.map((entry) => (\n <li\n key={entry.id}\n className=\"rounded-lg border border-border-seam bg-bg-surface/40 p-3 text-sm\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-medium text-text-primary\">\n {formatAction(entry.action)}\n </span>\n <span className=\"text-xs tabular-nums text-text-secondary\">\n {new Date(entry.createdAt).toLocaleString()}\n </span>\n </div>\n {entry.fromStatus || entry.toStatus ? (\n <p className=\"mt-1 text-xs text-text-secondary\">\n {entry.fromStatus ?? '—'} → {entry.toStatus ?? '—'}\n </p>\n ) : null}\n {entry.reason ? (\n <p className=\"mt-1 text-xs italic text-text-secondary\">\n &ldquo;{entry.reason}&rdquo;\n </p>\n ) : null}\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('ggnomad.auditTrail.by', 'by {{actor}}').replace('{{actor}}', entry.actorId)}\n </p>\n </li>\n ))}\n </ol>\n )}\n </div>\n\n <AlertDialogFooter>\n <AlertDialogCancel>{t('ggnomad.auditTrail.close', 'Close')}</AlertDialogCancel>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"],"mappings":";;;;;;;AAoCA,SAAS,EAAa,GAAwB;CAC5C,OAAO,EACJ,YAAY,CAAC,CACb,MAAM,GAAG,CAAC,CACV,KAAK,MAAS,EAAK,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,EAAK,MAAM,CAAC,CAAC,CAAC,CAC3D,KAAK,GAAG;AACb;AAQA,SAAgB,EAAiB,EAC/B,SACA,iBACA,eACA,aACA,YACwB;CACxB,IAAM,EAAE,SAAM,EAAQ,GAEhB,EAAE,SAAM,eAAY,EACxB,GAIA;EACE,WAAW;GAAE;GAAY;GAAU,OAAO;GAAI,QAAQ;EAAE;EACxD,MAAM,CAAC;EACP,aAAa;CACf,CACF,GAEM,IAAU,GAAM,mBAAmB,CAAC;CAE1C,OACE,kBAAC,GAAD;EAAmB;EAAoB;YACrC,kBAAC,GAAD;GAAoB,WAAU;aAA9B;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAkB,WAAU;eAA5B,CACE,kBAAC,GAAD,EAAS,WAAU,SAAU,CAAA,GAC5B,EAAE,4BAA4B,oBAAoB,CACnC;QAClB,kBAAC,GAAD,EAAA,UACG,EACC,kCACA,uDACF,CAAC,CAAC,QAAQ,aAAa,CAAK,EACN,CAAA,CACP,EAAA,CAAA;IAEnB,kBAAC,OAAD;KAAK,WAAU;eACZ,IACC,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAE,8BAA8B,kBAAkB;KAClD,CAAA,IACD,EAAQ,WAAW,IACrB,kBAAC,GAAD;MACE,cAAa;MACb,OAAO,EAAE,iCAAiC,6BAA6B;MACvE,aAAa,EACX,uCACA,iFACF;KACD,CAAA,IAED,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAQ,KAAK,MACZ,kBAAC,MAAD;OAEE,WAAU;iBAFZ;QAIE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAa,EAAM,MAAM;SACtB,CAAA,GACN,kBAAC,QAAD;UAAM,WAAU;oBACb,IAAI,KAAK,EAAM,SAAS,CAAC,CAAC,eAAe;SACtC,CAAA,CACH;;QACJ,EAAM,cAAc,EAAM,WACzB,kBAAC,KAAD;SAAG,WAAU;mBAAb;UACG,EAAM,cAAc;UAAI;UAAI,EAAM,YAAY;SAC9C;aACD;QACH,EAAM,SACL,kBAAC,KAAD;SAAG,WAAU;mBAAb;UAAuD;UAC7C,EAAM;UAAO;SACpB;aACD;QACJ,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAE,yBAAyB,cAAc,CAAC,CAAC,QAAQ,aAAa,EAAM,OAAO;QAC7E,CAAA;OACD;SAxBG,EAAM,EAwBT,CACL;KACC,CAAA;IAEH,CAAA;IAEL,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAoB,EAAE,4BAA4B,OAAO,EAAqB,CAAA,EAC7D,CAAA;GACD;;CACT,CAAA;AAEjB"}
1
+ {"version":3,"file":"AuditTrailDialog.js","names":[],"sources":["../../../src/ggnomad/components/AuditTrailDialog.tsx"],"sourcesContent":["import { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useSafePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { History } from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_AUDIT_LOG_QUERY, GGNOMAD_WORKSPACE_AUDIT_LOG_QUERY } from '../graphql';\n\ninterface AuditLogEntry {\n id: string;\n action: string;\n fromStatus: string | null;\n toStatus: string | null;\n reason: string | null;\n actorId: string;\n createdAt: string;\n}\n\ninterface AuditLogVariables {\n entityType: string;\n entityId: string;\n limit?: number;\n offset?: number;\n}\n\ninterface AuditTrailDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n entityType: string;\n entityId: string;\n /** Shown in the dialog title, e.g. a reference code or listing name. */\n label: string;\n}\n\nfunction formatAction(action: string): string {\n return action\n .toLowerCase()\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n/**\n * BOFF-4388 — read-only transition history for one listing or booking, backed\n * by GgnomadAuditLog. Every listing-verification and booking-lifecycle\n * mutation writes one row here; this dialog is the operator-facing view of\n * that append-only trail.\n *\n * BOFF-10248 — `ggnomadAuditLog` answers only for the people involved in the\n * entity (a booking's traveler or host, a listing's owner). Marketplace\n * operators (`ggnomad:manage`) read any entity's history through\n * `ggnomadWorkspaceAuditLog`, so the dialog picks the field from the caller's\n * workspace permissions. The gateway still enforces both fields.\n */\nexport function AuditTrailDialog({\n open,\n onOpenChange,\n entityType,\n entityId,\n label,\n}: AuditTrailDialogProps) {\n const { t } = useI18n();\n const permissions = useSafePermissions();\n const permissionsLoading = permissions?.isLoading ?? false;\n const isOperator = !permissionsLoading && (permissions?.hasPermission('ggnomad:manage') ?? false);\n const variables: AuditLogVariables = { entityType, entityId, limit: 50, offset: 0 };\n\n const actorHistory = useQuery(\n GGNOMAD_AUDIT_LOG_QUERY as TypedDocumentNode<\n { ggnomadAuditLog: AuditLogEntry[] },\n AuditLogVariables\n >,\n {\n variables,\n skip: !open || permissionsLoading || isOperator,\n fetchPolicy: 'network-only',\n }\n );\n const workspaceHistory = useQuery(\n GGNOMAD_WORKSPACE_AUDIT_LOG_QUERY as TypedDocumentNode<\n { ggnomadWorkspaceAuditLog: AuditLogEntry[] },\n AuditLogVariables\n >,\n {\n variables,\n skip: !open || !isOperator,\n fetchPolicy: 'network-only',\n }\n );\n\n const loading =\n permissionsLoading || (isOperator ? workspaceHistory.loading : actorHistory.loading);\n const entries =\n (isOperator\n ? workspaceHistory.data?.ggnomadWorkspaceAuditLog\n : actorHistory.data?.ggnomadAuditLog) ?? [];\n\n return (\n <AlertDialog open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent className=\"max-w-lg\">\n <AlertDialogHeader>\n <AlertDialogTitle className=\"flex items-center gap-2\">\n <History className=\"size-4\" />\n {t('ggnomad.auditTrail.title', 'Transition history')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {t(\n 'ggnomad.auditTrail.description',\n 'Every verification or lifecycle change for {{label}}.'\n ).replace('{{label}}', label)}\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n <div className=\"max-h-[60vh] space-y-2 overflow-y-auto\">\n {loading ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {t('ggnomad.auditTrail.loading', 'Loading history…')}\n </p>\n ) : entries.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.auditTrail.emptyTitle', 'No transitions recorded yet')}\n description={t(\n 'ggnomad.auditTrail.emptyDescription',\n 'Approvals, rejections, check-ins and other lifecycle changes will show up here.'\n )}\n />\n ) : (\n <ol className=\"space-y-2\">\n {entries.map((entry) => (\n <li\n key={entry.id}\n className=\"rounded-lg border border-border-seam bg-bg-surface/40 p-3 text-sm\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-medium text-text-primary\">\n {formatAction(entry.action)}\n </span>\n <span className=\"text-xs tabular-nums text-text-secondary\">\n {new Date(entry.createdAt).toLocaleString()}\n </span>\n </div>\n {entry.fromStatus || entry.toStatus ? (\n <p className=\"mt-1 text-xs text-text-secondary\">\n {entry.fromStatus ?? '—'} → {entry.toStatus ?? '—'}\n </p>\n ) : null}\n {entry.reason ? (\n <p className=\"mt-1 text-xs italic text-text-secondary\">\n &ldquo;{entry.reason}&rdquo;\n </p>\n ) : null}\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('ggnomad.auditTrail.by', 'by {{actor}}').replace('{{actor}}', entry.actorId)}\n </p>\n </li>\n ))}\n </ol>\n )}\n </div>\n\n <AlertDialogFooter>\n <AlertDialogCancel>{t('ggnomad.auditTrail.close', 'Close')}</AlertDialogCancel>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"],"mappings":";;;;;;;;AA4CA,SAAS,EAAa,GAAwB;CAC5C,OAAO,EACJ,YAAY,CAAC,CACb,MAAM,GAAG,CAAC,CACV,KAAK,MAAS,EAAK,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,EAAK,MAAM,CAAC,CAAC,CAAC,CAC3D,KAAK,GAAG;AACb;AAcA,SAAgB,EAAiB,EAC/B,SACA,iBACA,eACA,aACA,YACwB;CACxB,IAAM,EAAE,SAAM,EAAQ,GAChB,IAAc,EAAmB,GACjC,IAAqB,GAAa,aAAa,IAC/C,IAAa,CAAC,MAAuB,GAAa,cAAc,gBAAgB,KAAK,KACrF,IAA+B;EAAE;EAAY;EAAU,OAAO;EAAI,QAAQ;CAAE,GAE5E,IAAe,EACnB,GAIA;EACE;EACA,MAAM,CAAC,KAAQ,KAAsB;EACrC,aAAa;CACf,CACF,GACM,IAAmB,EACvB,GAIA;EACE;EACA,MAAM,CAAC,KAAQ,CAAC;EAChB,aAAa;CACf,CACF,GAEM,IACJ,MAAuB,IAAa,EAAiB,UAAU,EAAa,UACxE,KACH,IACG,EAAiB,MAAM,2BACvB,EAAa,MAAM,oBAAoB,CAAC;CAE9C,OACE,kBAAC,GAAD;EAAmB;EAAoB;YACrC,kBAAC,GAAD;GAAoB,WAAU;aAA9B;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAkB,WAAU;eAA5B,CACE,kBAAC,GAAD,EAAS,WAAU,SAAU,CAAA,GAC5B,EAAE,4BAA4B,oBAAoB,CACnC;QAClB,kBAAC,GAAD,EAAA,UACG,EACC,kCACA,uDACF,CAAC,CAAC,QAAQ,aAAa,CAAK,EACN,CAAA,CACP,EAAA,CAAA;IAEnB,kBAAC,OAAD;KAAK,WAAU;eACZ,IACC,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAE,8BAA8B,kBAAkB;KAClD,CAAA,IACD,EAAQ,WAAW,IACrB,kBAAC,GAAD;MACE,cAAa;MACb,OAAO,EAAE,iCAAiC,6BAA6B;MACvE,aAAa,EACX,uCACA,iFACF;KACD,CAAA,IAED,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAQ,KAAK,MACZ,kBAAC,MAAD;OAEE,WAAU;iBAFZ;QAIE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAa,EAAM,MAAM;SACtB,CAAA,GACN,kBAAC,QAAD;UAAM,WAAU;oBACb,IAAI,KAAK,EAAM,SAAS,CAAC,CAAC,eAAe;SACtC,CAAA,CACH;;QACJ,EAAM,cAAc,EAAM,WACzB,kBAAC,KAAD;SAAG,WAAU;mBAAb;UACG,EAAM,cAAc;UAAI;UAAI,EAAM,YAAY;SAC9C;aACD;QACH,EAAM,SACL,kBAAC,KAAD;SAAG,WAAU;mBAAb;UAAuD;UAC7C,EAAM;UAAO;SACpB;aACD;QACJ,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAE,yBAAyB,cAAc,CAAC,CAAC,QAAQ,aAAa,EAAM,OAAO;QAC7E,CAAA;OACD;SAxBG,EAAM,EAwBT,CACL;KACC,CAAA;IAEH,CAAA;IAEL,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAoB,EAAE,4BAA4B,OAAO,EAAqB,CAAA,EAC7D,CAAA;GACD;;CACT,CAAA;AAEjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"ListingEditDialog.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/ListingEditDialog.tsx"],"names":[],"mappings":"AAwBA,KAAK,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAE7D,UAAU,sBAAsB;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,UAAU,EAAE,UAAU,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE;QACP,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,EAAE,EACF,OAAO,EACP,OAAO,GACR,EAAE,sBAAsB,+BAgTxB"}
1
+ {"version":3,"file":"ListingEditDialog.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/ListingEditDialog.tsx"],"names":[],"mappings":"AA2BA,KAAK,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAoB7D,UAAU,sBAAsB;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,UAAU,EAAE,UAAU,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE;QACP,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,EAAE,EACF,OAAO,EACP,OAAO,GACR,EAAE,sBAAsB,+BAoXxB"}
@@ -1,161 +1,195 @@
1
- import { UPDATE_GGNOMAD_ACTIVITY_MUTATION as e, UPDATE_GGNOMAD_EVENT_MUTATION as t, UPDATE_GGNOMAD_PROPERTY_MUTATION as n, UPDATE_GGNOMAD_TOUR_MUTATION as r } from "../graphql.js";
2
- import { notify as i } from "../utils/notify.js";
3
- import { useMemo as a, useState as o } from "react";
4
- import { Pencil as s } from "lucide-react";
5
- import { useI18n as c } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
6
- import { useMutation as l } from "@apollo/client/react";
7
- import { AlertDialog as u, AlertDialogAction as d, AlertDialogCancel as f, AlertDialogContent as p, AlertDialogDescription as m, AlertDialogFooter as h, AlertDialogHeader as g, AlertDialogTitle as _ } from "@burdenoff/fe-libs/ui";
8
- import { jsx as v, jsxs as y } from "react/jsx-runtime";
1
+ import { PAUSE_GGNOMAD_LISTING_MUTATION as e, RESUME_GGNOMAD_LISTING_MUTATION as t, SUBMIT_GGNOMAD_LISTING_FOR_REVIEW_MUTATION as n, UPDATE_GGNOMAD_ACTIVITY_MUTATION as r, UPDATE_GGNOMAD_EVENT_MUTATION as i, UPDATE_GGNOMAD_PROPERTY_MUTATION as a, UPDATE_GGNOMAD_TOUR_MUTATION as o } from "../graphql.js";
2
+ import { notify as s } from "../utils/notify.js";
3
+ import { useMemo as c, useState as l } from "react";
4
+ import { Pencil as u } from "lucide-react";
5
+ import { useI18n as d } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
6
+ import { useMutation as f } from "@apollo/client/react";
7
+ import { AlertDialog as p, AlertDialogAction as m, AlertDialogCancel as ee, AlertDialogContent as h, AlertDialogDescription as g, AlertDialogFooter as _, AlertDialogHeader as v, AlertDialogTitle as te } from "@burdenoff/fe-libs/ui";
8
+ import { jsx as y, jsxs as b } from "react/jsx-runtime";
9
9
  //#region src/ggnomad/components/ListingEditDialog.tsx
10
- function b({ open: b, onOpenChange: x, targetType: S, id: C, initial: w, onSaved: T }) {
11
- let { t: E } = c(), [D, O] = o(w.name ?? ""), [k, A] = o(w.description ?? ""), j = w.pricePerNight ?? w.price ?? 0, [M, N] = o(String(j)), [P, F] = o(w.status ?? "ACTIVE"), [I, L] = o((w.images ?? []).join("\n")), [R, { loading: z }] = l(n), [B, { loading: V }] = l(r), [H, { loading: U }] = l(t), [W, { loading: G }] = l(e), K = a(() => z || V || U || G, [
12
- z,
10
+ var x = {
11
+ DRAFT: "SUBMIT",
12
+ ACTIVE: "PAUSE",
13
+ PAUSED: "RESUME"
14
+ };
15
+ function S({ open: S, onOpenChange: C, targetType: w, id: T, initial: E, onSaved: D }) {
16
+ let { t: O } = d(), [k, A] = l(E.name ?? ""), [j, M] = l(E.description ?? ""), N = E.pricePerNight ?? E.price ?? 0, [P, F] = l(String(N)), I = (E.status ?? "").toUpperCase(), L = x[I], [R, z] = l((E.images ?? []).join("\n")), [B, { loading: V }] = f(a), [H, { loading: U }] = f(o), [W, { loading: G }] = f(i), [K, { loading: q }] = f(r), [J, { loading: Y }] = f(n), [X, { loading: Z }] = f(e), [ne, { loading: Q }] = f(t), $ = c(() => V || U || G || q || Y || Z || Q, [
13
17
  V,
14
18
  U,
15
- G
16
- ]), q = async () => {
19
+ G,
20
+ q,
21
+ Y,
22
+ Z,
23
+ Q
24
+ ]), re = async (e) => {
25
+ let t = e === "SUBMIT" ? J : e === "PAUSE" ? X : ne;
26
+ try {
27
+ let n = await t({ variables: {
28
+ listingType: w,
29
+ id: T
30
+ } });
31
+ if (n.error) {
32
+ s.error(O("ggnomad.listingEdit.lifecycleErrorTitle", "Could not change listing status"), n.error.message);
33
+ return;
34
+ }
35
+ s.success(O("ggnomad.listingEdit.lifecycleSuccessTitle", "Listing status updated"), e === "SUBMIT" ? O("ggnomad.listingEdit.submittedBody", "Submitted for review. It goes live once a marketplace operator approves it.") : e === "PAUSE" ? O("ggnomad.listingEdit.pausedBody", "The listing is paused and hidden from booking.") : O("ggnomad.listingEdit.resumedBody", "The listing is live again.")), C(!1), D?.();
36
+ } catch (e) {
37
+ s.error(O("ggnomad.listingEdit.lifecycleErrorTitle", "Could not change listing status"), e instanceof Error ? e.message : String(e));
38
+ }
39
+ }, ie = {
40
+ DRAFT: O("ggnomad.listingEdit.statusDraft", "Draft"),
41
+ PENDING_REVIEW: O("ggnomad.listingEdit.statusPendingReview", "Pending review"),
42
+ ACTIVE: O("ggnomad.listingEdit.statusActive", "Active"),
43
+ PAUSED: O("ggnomad.listingEdit.statusPaused", "Paused"),
44
+ ARCHIVED: O("ggnomad.listingEdit.statusArchived", "Archived")
45
+ }, ae = {
46
+ SUBMIT: O("ggnomad.listingEdit.submitForReview", "Submit for review"),
47
+ PAUSE: O("ggnomad.listingEdit.pause", "Pause listing"),
48
+ RESUME: O("ggnomad.listingEdit.resume", "Resume listing")
49
+ }, oe = async () => {
17
50
  try {
18
51
  let e = {};
19
- D !== (w.name ?? "") && (e.name = D), k !== (w.description ?? "") && (e.description = k);
20
- let t = parseFloat(M);
21
- t !== (w.pricePerNight ?? w.price ?? 0) && (S === "PROPERTY" ? e.pricePerNight = t : e.price = t), P !== (w.status ?? "ACTIVE") && (e.status = P);
22
- let n = I.split("\n").map((e) => e.trim()).filter(Boolean), r = w.images ?? [];
52
+ k !== (E.name ?? "") && (e.name = k), j !== (E.description ?? "") && (e.description = j);
53
+ let t = parseFloat(P);
54
+ t !== (E.pricePerNight ?? E.price ?? 0) && (w === "PROPERTY" ? e.pricePerNight = t : e.price = t);
55
+ let n = R.split("\n").map((e) => e.trim()).filter(Boolean), r = E.images ?? [];
23
56
  if (JSON.stringify(n) !== JSON.stringify(r) && (e.images = n), Object.keys(e).length === 0) {
24
- i.info(E("ggnomad.listingEdit.noChangesTitle", "No changes"), E("ggnomad.listingEdit.noChangesBody", "Nothing was modified.")), x(!1);
57
+ s.info(O("ggnomad.listingEdit.noChangesTitle", "No changes"), O("ggnomad.listingEdit.noChangesBody", "Nothing was modified.")), C(!1);
25
58
  return;
26
59
  }
27
- let a;
28
- if (a = S === "PROPERTY" ? await R({ variables: {
29
- id: C,
60
+ let i;
61
+ if (i = w === "PROPERTY" ? await B({ variables: {
62
+ id: T,
30
63
  input: e
31
- } }) : S === "TOUR" ? await B({ variables: {
32
- id: C,
64
+ } }) : w === "TOUR" ? await H({ variables: {
65
+ id: T,
33
66
  input: e
34
- } }) : S === "EVENT" ? await H({ variables: {
35
- id: C,
67
+ } }) : w === "EVENT" ? await W({ variables: {
68
+ id: T,
36
69
  input: e
37
- } }) : await W({ variables: {
38
- id: C,
70
+ } }) : await K({ variables: {
71
+ id: T,
39
72
  input: e
40
- } }), a?.error) {
41
- i.error(E("ggnomad.listingEdit.errorTitle", "Could not update listing"), a.error.message);
73
+ } }), i?.error) {
74
+ s.error(O("ggnomad.listingEdit.errorTitle", "Could not update listing"), i.error.message);
42
75
  return;
43
76
  }
44
- i.success(E("ggnomad.listingEdit.successTitle", "Listing updated"), E("ggnomad.listingEdit.successBody", "Your changes have been saved.")), x(!1), T?.();
77
+ s.success(O("ggnomad.listingEdit.successTitle", "Listing updated"), O("ggnomad.listingEdit.successBody", "Your changes have been saved.")), C(!1), D?.();
45
78
  } catch (e) {
46
- i.error(E("ggnomad.listingEdit.errorTitle", "Could not update listing"), e instanceof Error ? e.message : String(e));
79
+ s.error(O("ggnomad.listingEdit.errorTitle", "Could not update listing"), e instanceof Error ? e.message : String(e));
47
80
  }
48
- }, J = S === "PROPERTY" ? "Price per night" : "Price";
49
- return /* @__PURE__ */ v(u, {
50
- open: b,
51
- onOpenChange: x,
52
- children: /* @__PURE__ */ y(p, {
81
+ }, se = w === "PROPERTY" ? "Price per night" : "Price";
82
+ return /* @__PURE__ */ y(p, {
83
+ open: S,
84
+ onOpenChange: C,
85
+ children: /* @__PURE__ */ b(h, {
53
86
  className: "max-w-2xl",
54
87
  children: [
55
- /* @__PURE__ */ y(g, { children: [/* @__PURE__ */ y(_, {
88
+ /* @__PURE__ */ b(v, { children: [/* @__PURE__ */ b(te, {
56
89
  className: "flex items-center gap-2",
57
- children: [/* @__PURE__ */ v(s, { className: "size-4" }), E("ggnomad.listingEdit.title", "Edit listing")]
58
- }), /* @__PURE__ */ v(m, { children: E("ggnomad.listingEdit.description", "Update the details for this listing.") })] }),
59
- /* @__PURE__ */ y("div", {
90
+ children: [/* @__PURE__ */ y(u, { className: "size-4" }), O("ggnomad.listingEdit.title", "Edit listing")]
91
+ }), /* @__PURE__ */ y(g, { children: O("ggnomad.listingEdit.description", "Update the details for this listing.") })] }),
92
+ /* @__PURE__ */ b("div", {
60
93
  className: "space-y-4",
61
94
  children: [
62
- /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
95
+ /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
63
96
  htmlFor: "listing-edit-name",
64
97
  className: "block text-sm font-medium text-text-primary",
65
- children: E("ggnomad.listingEdit.nameLabel", "Name")
66
- }), /* @__PURE__ */ v("input", {
98
+ children: O("ggnomad.listingEdit.nameLabel", "Name")
99
+ }), /* @__PURE__ */ y("input", {
67
100
  id: "listing-edit-name",
68
101
  type: "text",
69
- value: D,
70
- onChange: (e) => O(e.target.value),
71
- disabled: K,
102
+ value: k,
103
+ onChange: (e) => A(e.target.value),
104
+ disabled: $,
72
105
  className: "mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50",
73
- placeholder: E("ggnomad.listingEdit.namePlaceholder", "Enter name")
106
+ placeholder: O("ggnomad.listingEdit.namePlaceholder", "Enter name")
74
107
  })] }),
75
- /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
108
+ /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
76
109
  htmlFor: "listing-edit-description",
77
110
  className: "block text-sm font-medium text-text-primary",
78
- children: E("ggnomad.listingEdit.descriptionLabel", "Description")
79
- }), /* @__PURE__ */ v("textarea", {
111
+ children: O("ggnomad.listingEdit.descriptionLabel", "Description")
112
+ }), /* @__PURE__ */ y("textarea", {
80
113
  id: "listing-edit-description",
81
- value: k,
82
- onChange: (e) => A(e.target.value),
83
- disabled: K,
114
+ value: j,
115
+ onChange: (e) => M(e.target.value),
116
+ disabled: $,
84
117
  rows: 3,
85
118
  className: "mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50",
86
- placeholder: E("ggnomad.listingEdit.descriptionPlaceholder", "Enter description")
119
+ placeholder: O("ggnomad.listingEdit.descriptionPlaceholder", "Enter description")
87
120
  })] }),
88
- /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
121
+ /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
89
122
  htmlFor: "listing-edit-price",
90
123
  className: "block text-sm font-medium text-text-primary",
91
- children: E("ggnomad.listingEdit.priceLabel", J)
92
- }), /* @__PURE__ */ v("input", {
124
+ children: O("ggnomad.listingEdit.priceLabel", se)
125
+ }), /* @__PURE__ */ y("input", {
93
126
  id: "listing-edit-price",
94
127
  type: "number",
95
128
  step: "0.01",
96
129
  min: "0",
97
- value: M,
98
- onChange: (e) => N(e.target.value),
99
- disabled: K,
130
+ value: P,
131
+ onChange: (e) => F(e.target.value),
132
+ disabled: $,
100
133
  className: "mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50",
101
134
  placeholder: "0.00"
102
135
  })] }),
103
- /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
104
- htmlFor: "listing-edit-status",
105
- className: "block text-sm font-medium text-text-primary",
106
- children: E("ggnomad.listingEdit.statusLabel", "Status")
107
- }), /* @__PURE__ */ y("select", {
108
- id: "listing-edit-status",
109
- value: P,
110
- onChange: (e) => F(e.target.value),
111
- disabled: K,
112
- className: "mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50",
136
+ I ? /* @__PURE__ */ b("div", {
137
+ "data-testid": "listing-edit-status",
113
138
  children: [
114
- /* @__PURE__ */ v("option", {
115
- value: "ACTIVE",
116
- children: E("ggnomad.listingEdit.statusActive", "Active")
139
+ /* @__PURE__ */ y("span", {
140
+ className: "block text-sm font-medium text-text-primary",
141
+ children: O("ggnomad.listingEdit.statusLabel", "Status")
117
142
  }),
118
- /* @__PURE__ */ v("option", {
119
- value: "PAUSED",
120
- children: E("ggnomad.listingEdit.statusPaused", "Paused")
143
+ /* @__PURE__ */ b("div", {
144
+ className: "mt-1 flex flex-wrap items-center gap-3",
145
+ children: [/* @__PURE__ */ y("span", {
146
+ className: "rounded-md border border-border-seam bg-bg-sunken px-2 py-1 text-sm text-text-primary",
147
+ children: ie[I] ?? I
148
+ }), L ? /* @__PURE__ */ y("button", {
149
+ type: "button",
150
+ onClick: () => void re(L),
151
+ disabled: $,
152
+ className: "rounded-md border border-border-seam px-3 py-1 text-sm font-medium text-text-primary hover:bg-bg-surface disabled:opacity-50",
153
+ children: ae[L]
154
+ }) : null]
121
155
  }),
122
- /* @__PURE__ */ v("option", {
123
- value: "DRAFT",
124
- children: E("ggnomad.listingEdit.statusDraft", "Draft")
125
- })
156
+ I === "PENDING_REVIEW" ? /* @__PURE__ */ y("p", {
157
+ className: "mt-1 text-xs text-text-secondary",
158
+ children: O("ggnomad.listingEdit.pendingReviewHint", "A marketplace operator is reviewing this listing. It goes live once approved.")
159
+ }) : null
126
160
  ]
127
- })] }),
128
- /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
161
+ }) : null,
162
+ /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
129
163
  htmlFor: "listing-edit-images",
130
164
  className: "block text-sm font-medium text-text-primary",
131
- children: E("ggnomad.listingEdit.imagesLabel", "Images (one URL per line)")
132
- }), /* @__PURE__ */ v("textarea", {
165
+ children: O("ggnomad.listingEdit.imagesLabel", "Images (one URL per line)")
166
+ }), /* @__PURE__ */ y("textarea", {
133
167
  id: "listing-edit-images",
134
- value: I,
135
- onChange: (e) => L(e.target.value),
136
- disabled: K,
168
+ value: R,
169
+ onChange: (e) => z(e.target.value),
170
+ disabled: $,
137
171
  rows: 3,
138
172
  className: "mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm font-mono text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50",
139
- placeholder: E("ggnomad.listingEdit.imagesPlaceholder", "https://example.com/image1.jpg\nhttps://example.com/image2.jpg")
173
+ placeholder: O("ggnomad.listingEdit.imagesPlaceholder", "https://example.com/image1.jpg\nhttps://example.com/image2.jpg")
140
174
  })] })
141
175
  ]
142
176
  }),
143
- /* @__PURE__ */ y(h, { children: [/* @__PURE__ */ v(f, {
144
- disabled: K,
145
- children: E("ggnomad.listingEdit.cancel", "Cancel")
146
- }), /* @__PURE__ */ v(d, {
177
+ /* @__PURE__ */ b(_, { children: [/* @__PURE__ */ y(ee, {
178
+ disabled: $,
179
+ children: O("ggnomad.listingEdit.cancel", "Cancel")
180
+ }), /* @__PURE__ */ y(m, {
147
181
  onClick: (e) => {
148
- e.preventDefault(), q();
182
+ e.preventDefault(), oe();
149
183
  },
150
- disabled: K,
184
+ disabled: $,
151
185
  className: "bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover",
152
- children: K ? E("ggnomad.listingEdit.saving", "Saving…") : E("ggnomad.listingEdit.save", "Save changes")
186
+ children: $ ? O("ggnomad.listingEdit.saving", "Saving…") : O("ggnomad.listingEdit.save", "Save changes")
153
187
  })] })
154
188
  ]
155
189
  })
156
190
  });
157
191
  }
158
192
  //#endregion
159
- export { b as ListingEditDialog };
193
+ export { S as ListingEditDialog };
160
194
 
161
195
  //# sourceMappingURL=ListingEditDialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ListingEditDialog.js","names":[],"sources":["../../../src/ggnomad/components/ListingEditDialog.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { useMutation } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Pencil } from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n UPDATE_GGNOMAD_PROPERTY_MUTATION,\n UPDATE_GGNOMAD_TOUR_MUTATION,\n UPDATE_GGNOMAD_EVENT_MUTATION,\n UPDATE_GGNOMAD_ACTIVITY_MUTATION,\n} from '../graphql';\nimport { notify } from '../utils/notify';\n\ntype TargetType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ninterface ListingEditDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n targetType: TargetType;\n id: string;\n initial: {\n name?: string;\n description?: string;\n price?: number;\n pricePerNight?: number;\n status?: string;\n images?: string[];\n };\n onSaved?: () => void;\n}\n\n/** Edit dialog for listing details (property, tour, event, activity). */\nexport function ListingEditDialog({\n open,\n onOpenChange,\n targetType,\n id,\n initial,\n onSaved,\n}: ListingEditDialogProps) {\n const { t } = useI18n();\n const [name, setName] = useState(initial.name ?? '');\n const [description, setDescription] = useState(initial.description ?? '');\n const priceField = initial.pricePerNight ?? initial.price ?? 0;\n const [price, setPrice] = useState(String(priceField));\n const [status, setStatus] = useState(initial.status ?? 'ACTIVE');\n const [imagesText, setImagesText] = useState((initial.images ?? []).join('\\n'));\n\n const [updateProperty, { loading: updatingProperty }] = useMutation(\n UPDATE_GGNOMAD_PROPERTY_MUTATION as TypedDocumentNode<\n { updateGgnomadProperty: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n pricePerNight?: number;\n status?: string;\n images?: string[];\n };\n }\n >\n );\n\n const [updateTour, { loading: updatingTour }] = useMutation(\n UPDATE_GGNOMAD_TOUR_MUTATION as TypedDocumentNode<\n { updateGgnomadTour: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n price?: number;\n status?: string;\n images?: string[];\n };\n }\n >\n );\n\n const [updateEvent, { loading: updatingEvent }] = useMutation(\n UPDATE_GGNOMAD_EVENT_MUTATION as TypedDocumentNode<\n { updateGgnomadEvent: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n price?: number;\n status?: string;\n images?: string[];\n };\n }\n >\n );\n\n const [updateActivity, { loading: updatingActivity }] = useMutation(\n UPDATE_GGNOMAD_ACTIVITY_MUTATION as TypedDocumentNode<\n { updateGgnomadActivity: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n price?: number;\n status?: string;\n images?: string[];\n };\n }\n >\n );\n\n const loading = useMemo(\n () => updatingProperty || updatingTour || updatingEvent || updatingActivity,\n [updatingProperty, updatingTour, updatingEvent, updatingActivity]\n );\n\n const handleSave = async () => {\n try {\n // Build input with only changed fields\n const input: Record<string, unknown> = {};\n\n if (name !== (initial.name ?? '')) {\n input.name = name;\n }\n if (description !== (initial.description ?? '')) {\n input.description = description;\n }\n\n const priceValue = parseFloat(price);\n const initialPrice = initial.pricePerNight ?? initial.price ?? 0;\n if (priceValue !== initialPrice) {\n if (targetType === 'PROPERTY') {\n input.pricePerNight = priceValue;\n } else {\n input.price = priceValue;\n }\n }\n\n if (status !== (initial.status ?? 'ACTIVE')) {\n input.status = status;\n }\n\n const newImages = imagesText\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n const initialImages = initial.images ?? [];\n if (JSON.stringify(newImages) !== JSON.stringify(initialImages)) {\n input.images = newImages;\n }\n\n // If nothing changed, just close\n if (Object.keys(input).length === 0) {\n notify.info(\n t('ggnomad.listingEdit.noChangesTitle', 'No changes'),\n t('ggnomad.listingEdit.noChangesBody', 'Nothing was modified.')\n );\n onOpenChange(false);\n return;\n }\n\n let result;\n if (targetType === 'PROPERTY') {\n result = await updateProperty({\n variables: { id, input },\n });\n } else if (targetType === 'TOUR') {\n result = await updateTour({\n variables: { id, input },\n });\n } else if (targetType === 'EVENT') {\n result = await updateEvent({\n variables: { id, input },\n });\n } else {\n result = await updateActivity({\n variables: { id, input },\n });\n }\n\n if (result?.error) {\n notify.error(\n t('ggnomad.listingEdit.errorTitle', 'Could not update listing'),\n result.error.message\n );\n return;\n }\n\n notify.success(\n t('ggnomad.listingEdit.successTitle', 'Listing updated'),\n t('ggnomad.listingEdit.successBody', 'Your changes have been saved.')\n );\n\n onOpenChange(false);\n onSaved?.();\n } catch (err) {\n notify.error(\n t('ggnomad.listingEdit.errorTitle', 'Could not update listing'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const priceLabel = targetType === 'PROPERTY' ? 'Price per night' : 'Price';\n\n return (\n <AlertDialog open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent className=\"max-w-2xl\">\n <AlertDialogHeader>\n <AlertDialogTitle className=\"flex items-center gap-2\">\n <Pencil className=\"size-4\" />\n {t('ggnomad.listingEdit.title', 'Edit listing')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {t('ggnomad.listingEdit.description', 'Update the details for this listing.')}\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n <div className=\"space-y-4\">\n {/* Name */}\n <div>\n <label\n htmlFor=\"listing-edit-name\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.listingEdit.nameLabel', 'Name')}\n </label>\n <input\n id=\"listing-edit-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n disabled={loading}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n placeholder={t('ggnomad.listingEdit.namePlaceholder', 'Enter name')}\n />\n </div>\n\n {/* Description */}\n <div>\n <label\n htmlFor=\"listing-edit-description\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.listingEdit.descriptionLabel', 'Description')}\n </label>\n <textarea\n id=\"listing-edit-description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n disabled={loading}\n rows={3}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n placeholder={t('ggnomad.listingEdit.descriptionPlaceholder', 'Enter description')}\n />\n </div>\n\n {/* Price */}\n <div>\n <label\n htmlFor=\"listing-edit-price\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t(`ggnomad.listingEdit.priceLabel`, priceLabel)}\n </label>\n <input\n id=\"listing-edit-price\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={price}\n onChange={(e) => setPrice(e.target.value)}\n disabled={loading}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n placeholder=\"0.00\"\n />\n </div>\n\n {/* Status */}\n <div>\n <label\n htmlFor=\"listing-edit-status\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.listingEdit.statusLabel', 'Status')}\n </label>\n <select\n id=\"listing-edit-status\"\n value={status}\n onChange={(e) => setStatus(e.target.value)}\n disabled={loading}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n >\n <option value=\"ACTIVE\">{t('ggnomad.listingEdit.statusActive', 'Active')}</option>\n <option value=\"PAUSED\">{t('ggnomad.listingEdit.statusPaused', 'Paused')}</option>\n <option value=\"DRAFT\">{t('ggnomad.listingEdit.statusDraft', 'Draft')}</option>\n </select>\n </div>\n\n {/* Images */}\n <div>\n <label\n htmlFor=\"listing-edit-images\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.listingEdit.imagesLabel', 'Images (one URL per line)')}\n </label>\n <textarea\n id=\"listing-edit-images\"\n value={imagesText}\n onChange={(e) => setImagesText(e.target.value)}\n disabled={loading}\n rows={3}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm font-mono text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n placeholder={t(\n 'ggnomad.listingEdit.imagesPlaceholder',\n 'https://example.com/image1.jpg\\nhttps://example.com/image2.jpg'\n )}\n />\n </div>\n </div>\n\n <AlertDialogFooter>\n <AlertDialogCancel disabled={loading}>\n {t('ggnomad.listingEdit.cancel', 'Cancel')}\n </AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n void handleSave();\n }}\n disabled={loading}\n className=\"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover\"\n >\n {loading\n ? t('ggnomad.listingEdit.saving', 'Saving…')\n : t('ggnomad.listingEdit.save', 'Save changes')}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ListingEditDialog.js","names":[],"sources":["../../../src/ggnomad/components/ListingEditDialog.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { useMutation } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Pencil } from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n UPDATE_GGNOMAD_PROPERTY_MUTATION,\n UPDATE_GGNOMAD_TOUR_MUTATION,\n UPDATE_GGNOMAD_EVENT_MUTATION,\n UPDATE_GGNOMAD_ACTIVITY_MUTATION,\n SUBMIT_GGNOMAD_LISTING_FOR_REVIEW_MUTATION,\n PAUSE_GGNOMAD_LISTING_MUTATION,\n RESUME_GGNOMAD_LISTING_MUTATION,\n} from '../graphql';\nimport { notify } from '../utils/notify';\n\ntype TargetType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ntype LifecycleAction = 'SUBMIT' | 'PAUSE' | 'RESUME';\n\n/**\n * BOFF-10194 — a listing's status is a verification-workflow state, so it is\n * no longer an editable field here. Each status offers the one transition the\n * host may run; going live from review is an operator's approval.\n */\nconst LIFECYCLE_ACTION_BY_STATUS: Record<string, LifecycleAction | undefined> = {\n DRAFT: 'SUBMIT',\n ACTIVE: 'PAUSE',\n PAUSED: 'RESUME',\n};\n\ntype LifecycleMutation = TypedDocumentNode<\n Record<string, { id: string; status: string }>,\n { listingType: TargetType; id: string }\n>;\n\ninterface ListingEditDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n targetType: TargetType;\n id: string;\n initial: {\n name?: string;\n description?: string;\n price?: number;\n pricePerNight?: number;\n status?: string;\n images?: string[];\n };\n onSaved?: () => void;\n}\n\n/** Edit dialog for listing details (property, tour, event, activity). */\nexport function ListingEditDialog({\n open,\n onOpenChange,\n targetType,\n id,\n initial,\n onSaved,\n}: ListingEditDialogProps) {\n const { t } = useI18n();\n const [name, setName] = useState(initial.name ?? '');\n const [description, setDescription] = useState(initial.description ?? '');\n const priceField = initial.pricePerNight ?? initial.price ?? 0;\n const [price, setPrice] = useState(String(priceField));\n const status = (initial.status ?? '').toUpperCase();\n const lifecycleAction = LIFECYCLE_ACTION_BY_STATUS[status];\n const [imagesText, setImagesText] = useState((initial.images ?? []).join('\\n'));\n\n const [updateProperty, { loading: updatingProperty }] = useMutation(\n UPDATE_GGNOMAD_PROPERTY_MUTATION as TypedDocumentNode<\n { updateGgnomadProperty: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n pricePerNight?: number;\n images?: string[];\n };\n }\n >\n );\n\n const [updateTour, { loading: updatingTour }] = useMutation(\n UPDATE_GGNOMAD_TOUR_MUTATION as TypedDocumentNode<\n { updateGgnomadTour: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n price?: number;\n images?: string[];\n };\n }\n >\n );\n\n const [updateEvent, { loading: updatingEvent }] = useMutation(\n UPDATE_GGNOMAD_EVENT_MUTATION as TypedDocumentNode<\n { updateGgnomadEvent: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n price?: number;\n images?: string[];\n };\n }\n >\n );\n\n const [updateActivity, { loading: updatingActivity }] = useMutation(\n UPDATE_GGNOMAD_ACTIVITY_MUTATION as TypedDocumentNode<\n { updateGgnomadActivity: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n price?: number;\n images?: string[];\n };\n }\n >\n );\n\n const [submitForReview, { loading: submitting }] = useMutation(\n SUBMIT_GGNOMAD_LISTING_FOR_REVIEW_MUTATION as LifecycleMutation\n );\n const [pauseListing, { loading: pausing }] = useMutation(\n PAUSE_GGNOMAD_LISTING_MUTATION as LifecycleMutation\n );\n const [resumeListing, { loading: resuming }] = useMutation(\n RESUME_GGNOMAD_LISTING_MUTATION as LifecycleMutation\n );\n\n const loading = useMemo(\n () =>\n updatingProperty ||\n updatingTour ||\n updatingEvent ||\n updatingActivity ||\n submitting ||\n pausing ||\n resuming,\n [updatingProperty, updatingTour, updatingEvent, updatingActivity, submitting, pausing, resuming]\n );\n\n const handleLifecycle = async (action: LifecycleAction) => {\n const run =\n action === 'SUBMIT' ? submitForReview : action === 'PAUSE' ? pauseListing : resumeListing;\n try {\n const result = await run({ variables: { listingType: targetType, id } });\n if (result.error) {\n notify.error(\n t('ggnomad.listingEdit.lifecycleErrorTitle', 'Could not change listing status'),\n result.error.message\n );\n return;\n }\n notify.success(\n t('ggnomad.listingEdit.lifecycleSuccessTitle', 'Listing status updated'),\n action === 'SUBMIT'\n ? t(\n 'ggnomad.listingEdit.submittedBody',\n 'Submitted for review. It goes live once a marketplace operator approves it.'\n )\n : action === 'PAUSE'\n ? t('ggnomad.listingEdit.pausedBody', 'The listing is paused and hidden from booking.')\n : t('ggnomad.listingEdit.resumedBody', 'The listing is live again.')\n );\n onOpenChange(false);\n onSaved?.();\n } catch (err) {\n notify.error(\n t('ggnomad.listingEdit.lifecycleErrorTitle', 'Could not change listing status'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const statusLabel: Record<string, string> = {\n DRAFT: t('ggnomad.listingEdit.statusDraft', 'Draft'),\n PENDING_REVIEW: t('ggnomad.listingEdit.statusPendingReview', 'Pending review'),\n ACTIVE: t('ggnomad.listingEdit.statusActive', 'Active'),\n PAUSED: t('ggnomad.listingEdit.statusPaused', 'Paused'),\n ARCHIVED: t('ggnomad.listingEdit.statusArchived', 'Archived'),\n };\n\n const lifecycleLabel: Record<LifecycleAction, string> = {\n SUBMIT: t('ggnomad.listingEdit.submitForReview', 'Submit for review'),\n PAUSE: t('ggnomad.listingEdit.pause', 'Pause listing'),\n RESUME: t('ggnomad.listingEdit.resume', 'Resume listing'),\n };\n\n const handleSave = async () => {\n try {\n // Build input with only changed fields\n const input: Record<string, unknown> = {};\n\n if (name !== (initial.name ?? '')) {\n input.name = name;\n }\n if (description !== (initial.description ?? '')) {\n input.description = description;\n }\n\n const priceValue = parseFloat(price);\n const initialPrice = initial.pricePerNight ?? initial.price ?? 0;\n if (priceValue !== initialPrice) {\n if (targetType === 'PROPERTY') {\n input.pricePerNight = priceValue;\n } else {\n input.price = priceValue;\n }\n }\n\n const newImages = imagesText\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n const initialImages = initial.images ?? [];\n if (JSON.stringify(newImages) !== JSON.stringify(initialImages)) {\n input.images = newImages;\n }\n\n // If nothing changed, just close\n if (Object.keys(input).length === 0) {\n notify.info(\n t('ggnomad.listingEdit.noChangesTitle', 'No changes'),\n t('ggnomad.listingEdit.noChangesBody', 'Nothing was modified.')\n );\n onOpenChange(false);\n return;\n }\n\n let result;\n if (targetType === 'PROPERTY') {\n result = await updateProperty({\n variables: { id, input },\n });\n } else if (targetType === 'TOUR') {\n result = await updateTour({\n variables: { id, input },\n });\n } else if (targetType === 'EVENT') {\n result = await updateEvent({\n variables: { id, input },\n });\n } else {\n result = await updateActivity({\n variables: { id, input },\n });\n }\n\n if (result?.error) {\n notify.error(\n t('ggnomad.listingEdit.errorTitle', 'Could not update listing'),\n result.error.message\n );\n return;\n }\n\n notify.success(\n t('ggnomad.listingEdit.successTitle', 'Listing updated'),\n t('ggnomad.listingEdit.successBody', 'Your changes have been saved.')\n );\n\n onOpenChange(false);\n onSaved?.();\n } catch (err) {\n notify.error(\n t('ggnomad.listingEdit.errorTitle', 'Could not update listing'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const priceLabel = targetType === 'PROPERTY' ? 'Price per night' : 'Price';\n\n return (\n <AlertDialog open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent className=\"max-w-2xl\">\n <AlertDialogHeader>\n <AlertDialogTitle className=\"flex items-center gap-2\">\n <Pencil className=\"size-4\" />\n {t('ggnomad.listingEdit.title', 'Edit listing')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {t('ggnomad.listingEdit.description', 'Update the details for this listing.')}\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n <div className=\"space-y-4\">\n {/* Name */}\n <div>\n <label\n htmlFor=\"listing-edit-name\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.listingEdit.nameLabel', 'Name')}\n </label>\n <input\n id=\"listing-edit-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n disabled={loading}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n placeholder={t('ggnomad.listingEdit.namePlaceholder', 'Enter name')}\n />\n </div>\n\n {/* Description */}\n <div>\n <label\n htmlFor=\"listing-edit-description\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.listingEdit.descriptionLabel', 'Description')}\n </label>\n <textarea\n id=\"listing-edit-description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n disabled={loading}\n rows={3}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n placeholder={t('ggnomad.listingEdit.descriptionPlaceholder', 'Enter description')}\n />\n </div>\n\n {/* Price */}\n <div>\n <label\n htmlFor=\"listing-edit-price\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t(`ggnomad.listingEdit.priceLabel`, priceLabel)}\n </label>\n <input\n id=\"listing-edit-price\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={price}\n onChange={(e) => setPrice(e.target.value)}\n disabled={loading}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n placeholder=\"0.00\"\n />\n </div>\n\n {/* Status: read-only; changed only through the verification workflow (BOFF-10194) */}\n {status ? (\n <div data-testid=\"listing-edit-status\">\n <span className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.statusLabel', 'Status')}\n </span>\n <div className=\"mt-1 flex flex-wrap items-center gap-3\">\n <span className=\"rounded-md border border-border-seam bg-bg-sunken px-2 py-1 text-sm text-text-primary\">\n {statusLabel[status] ?? status}\n </span>\n {lifecycleAction ? (\n <button\n type=\"button\"\n onClick={() => void handleLifecycle(lifecycleAction)}\n disabled={loading}\n className=\"rounded-md border border-border-seam px-3 py-1 text-sm font-medium text-text-primary hover:bg-bg-surface disabled:opacity-50\"\n >\n {lifecycleLabel[lifecycleAction]}\n </button>\n ) : null}\n </div>\n {status === 'PENDING_REVIEW' ? (\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t(\n 'ggnomad.listingEdit.pendingReviewHint',\n 'A marketplace operator is reviewing this listing. It goes live once approved.'\n )}\n </p>\n ) : null}\n </div>\n ) : null}\n\n {/* Images */}\n <div>\n <label\n htmlFor=\"listing-edit-images\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.listingEdit.imagesLabel', 'Images (one URL per line)')}\n </label>\n <textarea\n id=\"listing-edit-images\"\n value={imagesText}\n onChange={(e) => setImagesText(e.target.value)}\n disabled={loading}\n rows={3}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm font-mono text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n placeholder={t(\n 'ggnomad.listingEdit.imagesPlaceholder',\n 'https://example.com/image1.jpg\\nhttps://example.com/image2.jpg'\n )}\n />\n </div>\n </div>\n\n <AlertDialogFooter>\n <AlertDialogCancel disabled={loading}>\n {t('ggnomad.listingEdit.cancel', 'Cancel')}\n </AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n void handleSave();\n }}\n disabled={loading}\n className=\"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover\"\n >\n {loading\n ? t('ggnomad.listingEdit.saving', 'Saving…')\n : t('ggnomad.listingEdit.save', 'Save changes')}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"],"mappings":"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"}
@@ -2,7 +2,8 @@ export interface GgnomadTicket {
2
2
  id: string;
3
3
  bookingId: string;
4
4
  ticketCode: string;
5
- qrCodeDataUrl: string;
5
+ /** BOFF-10194 — null unless the viewer made the booking; hosts scan the traveler's QR instead. */
6
+ qrCodeDataUrl: string | null;
6
7
  status: 'ISSUED' | 'REDEEMED' | 'REVOKED';
7
8
  issuedAt: string;
8
9
  redeemedAt: string | null;