@burdenoff/microfe-ggnomad 2026.705.5 → 2026.705.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"GGNomadRoutes.js","names":[],"sources":["../../src/ggnomad/GGNomadRoutes.tsx"],"sourcesContent":["import { lazy, Suspense } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { Navigate, Route, Routes } from 'react-router-dom';\n\nimport { DashboardPage } from './pages/DashboardPage';\n\nconst SearchPage = lazy(() =>\n import('./pages/SearchPage').then((m) => ({ default: m.SearchPage }))\n);\nconst TravelPage = lazy(() =>\n import('./pages/TravelPage').then((m) => ({ default: m.TravelPage }))\n);\nconst HospitalityPage = lazy(() =>\n import('./pages/HospitalityPage').then((m) => ({ default: m.HospitalityPage }))\n);\nconst PropertyDetailsPage = lazy(() =>\n import('./pages/PropertyDetailsPage').then((m) => ({ default: m.PropertyDetailsPage }))\n);\nconst TourismPage = lazy(() =>\n import('./pages/TourismPage').then((m) => ({ default: m.TourismPage }))\n);\nconst TourDetailsPage = lazy(() =>\n import('./pages/TourDetailsPage').then((m) => ({ default: m.TourDetailsPage }))\n);\nconst ActivityDetailsPage = lazy(() =>\n import('./pages/ActivityDetailsPage').then((m) => ({ default: m.ActivityDetailsPage }))\n);\nconst EntertainmentPage = lazy(() =>\n import('./pages/EntertainmentPage').then((m) => ({ default: m.EntertainmentPage }))\n);\nconst EventDetailsPage = lazy(() =>\n import('./pages/EventDetailsPage').then((m) => ({ default: m.EventDetailsPage }))\n);\nconst BookingsPage = lazy(() =>\n import('./pages/BookingsPage').then((m) => ({ default: m.BookingsPage }))\n);\nconst FavoritesPage = lazy(() =>\n import('./pages/FavoritesPage').then((m) => ({ default: m.FavoritesPage }))\n);\nconst ProviderPage = lazy(() =>\n import('./pages/ProviderPage').then((m) => ({ default: m.ProviderPage }))\n);\nconst AnalyticsPage = lazy(() =>\n import('./pages/AnalyticsPage').then((m) => ({ default: m.AnalyticsPage }))\n);\nconst TripsPage = lazy(() =>\n import('./pages/TripsPage').then((m) => ({ default: m.TripsPage }))\n);\nconst TripPlanDetailPage = lazy(() =>\n import('./pages/TripPlanDetailPage').then((m) => ({ default: m.TripPlanDetailPage }))\n);\n\nfunction PageLoader() {\n return (\n <div className=\"flex min-h-[320px] items-center justify-center\">\n <Loader2 className=\"h-8 w-8 animate-spin text-primary\" />\n </div>\n );\n}\n\nexport function GGNomadRoutes() {\n return (\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={<DashboardPage />} />\n <Route path=\"search\" element={<SearchPage />} />\n <Route path=\"travel\" element={<TravelPage />} />\n <Route path=\"hospitality\" element={<HospitalityPage />} />\n <Route path=\"hospitality/:id\" element={<PropertyDetailsPage />} />\n <Route path=\"tourism\" element={<TourismPage />} />\n <Route path=\"tourism/tours/:id\" element={<TourDetailsPage />} />\n <Route path=\"tourism/activities/:id\" element={<ActivityDetailsPage />} />\n <Route path=\"entertainment\" element={<EntertainmentPage />} />\n <Route path=\"entertainment/:id\" element={<EventDetailsPage />} />\n <Route path=\"bookings\" element={<BookingsPage />} />\n <Route path=\"favorites\" element={<FavoritesPage />} />\n <Route path=\"provider\" element={<ProviderPage />} />\n <Route path=\"analytics\" element={<AnalyticsPage />} />\n <Route path=\"trips\" element={<TripsPage />} />\n <Route path=\"trips/:id\" element={<TripPlanDetailPage />} />\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"GGNomadRoutes.js","names":[],"sources":["../../src/ggnomad/GGNomadRoutes.tsx"],"sourcesContent":["import { lazy, Suspense } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { Navigate, Route, Routes } from 'react-router-dom';\n\nimport { DashboardPage } from './pages/DashboardPage';\n\nconst SearchPage = lazy(() =>\n import('./pages/SearchPage').then((m) => ({ default: m.SearchPage }))\n);\nconst TravelPage = lazy(() =>\n import('./pages/TravelPage').then((m) => ({ default: m.TravelPage }))\n);\nconst HospitalityPage = lazy(() =>\n import('./pages/HospitalityPage').then((m) => ({ default: m.HospitalityPage }))\n);\nconst PropertyDetailsPage = lazy(() =>\n import('./pages/PropertyDetailsPage').then((m) => ({ default: m.PropertyDetailsPage }))\n);\nconst TourismPage = lazy(() =>\n import('./pages/TourismPage').then((m) => ({ default: m.TourismPage }))\n);\nconst TourDetailsPage = lazy(() =>\n import('./pages/TourDetailsPage').then((m) => ({ default: m.TourDetailsPage }))\n);\nconst ActivityDetailsPage = lazy(() =>\n import('./pages/ActivityDetailsPage').then((m) => ({ default: m.ActivityDetailsPage }))\n);\nconst EntertainmentPage = lazy(() =>\n import('./pages/EntertainmentPage').then((m) => ({ default: m.EntertainmentPage }))\n);\nconst EventDetailsPage = lazy(() =>\n import('./pages/EventDetailsPage').then((m) => ({ default: m.EventDetailsPage }))\n);\nconst BookingsPage = lazy(() =>\n import('./pages/BookingsPage').then((m) => ({ default: m.BookingsPage }))\n);\nconst FavoritesPage = lazy(() =>\n import('./pages/FavoritesPage').then((m) => ({ default: m.FavoritesPage }))\n);\nconst ProviderPage = lazy(() =>\n import('./pages/ProviderPage').then((m) => ({ default: m.ProviderPage }))\n);\nconst AnalyticsPage = lazy(() =>\n import('./pages/AnalyticsPage').then((m) => ({ default: m.AnalyticsPage }))\n);\nconst TripsPage = lazy(() => import('./pages/TripsPage').then((m) => ({ default: m.TripsPage })));\nconst TripPlanDetailPage = lazy(() =>\n import('./pages/TripPlanDetailPage').then((m) => ({ default: m.TripPlanDetailPage }))\n);\n\nfunction PageLoader() {\n return (\n <div className=\"flex min-h-[320px] items-center justify-center\">\n <Loader2 className=\"h-8 w-8 animate-spin text-primary\" />\n </div>\n );\n}\n\nexport function GGNomadRoutes() {\n return (\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={<DashboardPage />} />\n <Route path=\"search\" element={<SearchPage />} />\n <Route path=\"travel\" element={<TravelPage />} />\n <Route path=\"hospitality\" element={<HospitalityPage />} />\n <Route path=\"hospitality/:id\" element={<PropertyDetailsPage />} />\n <Route path=\"tourism\" element={<TourismPage />} />\n <Route path=\"tourism/tours/:id\" element={<TourDetailsPage />} />\n <Route path=\"tourism/activities/:id\" element={<ActivityDetailsPage />} />\n <Route path=\"entertainment\" element={<EntertainmentPage />} />\n <Route path=\"entertainment/:id\" element={<EventDetailsPage />} />\n <Route path=\"bookings\" element={<BookingsPage />} />\n <Route path=\"favorites\" element={<FavoritesPage />} />\n <Route path=\"provider\" element={<ProviderPage />} />\n <Route path=\"analytics\" element={<AnalyticsPage />} />\n <Route path=\"trips\" element={<TripsPage />} />\n <Route path=\"trips/:id\" element={<TripPlanDetailPage />} />\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,151 @@
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";
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,
13
+ V,
14
+ U,
15
+ G
16
+ ]), q = async () => {
17
+ try {
18
+ 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 ?? [];
23
+ 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);
25
+ return;
26
+ }
27
+ let a;
28
+ if (a = S === "PROPERTY" ? await R({ variables: {
29
+ id: C,
30
+ input: e
31
+ } }) : S === "TOUR" ? await B({ variables: {
32
+ id: C,
33
+ input: e
34
+ } }) : S === "EVENT" ? await H({ variables: {
35
+ id: C,
36
+ input: e
37
+ } }) : await W({ variables: {
38
+ id: C,
39
+ input: e
40
+ } }), a?.error) {
41
+ i.error(E("ggnomad.listingEdit.errorTitle", "Could not update listing"), a.error.message);
42
+ return;
43
+ }
44
+ i.success(E("ggnomad.listingEdit.successTitle", "Listing updated"), E("ggnomad.listingEdit.successBody", "Your changes have been saved.")), x(!1), T?.();
45
+ } catch (e) {
46
+ i.error(E("ggnomad.listingEdit.errorTitle", "Could not update listing"), e instanceof Error ? e.message : String(e));
47
+ }
48
+ }, J = S === "PROPERTY" ? "Price per night" : "Price";
49
+ return /* @__PURE__ */ v(u, {
50
+ open: b,
51
+ onOpenChange: x,
52
+ children: /* @__PURE__ */ y(p, {
53
+ className: "max-w-2xl",
54
+ children: [
55
+ /* @__PURE__ */ y(g, { children: [/* @__PURE__ */ y(_, {
56
+ 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", {
60
+ className: "space-y-4",
61
+ children: [
62
+ /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
63
+ className: "block text-sm font-medium text-text-primary",
64
+ children: E("ggnomad.listingEdit.nameLabel", "Name")
65
+ }), /* @__PURE__ */ v("input", {
66
+ type: "text",
67
+ value: D,
68
+ onChange: (e) => O(e.target.value),
69
+ disabled: K,
70
+ 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",
71
+ placeholder: E("ggnomad.listingEdit.namePlaceholder", "Enter name")
72
+ })] }),
73
+ /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
74
+ className: "block text-sm font-medium text-text-primary",
75
+ children: E("ggnomad.listingEdit.descriptionLabel", "Description")
76
+ }), /* @__PURE__ */ v("textarea", {
77
+ value: k,
78
+ onChange: (e) => A(e.target.value),
79
+ disabled: K,
80
+ rows: 3,
81
+ 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",
82
+ placeholder: E("ggnomad.listingEdit.descriptionPlaceholder", "Enter description")
83
+ })] }),
84
+ /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
85
+ className: "block text-sm font-medium text-text-primary",
86
+ children: E("ggnomad.listingEdit.priceLabel", J)
87
+ }), /* @__PURE__ */ v("input", {
88
+ type: "number",
89
+ step: "0.01",
90
+ min: "0",
91
+ value: M,
92
+ onChange: (e) => N(e.target.value),
93
+ disabled: K,
94
+ 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",
95
+ placeholder: "0.00"
96
+ })] }),
97
+ /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
98
+ className: "block text-sm font-medium text-text-primary",
99
+ children: E("ggnomad.listingEdit.statusLabel", "Status")
100
+ }), /* @__PURE__ */ y("select", {
101
+ value: P,
102
+ onChange: (e) => F(e.target.value),
103
+ disabled: K,
104
+ 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",
105
+ children: [
106
+ /* @__PURE__ */ v("option", {
107
+ value: "ACTIVE",
108
+ children: E("ggnomad.listingEdit.statusActive", "Active")
109
+ }),
110
+ /* @__PURE__ */ v("option", {
111
+ value: "PAUSED",
112
+ children: E("ggnomad.listingEdit.statusPaused", "Paused")
113
+ }),
114
+ /* @__PURE__ */ v("option", {
115
+ value: "DRAFT",
116
+ children: E("ggnomad.listingEdit.statusDraft", "Draft")
117
+ })
118
+ ]
119
+ })] }),
120
+ /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
121
+ className: "block text-sm font-medium text-text-primary",
122
+ children: E("ggnomad.listingEdit.imagesLabel", "Images (one URL per line)")
123
+ }), /* @__PURE__ */ v("textarea", {
124
+ value: I,
125
+ onChange: (e) => L(e.target.value),
126
+ disabled: K,
127
+ rows: 3,
128
+ 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",
129
+ placeholder: E("ggnomad.listingEdit.imagesPlaceholder", "https://example.com/image1.jpg\nhttps://example.com/image2.jpg")
130
+ })] })
131
+ ]
132
+ }),
133
+ /* @__PURE__ */ y(h, { children: [/* @__PURE__ */ v(f, {
134
+ disabled: K,
135
+ children: E("ggnomad.listingEdit.cancel", "Cancel")
136
+ }), /* @__PURE__ */ v(d, {
137
+ onClick: (e) => {
138
+ e.preventDefault(), q();
139
+ },
140
+ disabled: K,
141
+ className: "bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover",
142
+ children: K ? E("ggnomad.listingEdit.saving", "Saving…") : E("ggnomad.listingEdit.save", "Save changes")
143
+ })] })
144
+ ]
145
+ })
146
+ });
147
+ }
148
+ //#endregion
149
+ export { b as ListingEditDialog };
150
+
151
+ //# sourceMappingURL=ListingEditDialog.js.map
@@ -0,0 +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 className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.nameLabel', 'Name')}\n </label>\n <input\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 className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.descriptionLabel', 'Description')}\n </label>\n <textarea\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 className=\"block text-sm font-medium text-text-primary\">\n {t(`ggnomad.listingEdit.priceLabel`, priceLabel)}\n </label>\n <input\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 className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.statusLabel', 'Status')}\n </label>\n <select\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 className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.imagesLabel', 'Images (one URL per line)')}\n </label>\n <textarea\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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"}
@@ -0,0 +1,325 @@
1
+ import { CREATE_GGNOMAD_REVIEW_MUTATION as e, GGNOMAD_REVIEWS_QUERY as t, GGNOMAD_REVIEW_SUMMARY_QUERY as n } from "../graphql.js";
2
+ import { cn as r } from "../utils/cn.js";
3
+ import { notify as i } from "../utils/notify.js";
4
+ import { useState as a } from "react";
5
+ import { AlertCircle as o, MessageSquare as s, Star as c } from "lucide-react";
6
+ import { useI18n as l } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
7
+ import { useMutation as u, useQuery as d } from "@apollo/client/react";
8
+ import { Button as f, GlassCard as p, Input as m, Textarea as h } from "@burdenoff/fe-libs/ui";
9
+ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
10
+ //#region src/ggnomad/components/ReviewsSection.tsx
11
+ function v({ distribution: e }) {
12
+ let t = e.reduce((e, t) => e + t, 0);
13
+ return /* @__PURE__ */ g("div", {
14
+ className: "space-y-2",
15
+ children: [
16
+ 5,
17
+ 4,
18
+ 3,
19
+ 2,
20
+ 1
21
+ ].map((n, r) => {
22
+ let i = e[4 - r] ?? 0, a = t > 0 ? i / t * 100 : 0;
23
+ return /* @__PURE__ */ _("div", {
24
+ className: "flex items-center gap-2",
25
+ children: [
26
+ /* @__PURE__ */ _("div", {
27
+ className: "flex items-center gap-1 whitespace-nowrap text-xs text-text-secondary",
28
+ children: [/* @__PURE__ */ g("span", { children: n }), /* @__PURE__ */ g(c, {
29
+ className: "size-3 text-status-warning-text",
30
+ "aria-hidden": "true"
31
+ })]
32
+ }),
33
+ /* @__PURE__ */ g("div", {
34
+ className: "flex-1 h-2 rounded-full bg-bg-sunken overflow-hidden",
35
+ children: /* @__PURE__ */ g("div", {
36
+ className: "h-full bg-status-warning-bg transition-all",
37
+ style: { width: `${a}%` },
38
+ role: "progressbar",
39
+ "aria-label": `${i} reviews with ${n} stars`,
40
+ "aria-valuenow": a,
41
+ "aria-valuemin": 0,
42
+ "aria-valuemax": 100
43
+ })
44
+ }),
45
+ /* @__PURE__ */ g("span", {
46
+ className: "text-xs text-text-secondary tabular-nums w-8 text-right",
47
+ children: i
48
+ })
49
+ ]
50
+ }, n);
51
+ })
52
+ });
53
+ }
54
+ function y({ review: e }) {
55
+ let { t } = l(), n = new Date(e.createdAt), i = n.toLocaleDateString(void 0, {
56
+ month: "short",
57
+ day: "numeric",
58
+ year: n.getFullYear() === (/* @__PURE__ */ new Date()).getFullYear() ? void 0 : "numeric"
59
+ });
60
+ return /* @__PURE__ */ _("div", {
61
+ className: "rounded-lg border border-border-seam bg-bg-surface/40 p-4 space-y-3",
62
+ children: [
63
+ /* @__PURE__ */ _("div", {
64
+ className: "flex items-start justify-between gap-3",
65
+ children: [/* @__PURE__ */ _("div", {
66
+ className: "flex items-center gap-2",
67
+ children: [/* @__PURE__ */ g("div", {
68
+ className: "flex gap-0.5",
69
+ children: [...[
70
+ ,
71
+ ,
72
+ ,
73
+ ,
74
+ ,
75
+ ]].map((t, n) => /* @__PURE__ */ g(c, {
76
+ className: r("size-3.5", n < e.rating ? "fill-status-warning-text text-status-warning-text" : "text-border-seam"),
77
+ "aria-hidden": "true"
78
+ }, n))
79
+ }), /* @__PURE__ */ _("span", {
80
+ className: "text-xs text-text-secondary font-medium",
81
+ children: [e.rating, "/5"]
82
+ })]
83
+ }), e.isVerified && /* @__PURE__ */ _("span", {
84
+ className: "inline-flex items-center gap-1 text-xs font-medium text-status-success-text bg-status-success-bg-subtle px-2 py-1 rounded-full",
85
+ children: [/* @__PURE__ */ g("span", {
86
+ className: "size-1.5 bg-status-success-text rounded-full",
87
+ "aria-hidden": "true"
88
+ }), t("ggnomad.reviews.verified", "Verified")]
89
+ })]
90
+ }),
91
+ e.title && /* @__PURE__ */ g("h4", {
92
+ className: "font-medium text-text-primary text-sm",
93
+ children: e.title
94
+ }),
95
+ e.content && /* @__PURE__ */ g("p", {
96
+ className: "text-sm text-text-secondary leading-relaxed",
97
+ children: e.content
98
+ }),
99
+ /* @__PURE__ */ _("div", {
100
+ className: "flex items-center justify-between pt-2 border-t border-border-seam",
101
+ children: [/* @__PURE__ */ g("span", {
102
+ className: "text-xs text-text-secondary",
103
+ children: i
104
+ }), e.helpfulCount > 0 && /* @__PURE__ */ _("span", {
105
+ className: "text-xs text-text-secondary flex items-center gap-1",
106
+ children: [/* @__PURE__ */ g(s, {
107
+ className: "size-3",
108
+ "aria-hidden": "true"
109
+ }), e.helpfulCount]
110
+ })]
111
+ }),
112
+ e.response && /* @__PURE__ */ _("div", {
113
+ className: "mt-3 pt-3 border-t border-border-seam bg-bg-sunken/50 rounded p-3 text-sm",
114
+ children: [
115
+ /* @__PURE__ */ g("p", {
116
+ className: "font-medium text-text-primary mb-1",
117
+ children: t("ggnomad.reviews.hostResponse", "Host response")
118
+ }),
119
+ /* @__PURE__ */ g("p", {
120
+ className: "text-text-secondary",
121
+ children: e.response
122
+ }),
123
+ e.respondedAt && /* @__PURE__ */ g("p", {
124
+ className: "text-xs text-text-secondary mt-2",
125
+ children: new Date(e.respondedAt).toLocaleDateString()
126
+ })
127
+ ]
128
+ })
129
+ ]
130
+ });
131
+ }
132
+ function b({ targetType: t, targetId: n, onReviewCreated: o }) {
133
+ let { t: s } = l(), [d, v] = a(5), [y, b] = a(""), [x, S] = a(""), [C, { loading: w }] = u(e);
134
+ return /* @__PURE__ */ _(p, {
135
+ treatment: "glass",
136
+ glow: !0,
137
+ className: "border-border-seam p-5",
138
+ children: [/* @__PURE__ */ g("h3", {
139
+ className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
140
+ children: s("ggnomad.reviews.composer.title", "Share your experience")
141
+ }), /* @__PURE__ */ _("form", {
142
+ onSubmit: async (e) => {
143
+ if (e.preventDefault(), d === 0) {
144
+ i.warning(s("ggnomad.reviews.composer.ratingRequired", "Rating required"), s("ggnomad.reviews.composer.selectRating", "Please select a rating before submitting."));
145
+ return;
146
+ }
147
+ try {
148
+ let e = await C({ variables: { input: {
149
+ targetType: t,
150
+ targetId: n,
151
+ rating: d,
152
+ title: y.trim() || void 0,
153
+ content: x.trim() || void 0
154
+ } } });
155
+ if (e.error) {
156
+ i.error(s("ggnomad.reviews.composer.error", "Could not post review"), e.error.message);
157
+ return;
158
+ }
159
+ i.success(s("ggnomad.reviews.composer.success", "Review posted"), s("ggnomad.reviews.composer.successDesc", "Thank you for sharing your experience!")), v(5), b(""), S(""), o();
160
+ } catch (e) {
161
+ i.error(s("ggnomad.reviews.composer.error", "Could not post review"), e instanceof Error ? e.message : String(e));
162
+ }
163
+ },
164
+ className: "space-y-4",
165
+ children: [
166
+ /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
167
+ className: "block text-sm font-medium text-text-primary mb-2",
168
+ children: s("ggnomad.reviews.composer.rating", "Your rating")
169
+ }), /* @__PURE__ */ g("div", {
170
+ className: "flex gap-2",
171
+ children: [
172
+ 1,
173
+ 2,
174
+ 3,
175
+ 4,
176
+ 5
177
+ ].map((e) => /* @__PURE__ */ g("button", {
178
+ type: "button",
179
+ onClick: () => v(e),
180
+ className: r("p-1.5 rounded-lg transition-all", d >= e ? "bg-status-warning-bg-subtle" : "bg-bg-sunken hover:bg-bg-surface"),
181
+ "aria-label": s("ggnomad.reviews.composer.ratingLabel", "Rate {{stars}} out of 5 stars").replace("{{stars}}", String(e)),
182
+ "aria-pressed": d >= e,
183
+ children: /* @__PURE__ */ g(c, {
184
+ className: r("size-5 transition-colors", d >= e ? "fill-status-warning-text text-status-warning-text" : "text-border-seam"),
185
+ "aria-hidden": "true"
186
+ })
187
+ }, e))
188
+ })] }),
189
+ /* @__PURE__ */ _("div", { children: [
190
+ /* @__PURE__ */ g("label", {
191
+ htmlFor: "review-title",
192
+ className: "block text-sm font-medium text-text-primary mb-2",
193
+ children: s("ggnomad.reviews.composer.titleLabel", "Title (optional)")
194
+ }),
195
+ /* @__PURE__ */ g(m, {
196
+ id: "review-title",
197
+ placeholder: s("ggnomad.reviews.composer.titlePlaceholder", "e.g., \"Great location, friendly host\""),
198
+ value: y,
199
+ onChange: (e) => b(e.target.value),
200
+ maxLength: 100,
201
+ disabled: w,
202
+ className: "bg-bg-surface border-border-seam"
203
+ }),
204
+ /* @__PURE__ */ _("p", {
205
+ className: "text-xs text-text-secondary mt-1",
206
+ children: [y.length, "/100"]
207
+ })
208
+ ] }),
209
+ /* @__PURE__ */ _("div", { children: [
210
+ /* @__PURE__ */ g("label", {
211
+ htmlFor: "review-content",
212
+ className: "block text-sm font-medium text-text-primary mb-2",
213
+ children: s("ggnomad.reviews.composer.contentLabel", "Your review (optional)")
214
+ }),
215
+ /* @__PURE__ */ g(h, {
216
+ id: "review-content",
217
+ placeholder: s("ggnomad.reviews.composer.contentPlaceholder", "Share the details of your experience..."),
218
+ value: x,
219
+ onChange: (e) => S(e.target.value),
220
+ maxLength: 1e3,
221
+ rows: 4,
222
+ disabled: w,
223
+ className: "bg-bg-surface border-border-seam resize-none"
224
+ }),
225
+ /* @__PURE__ */ _("p", {
226
+ className: "text-xs text-text-secondary mt-1",
227
+ children: [x.length, "/1000"]
228
+ })
229
+ ] }),
230
+ /* @__PURE__ */ g(f, {
231
+ type: "submit",
232
+ disabled: w || d === 0,
233
+ className: "w-full",
234
+ children: w ? s("ggnomad.reviews.composer.submitting", "Posting…") : s("ggnomad.reviews.composer.submit", "Post review")
235
+ })
236
+ ]
237
+ })]
238
+ });
239
+ }
240
+ function x({ targetType: e, targetId: i }) {
241
+ let { t: s } = l(), [u, m] = a(!1), { data: h, refetch: x } = d(n, { variables: {
242
+ targetType: e,
243
+ targetId: i
244
+ } }), { data: S, refetch: C } = d(t, { variables: {
245
+ targetType: e,
246
+ targetId: i,
247
+ first: 10,
248
+ skip: 0
249
+ } }), w = h?.ggnomadReviewSummary, T = S?.ggnomadReviews ?? [], E = () => {
250
+ m(!1), x(), C();
251
+ };
252
+ return /* @__PURE__ */ _("div", {
253
+ className: "space-y-6",
254
+ children: [
255
+ w && w.count > 0 && /* @__PURE__ */ _(p, {
256
+ treatment: "glass",
257
+ glow: !0,
258
+ className: "border-border-seam p-5",
259
+ children: [/* @__PURE__ */ g("h2", {
260
+ className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
261
+ children: s("ggnomad.reviews.summary", "Ratings & reviews")
262
+ }), /* @__PURE__ */ _("div", {
263
+ className: "grid gap-6 sm:grid-cols-2",
264
+ children: [/* @__PURE__ */ _("div", {
265
+ className: "flex flex-col items-center justify-center py-4",
266
+ children: [
267
+ /* @__PURE__ */ g("div", {
268
+ className: "text-5xl font-bold text-text-primary tabular-nums",
269
+ children: w.average.toFixed(1)
270
+ }),
271
+ /* @__PURE__ */ g("div", {
272
+ className: "flex gap-0.5 mt-2",
273
+ children: [...[
274
+ ,
275
+ ,
276
+ ,
277
+ ,
278
+ ,
279
+ ]].map((e, t) => /* @__PURE__ */ g(c, {
280
+ className: r("size-4", t < Math.round(w.average) ? "fill-status-warning-text text-status-warning-text" : "text-border-seam"),
281
+ "aria-hidden": "true"
282
+ }, t))
283
+ }),
284
+ /* @__PURE__ */ g("p", {
285
+ className: "text-sm text-text-secondary mt-2",
286
+ children: s("ggnomad.reviews.totalCount", "{{count}} {{plural}}").replace("{{count}}", String(w.count)).replace("{{plural}}", w.count === 1 ? s("ggnomad.reviews.review", "review") : s("ggnomad.reviews.reviews", "reviews"))
287
+ })
288
+ ]
289
+ }), /* @__PURE__ */ g("div", { children: /* @__PURE__ */ g(v, { distribution: w.distribution }) })]
290
+ })]
291
+ }),
292
+ !u && /* @__PURE__ */ g(f, {
293
+ onClick: () => m(!0),
294
+ variant: "secondary",
295
+ className: "w-full",
296
+ children: s("ggnomad.reviews.writeReview", "Write a review")
297
+ }),
298
+ u && /* @__PURE__ */ g(b, {
299
+ targetType: e,
300
+ targetId: i,
301
+ onReviewCreated: E
302
+ }),
303
+ T.length > 0 ? /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("h3", {
304
+ className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
305
+ children: s("ggnomad.reviews.allReviews", "All reviews")
306
+ }), /* @__PURE__ */ g("div", {
307
+ className: "grid gap-3",
308
+ children: T.map((e) => /* @__PURE__ */ g(y, { review: e }, e.id))
309
+ })] }) : w && w.count === 0 ? /* @__PURE__ */ _("div", {
310
+ className: "text-center py-8 rounded-lg border border-border-seam bg-bg-surface/40",
311
+ children: [/* @__PURE__ */ g(o, {
312
+ className: "size-6 text-text-secondary mx-auto mb-2",
313
+ "aria-hidden": "true"
314
+ }), /* @__PURE__ */ g("p", {
315
+ className: "text-sm text-text-secondary",
316
+ children: s("ggnomad.reviews.noReviewsYet", "Be the first to review")
317
+ })]
318
+ }) : null
319
+ ]
320
+ });
321
+ }
322
+ //#endregion
323
+ export { x as ReviewsSection };
324
+
325
+ //# sourceMappingURL=ReviewsSection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReviewsSection.js","names":[],"sources":["../../../src/ggnomad/components/ReviewsSection.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Star, MessageSquare, AlertCircle } from 'lucide-react';\nimport { Button, GlassCard, Input, Textarea } from '@burdenoff/fe-libs/ui';\n\nimport {\n GGNOMAD_REVIEW_SUMMARY_QUERY,\n GGNOMAD_REVIEWS_QUERY,\n CREATE_GGNOMAD_REVIEW_MUTATION,\n} from '../graphql';\nimport { notify } from '../utils/notify';\nimport { cn } from '../utils/cn';\n\ntype GgnomadBookingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ntype ReviewsSectionProps = {\n targetType: GgnomadBookingType;\n targetId: string;\n};\n\ntype ReviewSummaryData = {\n ggnomadReviewSummary: {\n average: number;\n count: number;\n distribution: number[];\n };\n};\n\ntype ReviewsData = {\n ggnomadReviews: Array<{\n id: string;\n rating: number;\n title?: string;\n content?: string;\n isVerified: boolean;\n helpfulCount: number;\n response?: string;\n respondedAt?: string;\n createdAt: string;\n }>;\n};\n\ntype CreateReviewInput = {\n targetType: GgnomadBookingType;\n targetId: string;\n rating: number;\n title?: string;\n content?: string;\n images?: string[];\n};\n\n/**\n * Star distribution bar chart component.\n * Displays a 5-row breakdown of rating distribution.\n */\nfunction StarDistributionChart({ distribution }: { distribution: number[] }) {\n const total = distribution.reduce((a, b) => a + b, 0);\n\n return (\n <div className=\"space-y-2\">\n {[5, 4, 3, 2, 1].map((stars, idx) => {\n const count = distribution[4 - idx] ?? 0;\n const percentage = total > 0 ? (count / total) * 100 : 0;\n\n return (\n <div key={stars} className=\"flex items-center gap-2\">\n <div className=\"flex items-center gap-1 whitespace-nowrap text-xs text-text-secondary\">\n <span>{stars}</span>\n <Star className=\"size-3 text-status-warning-text\" aria-hidden=\"true\" />\n </div>\n <div className=\"flex-1 h-2 rounded-full bg-bg-sunken overflow-hidden\">\n <div\n className=\"h-full bg-status-warning-bg transition-all\"\n style={{ width: `${percentage}%` }}\n role=\"progressbar\"\n aria-label={`${count} reviews with ${stars} stars`}\n aria-valuenow={percentage}\n aria-valuemin={0}\n aria-valuemax={100}\n />\n </div>\n <span className=\"text-xs text-text-secondary tabular-nums w-8 text-right\">{count}</span>\n </div>\n );\n })}\n </div>\n );\n}\n\n/**\n * Individual review card component.\n */\nfunction ReviewCard({ review }: { review: ReviewsData['ggnomadReviews'][0] }) {\n const { t } = useI18n();\n const createdDate = new Date(review.createdAt);\n const formattedDate = createdDate.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: createdDate.getFullYear() !== new Date().getFullYear() ? 'numeric' : undefined,\n });\n\n return (\n <div className=\"rounded-lg border border-border-seam bg-bg-surface/40 p-4 space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-2\">\n <div className=\"flex gap-0.5\">\n {[...Array(5)].map((_, i) => (\n <Star\n key={i}\n className={cn(\n 'size-3.5',\n i < review.rating\n ? 'fill-status-warning-text text-status-warning-text'\n : 'text-border-seam'\n )}\n aria-hidden=\"true\"\n />\n ))}\n </div>\n <span className=\"text-xs text-text-secondary font-medium\">{review.rating}/5</span>\n </div>\n {review.isVerified && (\n <span className=\"inline-flex items-center gap-1 text-xs font-medium text-status-success-text bg-status-success-bg-subtle px-2 py-1 rounded-full\">\n <span className=\"size-1.5 bg-status-success-text rounded-full\" aria-hidden=\"true\" />\n {t('ggnomad.reviews.verified', 'Verified')}\n </span>\n )}\n </div>\n\n {review.title && <h4 className=\"font-medium text-text-primary text-sm\">{review.title}</h4>}\n\n {review.content && (\n <p className=\"text-sm text-text-secondary leading-relaxed\">{review.content}</p>\n )}\n\n <div className=\"flex items-center justify-between pt-2 border-t border-border-seam\">\n <span className=\"text-xs text-text-secondary\">{formattedDate}</span>\n {review.helpfulCount > 0 && (\n <span className=\"text-xs text-text-secondary flex items-center gap-1\">\n <MessageSquare className=\"size-3\" aria-hidden=\"true\" />\n {review.helpfulCount}\n </span>\n )}\n </div>\n\n {review.response && (\n <div className=\"mt-3 pt-3 border-t border-border-seam bg-bg-sunken/50 rounded p-3 text-sm\">\n <p className=\"font-medium text-text-primary mb-1\">\n {t('ggnomad.reviews.hostResponse', 'Host response')}\n </p>\n <p className=\"text-text-secondary\">{review.response}</p>\n {review.respondedAt && (\n <p className=\"text-xs text-text-secondary mt-2\">\n {new Date(review.respondedAt).toLocaleDateString()}\n </p>\n )}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Review composer component.\n */\nfunction ReviewComposer({\n targetType,\n targetId,\n onReviewCreated,\n}: {\n targetType: GgnomadBookingType;\n targetId: string;\n onReviewCreated: () => void;\n}) {\n const { t } = useI18n();\n const [rating, setRating] = useState(5);\n const [title, setTitle] = useState('');\n const [content, setContent] = useState('');\n\n const [createReview, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_REVIEW_MUTATION as TypedDocumentNode<\n {\n createGgnomadReview: {\n id: string;\n rating: number;\n title?: string;\n content?: string;\n };\n },\n { input: CreateReviewInput }\n >\n );\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (rating === 0) {\n notify.warning(\n t('ggnomad.reviews.composer.ratingRequired', 'Rating required'),\n t('ggnomad.reviews.composer.selectRating', 'Please select a rating before submitting.')\n );\n return;\n }\n\n try {\n const result = await createReview({\n variables: {\n input: {\n targetType,\n targetId,\n rating,\n title: title.trim() || undefined,\n content: content.trim() || undefined,\n },\n },\n });\n\n if (result.error) {\n notify.error(\n t('ggnomad.reviews.composer.error', 'Could not post review'),\n result.error.message\n );\n return;\n }\n\n notify.success(\n t('ggnomad.reviews.composer.success', 'Review posted'),\n t('ggnomad.reviews.composer.successDesc', 'Thank you for sharing your experience!')\n );\n\n // Reset form\n setRating(5);\n setTitle('');\n setContent('');\n\n // Trigger refresh\n onReviewCreated();\n } catch (err) {\n notify.error(\n t('ggnomad.reviews.composer.error', 'Could not post review'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n return (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <h3 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.reviews.composer.title', 'Share your experience')}\n </h3>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {/* Rating selector */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n {t('ggnomad.reviews.composer.rating', 'Your rating')}\n </label>\n <div className=\"flex gap-2\">\n {[1, 2, 3, 4, 5].map((star) => (\n <button\n key={star}\n type=\"button\"\n onClick={() => setRating(star)}\n className={cn(\n 'p-1.5 rounded-lg transition-all',\n rating >= star\n ? 'bg-status-warning-bg-subtle'\n : 'bg-bg-sunken hover:bg-bg-surface'\n )}\n aria-label={t(\n 'ggnomad.reviews.composer.ratingLabel',\n `Rate {{stars}} out of 5 stars`\n ).replace('{{stars}}', String(star))}\n aria-pressed={rating >= star}\n >\n <Star\n className={cn(\n 'size-5 transition-colors',\n rating >= star\n ? 'fill-status-warning-text text-status-warning-text'\n : 'text-border-seam'\n )}\n aria-hidden=\"true\"\n />\n </button>\n ))}\n </div>\n </div>\n\n {/* Title input */}\n <div>\n <label\n htmlFor=\"review-title\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {t('ggnomad.reviews.composer.titleLabel', 'Title (optional)')}\n </label>\n <Input\n id=\"review-title\"\n placeholder={t(\n 'ggnomad.reviews.composer.titlePlaceholder',\n 'e.g., \"Great location, friendly host\"'\n )}\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n maxLength={100}\n disabled={creating}\n className=\"bg-bg-surface border-border-seam\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">{title.length}/100</p>\n </div>\n\n {/* Content textarea */}\n <div>\n <label\n htmlFor=\"review-content\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {t('ggnomad.reviews.composer.contentLabel', 'Your review (optional)')}\n </label>\n <Textarea\n id=\"review-content\"\n placeholder={t(\n 'ggnomad.reviews.composer.contentPlaceholder',\n 'Share the details of your experience...'\n )}\n value={content}\n onChange={(e) => setContent(e.target.value)}\n maxLength={1000}\n rows={4}\n disabled={creating}\n className=\"bg-bg-surface border-border-seam resize-none\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">{content.length}/1000</p>\n </div>\n\n {/* Submit button */}\n <Button type=\"submit\" disabled={creating || rating === 0} className=\"w-full\">\n {creating\n ? t('ggnomad.reviews.composer.submitting', 'Posting…')\n : t('ggnomad.reviews.composer.submit', 'Post review')}\n </Button>\n </form>\n </GlassCard>\n );\n}\n\n/**\n * ReviewsSection component.\n * Displays review summary, list, and composition UI for a bookable item.\n */\nexport function ReviewsSection({ targetType, targetId }: ReviewsSectionProps) {\n const { t } = useI18n();\n const [showComposer, setShowComposer] = useState(false);\n\n // Fetch review summary\n const { data: summaryData, refetch: refetchSummary } = useQuery(\n GGNOMAD_REVIEW_SUMMARY_QUERY as TypedDocumentNode<\n ReviewSummaryData,\n { targetType: GgnomadBookingType; targetId: string }\n >,\n { variables: { targetType, targetId } }\n );\n\n // Fetch reviews list\n const { data: reviewsData, refetch: refetchReviews } = useQuery(\n GGNOMAD_REVIEWS_QUERY as TypedDocumentNode<\n ReviewsData,\n { targetType: GgnomadBookingType; targetId: string; first?: number; skip?: number }\n >,\n {\n variables: { targetType, targetId, first: 10, skip: 0 },\n }\n );\n\n const summary = summaryData?.ggnomadReviewSummary;\n const reviews = reviewsData?.ggnomadReviews ?? [];\n\n const handleReviewCreated = () => {\n setShowComposer(false);\n void refetchSummary();\n void refetchReviews();\n };\n\n return (\n <div className=\"space-y-6\">\n {/* Summary Section */}\n {summary && summary.count > 0 && (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <h2 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.reviews.summary', 'Ratings & reviews')}\n </h2>\n\n <div className=\"grid gap-6 sm:grid-cols-2\">\n {/* Average Rating */}\n <div className=\"flex flex-col items-center justify-center py-4\">\n <div className=\"text-5xl font-bold text-text-primary tabular-nums\">\n {summary.average.toFixed(1)}\n </div>\n <div className=\"flex gap-0.5 mt-2\">\n {[...Array(5)].map((_, i) => (\n <Star\n key={i}\n className={cn(\n 'size-4',\n i < Math.round(summary.average)\n ? 'fill-status-warning-text text-status-warning-text'\n : 'text-border-seam'\n )}\n aria-hidden=\"true\"\n />\n ))}\n </div>\n <p className=\"text-sm text-text-secondary mt-2\">\n {t('ggnomad.reviews.totalCount', '{{count}} {{plural}}')\n .replace('{{count}}', String(summary.count))\n .replace(\n '{{plural}}',\n summary.count === 1\n ? t('ggnomad.reviews.review', 'review')\n : t('ggnomad.reviews.reviews', 'reviews')\n )}\n </p>\n </div>\n\n {/* Distribution Chart */}\n <div>\n <StarDistributionChart distribution={summary.distribution} />\n </div>\n </div>\n </GlassCard>\n )}\n\n {/* Write Review Composer */}\n {!showComposer && (\n <Button onClick={() => setShowComposer(true)} variant=\"secondary\" className=\"w-full\">\n {t('ggnomad.reviews.writeReview', 'Write a review')}\n </Button>\n )}\n\n {showComposer && (\n <ReviewComposer\n targetType={targetType}\n targetId={targetId}\n onReviewCreated={handleReviewCreated}\n />\n )}\n\n {/* Reviews List */}\n {reviews.length > 0 ? (\n <div>\n <h3 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.reviews.allReviews', 'All reviews')}\n </h3>\n <div className=\"grid gap-3\">\n {reviews.map((review) => (\n <ReviewCard key={review.id} review={review} />\n ))}\n </div>\n </div>\n ) : summary && summary.count === 0 ? (\n <div className=\"text-center py-8 rounded-lg border border-border-seam bg-bg-surface/40\">\n <AlertCircle className=\"size-6 text-text-secondary mx-auto mb-2\" aria-hidden=\"true\" />\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.reviews.noReviewsYet', 'Be the first to review')}\n </p>\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -6,3 +6,5 @@ import "./DetailRow.js";
6
6
  import "./LoadMoreButton.js";
7
7
  import "./DetailHero.js";
8
8
  import "./BookingPanel.js";
9
+ import "./ReviewsSection.js";
10
+ import "./ListingEditDialog.js";