@burdenoff/microfe-ggnomad 2026.825.1 → 2026.828.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.
- package/dist/ggnomad/components/AvailabilityPanel.d.ts +15 -0
- package/dist/ggnomad/components/AvailabilityPanel.d.ts.map +1 -0
- package/dist/ggnomad/components/AvailabilityPanel.js +91 -0
- package/dist/ggnomad/components/AvailabilityPanel.js.map +1 -0
- package/dist/ggnomad/components/BookingPanel.d.ts +1 -3
- package/dist/ggnomad/components/BookingPanel.d.ts.map +1 -1
- package/dist/ggnomad/components/BookingPanel.js +91 -82
- package/dist/ggnomad/components/BookingPanel.js.map +1 -1
- package/dist/ggnomad/components/EcoAccessPanel.d.ts +17 -0
- package/dist/ggnomad/components/EcoAccessPanel.d.ts.map +1 -0
- package/dist/ggnomad/components/EcoAccessPanel.js +68 -0
- package/dist/ggnomad/components/EcoAccessPanel.js.map +1 -0
- package/dist/ggnomad/components/FactGrid.d.ts +18 -0
- package/dist/ggnomad/components/FactGrid.d.ts.map +1 -0
- package/dist/ggnomad/components/FactGrid.js +29 -0
- package/dist/ggnomad/components/FactGrid.js.map +1 -0
- package/dist/ggnomad/components/GroupTripPanel.d.ts +22 -0
- package/dist/ggnomad/components/GroupTripPanel.d.ts.map +1 -0
- package/dist/ggnomad/components/GroupTripPanel.js +350 -0
- package/dist/ggnomad/components/GroupTripPanel.js.map +1 -0
- package/dist/ggnomad/components/ListingFilters.d.ts +29 -0
- package/dist/ggnomad/components/ListingFilters.d.ts.map +1 -0
- package/dist/ggnomad/components/ListingFilters.js +164 -0
- package/dist/ggnomad/components/ListingFilters.js.map +1 -0
- package/dist/ggnomad/components/PhotoGallery.d.ts +15 -0
- package/dist/ggnomad/components/PhotoGallery.d.ts.map +1 -0
- package/dist/ggnomad/components/PhotoGallery.js +54 -0
- package/dist/ggnomad/components/PhotoGallery.js.map +1 -0
- package/dist/ggnomad/components/ProviderCard.d.ts +19 -0
- package/dist/ggnomad/components/ProviderCard.d.ts.map +1 -0
- package/dist/ggnomad/components/ProviderCard.js +66 -0
- package/dist/ggnomad/components/ProviderCard.js.map +1 -0
- package/dist/ggnomad/components/RatingSummary.d.ts +14 -0
- package/dist/ggnomad/components/RatingSummary.d.ts.map +1 -0
- package/dist/ggnomad/components/RatingSummary.js +32 -0
- package/dist/ggnomad/components/RatingSummary.js.map +1 -0
- package/dist/ggnomad/components/TagChips.d.ts +16 -0
- package/dist/ggnomad/components/TagChips.d.ts.map +1 -0
- package/dist/ggnomad/components/TagChips.js +20 -0
- package/dist/ggnomad/components/TagChips.js.map +1 -0
- package/dist/ggnomad/components/index.d.ts +13 -0
- package/dist/ggnomad/components/index.d.ts.map +1 -1
- package/dist/ggnomad/components/index.js +9 -0
- package/dist/ggnomad/graphql.d.ts +10 -0
- package/dist/ggnomad/graphql.d.ts.map +1 -1
- package/dist/ggnomad/graphql.js +308 -20
- package/dist/ggnomad/graphql.js.map +1 -1
- package/dist/ggnomad/pages/ActivityDetailsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/ActivityDetailsPage.js +174 -86
- package/dist/ggnomad/pages/ActivityDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/AnalyticsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/AnalyticsPage.js +109 -77
- package/dist/ggnomad/pages/AnalyticsPage.js.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.js +188 -137
- package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
- package/dist/ggnomad/pages/DashboardPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/DashboardPage.js +245 -128
- package/dist/ggnomad/pages/DashboardPage.js.map +1 -1
- package/dist/ggnomad/pages/EntertainmentPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/EntertainmentPage.js +115 -53
- package/dist/ggnomad/pages/EntertainmentPage.js.map +1 -1
- package/dist/ggnomad/pages/EventDetailsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/EventDetailsPage.js +170 -87
- package/dist/ggnomad/pages/EventDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/FavoritesPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/FavoritesPage.js +201 -84
- package/dist/ggnomad/pages/FavoritesPage.js.map +1 -1
- package/dist/ggnomad/pages/HospitalityPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/HospitalityPage.js +99 -56
- package/dist/ggnomad/pages/HospitalityPage.js.map +1 -1
- package/dist/ggnomad/pages/PropertyDetailsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/PropertyDetailsPage.js +177 -93
- package/dist/ggnomad/pages/PropertyDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/ProviderPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/ProviderPage.js +112 -63
- package/dist/ggnomad/pages/ProviderPage.js.map +1 -1
- package/dist/ggnomad/pages/SearchPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/SearchPage.js +236 -65
- package/dist/ggnomad/pages/SearchPage.js.map +1 -1
- package/dist/ggnomad/pages/TourDetailsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TourDetailsPage.js +171 -86
- package/dist/ggnomad/pages/TourDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/TourismPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TourismPage.js +108 -54
- package/dist/ggnomad/pages/TourismPage.js.map +1 -1
- package/dist/ggnomad/pages/TravelPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TravelPage.js +406 -43
- package/dist/ggnomad/pages/TravelPage.js.map +1 -1
- package/dist/ggnomad/pages/TripPlanDetailPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TripPlanDetailPage.js +93 -70
- package/dist/ggnomad/pages/TripPlanDetailPage.js.map +1 -1
- package/dist/ggnomad/utils/format.d.ts +47 -0
- package/dist/ggnomad/utils/format.d.ts.map +1 -0
- package/dist/ggnomad/utils/format.js +125 -0
- package/dist/ggnomad/utils/format.js.map +1 -0
- package/dist/stories/BookingPanel.stories.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,117 +1,234 @@
|
|
|
1
1
|
import { GGNOMAD_FAVORITES_QUERY as e, GGNOMAD_FAVORITE_TARGETS_QUERY as t, TOGGLE_GGNOMAD_FAVORITE_MUTATION as n } from "../graphql.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { humanizeEnum as r } from "../utils/format.js";
|
|
3
|
+
import { PageShell as i } from "./PageShell.js";
|
|
4
|
+
import { cn as a } from "../utils/cn.js";
|
|
5
|
+
import { EntityCard as o } from "../components/EntityCard.js";
|
|
6
|
+
import { notify as s } from "../utils/notify.js";
|
|
7
|
+
import { RatingSummary as c } from "../components/RatingSummary.js";
|
|
5
8
|
import "../components/index.js";
|
|
6
|
-
import { useMemo as
|
|
7
|
-
import { Heart as
|
|
8
|
-
import { useI18n as
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { useMemo as l, useState as u } from "react";
|
|
10
|
+
import { ArrowUpRight as d, Heart as f, HeartOff as p } from "lucide-react";
|
|
11
|
+
import { useI18n as m } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
12
|
+
import { Link as h } from "react-router";
|
|
13
|
+
import { useMutation as g, useQuery as _ } from "@apollo/client/react";
|
|
14
|
+
import { CTAOverflowMenu as v, GlassCard as y, IllustratedEmptyState as b, PagePurpose as x } from "@burdenoff/fe-libs/ui";
|
|
15
|
+
import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
12
16
|
//#region src/ggnomad/pages/FavoritesPage.tsx
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
var T = [
|
|
18
|
+
"PROPERTY",
|
|
19
|
+
"TOUR",
|
|
20
|
+
"EVENT",
|
|
21
|
+
"ACTIVITY"
|
|
22
|
+
];
|
|
23
|
+
function E() {
|
|
24
|
+
let { t: E } = m(), [D, O] = u("ALL"), k = (e) => {
|
|
15
25
|
switch (e.toUpperCase()) {
|
|
16
|
-
case "PROPERTY": return
|
|
17
|
-
case "TOUR": return
|
|
18
|
-
case "EVENT": return
|
|
19
|
-
case "ACTIVITY": return
|
|
20
|
-
case "TRIP": return
|
|
21
|
-
default: return
|
|
26
|
+
case "PROPERTY": return E("ggnomad.favorites.type.property", "Property");
|
|
27
|
+
case "TOUR": return E("ggnomad.favorites.type.tour", "Tour");
|
|
28
|
+
case "EVENT": return E("ggnomad.favorites.type.event", "Event");
|
|
29
|
+
case "ACTIVITY": return E("ggnomad.favorites.type.activity", "Activity");
|
|
30
|
+
case "TRIP": return E("ggnomad.favorites.type.trip", "Trip");
|
|
31
|
+
default: return E("ggnomad.favorites.type.saved", "Saved item");
|
|
22
32
|
}
|
|
23
|
-
}, { data:
|
|
33
|
+
}, { data: A, refetch: j } = _(e), { data: M } = _(t), [N, { loading: P }] = g(n), F = l(() => {
|
|
24
34
|
let e = /* @__PURE__ */ new Map();
|
|
25
|
-
for (let t of [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
for (let t of M?.ggnomadProperties ?? []) e.set(`PROPERTY:${t.id}`, {
|
|
36
|
+
id: t.id,
|
|
37
|
+
name: t.name,
|
|
38
|
+
kind: t.type,
|
|
39
|
+
price: t.pricePerNight,
|
|
40
|
+
currency: t.currency,
|
|
41
|
+
rating: t.rating,
|
|
42
|
+
reviewCount: t.reviewCount,
|
|
43
|
+
images: t.images,
|
|
44
|
+
path: `../hospitality/${t.id}`
|
|
45
|
+
});
|
|
46
|
+
for (let t of M?.ggnomadTours ?? []) e.set(`TOUR:${t.id}`, {
|
|
47
|
+
id: t.id,
|
|
48
|
+
name: t.name,
|
|
49
|
+
kind: t.type,
|
|
50
|
+
price: t.price,
|
|
51
|
+
currency: t.currency,
|
|
52
|
+
rating: t.rating,
|
|
53
|
+
reviewCount: t.reviewCount,
|
|
54
|
+
images: t.images,
|
|
55
|
+
path: `../tourism/tours/${t.id}`
|
|
56
|
+
});
|
|
57
|
+
for (let t of M?.ggnomadEvents ?? []) e.set(`EVENT:${t.id}`, {
|
|
58
|
+
id: t.id,
|
|
59
|
+
name: t.name,
|
|
60
|
+
kind: t.category,
|
|
61
|
+
price: t.price,
|
|
62
|
+
currency: t.currency,
|
|
63
|
+
rating: t.rating,
|
|
64
|
+
reviewCount: t.reviewCount,
|
|
65
|
+
images: t.images,
|
|
66
|
+
path: `../entertainment/${t.id}`
|
|
67
|
+
});
|
|
68
|
+
for (let t of M?.ggnomadActivities ?? []) e.set(`ACTIVITY:${t.id}`, {
|
|
69
|
+
id: t.id,
|
|
70
|
+
name: t.name,
|
|
71
|
+
kind: t.category,
|
|
72
|
+
price: t.price,
|
|
73
|
+
currency: t.currency,
|
|
74
|
+
rating: t.rating,
|
|
75
|
+
reviewCount: t.reviewCount,
|
|
76
|
+
images: t.images,
|
|
77
|
+
path: `../tourism/activities/${t.id}`
|
|
78
|
+
});
|
|
79
|
+
for (let t of M?.ggnomadTrips ?? []) e.set(`TRIP:${t.id}`, {
|
|
80
|
+
id: t.id,
|
|
81
|
+
name: t.name,
|
|
82
|
+
kind: t.transportMode,
|
|
83
|
+
price: t.price,
|
|
84
|
+
currency: t.currency
|
|
85
|
+
});
|
|
31
86
|
return e;
|
|
32
|
-
}, [
|
|
33
|
-
let e =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
};
|
|
38
|
-
let t = x?.ggnomadTours?.[0];
|
|
39
|
-
if (t) return {
|
|
40
|
-
type: "TOUR",
|
|
41
|
-
listing: t
|
|
42
|
-
};
|
|
43
|
-
let n = x?.ggnomadEvents?.[0];
|
|
44
|
-
if (n) return {
|
|
45
|
-
type: "EVENT",
|
|
46
|
-
listing: n
|
|
47
|
-
};
|
|
48
|
-
let r = x?.ggnomadActivities?.[0];
|
|
49
|
-
return r ? {
|
|
50
|
-
type: "ACTIVITY",
|
|
51
|
-
listing: r
|
|
52
|
-
} : null;
|
|
53
|
-
}, E = async () => {
|
|
54
|
-
let e = T();
|
|
55
|
-
if (!e) {
|
|
56
|
-
a.warning(_("ggnomad.favorites.toast.noListingsTitle", "Nothing to favorite yet"), _("ggnomad.favorites.toast.noListingsBody", "Add a property, tour, event or activity first, then save it to your favorites."));
|
|
57
|
-
return;
|
|
87
|
+
}, [M]), I = l(() => A?.ggnomadFavorites ?? [], [A?.ggnomadFavorites]), L = l(() => {
|
|
88
|
+
let e = /* @__PURE__ */ new Map();
|
|
89
|
+
for (let t of I) {
|
|
90
|
+
let n = t.targetType.toUpperCase();
|
|
91
|
+
e.set(n, (e.get(n) ?? 0) + 1);
|
|
58
92
|
}
|
|
59
|
-
|
|
93
|
+
return e;
|
|
94
|
+
}, [I]), R = I.filter((e) => D === "ALL" || e.targetType.toUpperCase() === D), z = [{
|
|
95
|
+
value: "ALL",
|
|
96
|
+
label: E("ggnomad.favorites.filter.all", "All"),
|
|
97
|
+
count: I.length
|
|
98
|
+
}, ...[
|
|
99
|
+
"PROPERTY",
|
|
100
|
+
"TOUR",
|
|
101
|
+
"EVENT",
|
|
102
|
+
"ACTIVITY",
|
|
103
|
+
"TRIP"
|
|
104
|
+
].map((e) => ({
|
|
105
|
+
value: e,
|
|
106
|
+
label: k(e),
|
|
107
|
+
count: L.get(e) ?? 0
|
|
108
|
+
}))], B = async (e, t, n) => {
|
|
60
109
|
try {
|
|
61
|
-
let
|
|
62
|
-
targetType:
|
|
63
|
-
targetId:
|
|
110
|
+
let r = await N({ variables: { input: {
|
|
111
|
+
targetType: e,
|
|
112
|
+
targetId: t
|
|
64
113
|
} } });
|
|
65
|
-
if (
|
|
66
|
-
|
|
114
|
+
if (r.error) {
|
|
115
|
+
s.error(E("ggnomad.favorites.toast.errorTitle", "Could not update favorite"), r.error.message);
|
|
67
116
|
return;
|
|
68
117
|
}
|
|
69
|
-
let
|
|
70
|
-
|
|
118
|
+
let i = r.data?.toggleGgnomadFavorite === !0;
|
|
119
|
+
s.success(i ? E("ggnomad.favorites.toast.added", "Added to favorites") : E("ggnomad.favorites.toast.removed", "Removed from favorites"), n), await j();
|
|
71
120
|
} catch (e) {
|
|
72
|
-
|
|
121
|
+
s.error(E("ggnomad.favorites.toast.errorTitle", "Could not update favorite"), e instanceof Error ? e.message : String(e));
|
|
122
|
+
}
|
|
123
|
+
}, V = () => {
|
|
124
|
+
for (let e of T) {
|
|
125
|
+
let t = [...F.entries()].find(([t]) => t.startsWith(`${e}:`));
|
|
126
|
+
if (t) return {
|
|
127
|
+
type: e,
|
|
128
|
+
listing: t[1]
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}, H = async () => {
|
|
133
|
+
let e = V();
|
|
134
|
+
if (!e) {
|
|
135
|
+
s.warning(E("ggnomad.favorites.toast.noListingsTitle", "Nothing to favorite yet"), E("ggnomad.favorites.toast.noListingsBody", "Add a property, tour, event or activity first, then save it to your favorites."));
|
|
136
|
+
return;
|
|
73
137
|
}
|
|
138
|
+
await B(e.type, e.listing.id, e.listing.name);
|
|
74
139
|
};
|
|
75
|
-
return /* @__PURE__ */
|
|
76
|
-
title:
|
|
77
|
-
titleAccent:
|
|
78
|
-
subtitle:
|
|
79
|
-
actions: /* @__PURE__ */
|
|
140
|
+
return /* @__PURE__ */ w(i, {
|
|
141
|
+
title: E("ggnomad.favorites.title", "Favorites"),
|
|
142
|
+
titleAccent: E("ggnomad.favorites.title.accent", "Favorites"),
|
|
143
|
+
subtitle: E("ggnomad.favorites.subtitle", "Social ecosystem: saved entities across GGNomad domains"),
|
|
144
|
+
actions: /* @__PURE__ */ C(v, {
|
|
80
145
|
primary: {
|
|
81
|
-
label:
|
|
82
|
-
onSelect: () => void
|
|
83
|
-
|
|
146
|
+
label: E("ggnomad.favorites.actions.toggleDemo", "Toggle demo favorite"),
|
|
147
|
+
onSelect: () => void H(),
|
|
148
|
+
disabled: P,
|
|
149
|
+
icon: /* @__PURE__ */ C(f, {})
|
|
84
150
|
},
|
|
85
151
|
actions: []
|
|
86
152
|
}),
|
|
87
|
-
children: [/* @__PURE__ */
|
|
88
|
-
icon: /* @__PURE__ */
|
|
89
|
-
children:
|
|
90
|
-
}),
|
|
153
|
+
children: [/* @__PURE__ */ C(x, {
|
|
154
|
+
icon: /* @__PURE__ */ C(f, { className: "size-4" }),
|
|
155
|
+
children: E("ggnomad.favorites.purpose", "A personal shortlist that spans every GGNomad domain — save properties, tours, events and trips you’re considering so you can compare and come back to them before you book.")
|
|
156
|
+
}), I.length === 0 ? /* @__PURE__ */ C(b, {
|
|
91
157
|
illustration: "empty-notifications",
|
|
92
|
-
title:
|
|
93
|
-
description:
|
|
94
|
-
}) : /* @__PURE__ */
|
|
158
|
+
title: E("ggnomad.favorites.emptyTitle", "No favorites saved"),
|
|
159
|
+
description: E("ggnomad.favorites.emptyDescription", "Items you save across travel, hospitality and tourism will collect here.")
|
|
160
|
+
}) : /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C("ul", {
|
|
161
|
+
className: "flex flex-wrap gap-2",
|
|
162
|
+
children: z.map((e) => /* @__PURE__ */ C("li", { children: /* @__PURE__ */ w("button", {
|
|
163
|
+
type: "button",
|
|
164
|
+
"aria-pressed": D === e.value,
|
|
165
|
+
onClick: () => O(e.value),
|
|
166
|
+
className: a("rounded-full border px-3 py-1 text-sm transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg", D === e.value ? "border-action-primary-bg bg-action-primary-bg text-action-primary-text" : "border-border-seam bg-bg-sunken text-text-primary hover:border-border-default"),
|
|
167
|
+
children: [e.label, /* @__PURE__ */ C("span", {
|
|
168
|
+
className: "ml-1.5 tabular-nums opacity-80",
|
|
169
|
+
children: e.count
|
|
170
|
+
})]
|
|
171
|
+
}) }, e.value))
|
|
172
|
+
}), /* @__PURE__ */ w(y, {
|
|
95
173
|
treatment: "glass",
|
|
96
174
|
glow: !0,
|
|
97
175
|
className: "border-border-seam p-5",
|
|
98
|
-
children: [/* @__PURE__ */
|
|
176
|
+
children: [/* @__PURE__ */ C("h2", {
|
|
99
177
|
className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
|
|
100
|
-
children:
|
|
101
|
-
}), /* @__PURE__ */
|
|
178
|
+
children: E("ggnomad.favorites.savedItems", "Saved items")
|
|
179
|
+
}), R.length === 0 ? /* @__PURE__ */ C("p", {
|
|
180
|
+
className: "py-6 text-center text-sm text-text-secondary",
|
|
181
|
+
children: E("ggnomad.favorites.noneOfType", "Nothing saved in this category yet.")
|
|
182
|
+
}) : /* @__PURE__ */ C("div", {
|
|
102
183
|
className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-3",
|
|
103
|
-
children:
|
|
104
|
-
let t =
|
|
105
|
-
return /* @__PURE__ */
|
|
106
|
-
|
|
107
|
-
|
|
184
|
+
children: R.map((e) => {
|
|
185
|
+
let t = e.targetType.toUpperCase(), n = F.get(`${t}:${e.targetId}`), i = T.includes(t);
|
|
186
|
+
return /* @__PURE__ */ C(o, {
|
|
187
|
+
image: n?.images?.[0],
|
|
188
|
+
imageAlt: n?.name,
|
|
189
|
+
title: n?.name ?? k(e.targetType),
|
|
190
|
+
meta: n?.kind ? `${k(e.targetType)} · ${r(n.kind)}` : k(e.targetType),
|
|
191
|
+
badge: /* @__PURE__ */ C("span", {
|
|
192
|
+
className: "rounded-full bg-bg-surface/90 px-2.5 py-0.5 text-xs font-medium text-text-primary",
|
|
193
|
+
children: k(e.targetType)
|
|
194
|
+
}),
|
|
195
|
+
price: n?.price !== void 0 && n.currency ? `${n.currency} ${Number(n.price).toFixed(2)}` : void 0,
|
|
196
|
+
footer: /* @__PURE__ */ w("div", {
|
|
197
|
+
className: "space-y-2",
|
|
198
|
+
children: [n ? /* @__PURE__ */ C(c, {
|
|
199
|
+
rating: n.rating,
|
|
200
|
+
reviewCount: n.reviewCount
|
|
201
|
+
}) : /* @__PURE__ */ C("span", {
|
|
202
|
+
className: "text-sm text-text-secondary",
|
|
203
|
+
children: E("ggnomad.favorites.savedListing", "Saved listing")
|
|
204
|
+
}), /* @__PURE__ */ w("div", {
|
|
205
|
+
className: "flex flex-wrap items-center justify-between gap-2",
|
|
206
|
+
children: [n?.path ? /* @__PURE__ */ w(h, {
|
|
207
|
+
to: n.path,
|
|
208
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium text-action-primary-bg hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg",
|
|
209
|
+
children: [/* @__PURE__ */ C(d, {
|
|
210
|
+
className: "size-3.5",
|
|
211
|
+
"aria-hidden": "true"
|
|
212
|
+
}), E("ggnomad.favorites.view", "View details")]
|
|
213
|
+
}) : /* @__PURE__ */ C("span", {}), i ? /* @__PURE__ */ w("button", {
|
|
214
|
+
type: "button",
|
|
215
|
+
disabled: P,
|
|
216
|
+
onClick: () => void B(t, e.targetId, n?.name ?? k(e.targetType)),
|
|
217
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium text-text-secondary hover:bg-bg-sunken hover:text-status-error-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg disabled:opacity-60",
|
|
218
|
+
children: [/* @__PURE__ */ C(p, {
|
|
219
|
+
className: "size-3.5",
|
|
220
|
+
"aria-hidden": "true"
|
|
221
|
+
}), E("ggnomad.favorites.remove", "Remove")]
|
|
222
|
+
}) : null]
|
|
223
|
+
})]
|
|
224
|
+
})
|
|
108
225
|
}, e.id);
|
|
109
226
|
})
|
|
110
227
|
})]
|
|
111
|
-
})]
|
|
228
|
+
})] })]
|
|
112
229
|
});
|
|
113
230
|
}
|
|
114
231
|
//#endregion
|
|
115
|
-
export {
|
|
232
|
+
export { E as FavoritesPage };
|
|
116
233
|
|
|
117
234
|
//# sourceMappingURL=FavoritesPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FavoritesPage.js","names":[],"sources":["../../../src/ggnomad/pages/FavoritesPage.tsx"],"sourcesContent":["import { useMemo } 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 { Heart } from 'lucide-react';\nimport {\n CTAOverflowMenu,\n GlassCard,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n GGNOMAD_FAVORITES_QUERY,\n GGNOMAD_FAVORITE_TARGETS_QUERY,\n TOGGLE_GGNOMAD_FAVORITE_MUTATION,\n} from '../graphql';\nimport { PageShell } from './PageShell';\nimport { EntityCard } from '../components';\nimport { notify } from '../utils/notify';\n\ntype FavoriteType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ntype Listing = { id: string; name: string };\n\nexport function FavoritesPage() {\n const { t } = useI18n();\n\n const typeLabel = (type: string): string => {\n switch (type.toUpperCase()) {\n case 'PROPERTY':\n return t('ggnomad.favorites.type.property', 'Property');\n case 'TOUR':\n return t('ggnomad.favorites.type.tour', 'Tour');\n case 'EVENT':\n return t('ggnomad.favorites.type.event', 'Event');\n case 'ACTIVITY':\n return t('ggnomad.favorites.type.activity', 'Activity');\n case 'TRIP':\n return t('ggnomad.favorites.type.trip', 'Trip');\n default:\n return t('ggnomad.favorites.type.saved', 'Saved item');\n }\n };\n\n const { data, refetch } = useQuery(\n GGNOMAD_FAVORITES_QUERY as TypedDocumentNode<\n { ggnomadFavorites: Array<{ id: string; targetType: string; targetId: string }> },\n Record<string, never>\n >\n );\n\n // Real listing names so a saved favorite reads as its entity name instead of\n // a raw enum + UUID (the backend only stores targetType + targetId).\n const { data: targetsData } = useQuery(\n GGNOMAD_FAVORITE_TARGETS_QUERY as TypedDocumentNode<\n {\n ggnomadProperties: Listing[];\n ggnomadTours: Listing[];\n ggnomadEvents: Listing[];\n ggnomadActivities: Listing[];\n },\n Record<string, never>\n >\n );\n\n const [toggleFavorite] = useMutation(\n TOGGLE_GGNOMAD_FAVORITE_MUTATION as TypedDocumentNode<\n { toggleGgnomadFavorite: boolean },\n { input: { targetType: FavoriteType; targetId: string } }\n >\n );\n\n const nameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const listing of [\n ...(targetsData?.ggnomadProperties ?? []),\n ...(targetsData?.ggnomadTours ?? []),\n ...(targetsData?.ggnomadEvents ?? []),\n ...(targetsData?.ggnomadActivities ?? []),\n ]) {\n map.set(listing.id, listing.name);\n }\n return map;\n }, [targetsData]);\n\n const favorites = (data?.ggnomadFavorites ?? []) as Array<{\n id: string;\n targetType: string;\n targetId: string;\n }>;\n\n /** First available real listing, in priority order, mapped to a favorite type. */\n const resolveDemoTarget = (): { type: FavoriteType; listing: Listing } | null => {\n const property = targetsData?.ggnomadProperties?.[0];\n if (property) return { type: 'PROPERTY', listing: property };\n const tour = targetsData?.ggnomadTours?.[0];\n if (tour) return { type: 'TOUR', listing: tour };\n const event = targetsData?.ggnomadEvents?.[0];\n if (event) return { type: 'EVENT', listing: event };\n const activity = targetsData?.ggnomadActivities?.[0];\n if (activity) return { type: 'ACTIVITY', listing: activity };\n return null;\n };\n\n const handleToggleDemo = async () => {\n const target = resolveDemoTarget();\n if (!target) {\n notify.warning(\n t('ggnomad.favorites.toast.noListingsTitle', 'Nothing to favorite yet'),\n t(\n 'ggnomad.favorites.toast.noListingsBody',\n 'Add a property, tour, event or activity first, then save it to your favorites.'\n )\n );\n return;\n }\n const { type, listing } = target;\n try {\n const result = await toggleFavorite({\n variables: { input: { targetType: type, targetId: listing.id } },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.favorites.toast.errorTitle', 'Could not update favorite'),\n result.error.message\n );\n return;\n }\n const added = result.data?.toggleGgnomadFavorite === true;\n notify.success(\n added\n ? t('ggnomad.favorites.toast.added', 'Added to favorites')\n : t('ggnomad.favorites.toast.removed', 'Removed from favorites'),\n listing.name\n );\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.favorites.toast.errorTitle', 'Could not update favorite'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n return (\n <PageShell\n title={t('ggnomad.favorites.title', 'Favorites')}\n titleAccent={t('ggnomad.favorites.title.accent', 'Favorites')}\n subtitle={t(\n 'ggnomad.favorites.subtitle',\n 'Social ecosystem: saved entities across GGNomad domains'\n )}\n actions={\n <CTAOverflowMenu\n primary={{\n label: t('ggnomad.favorites.actions.toggleDemo', 'Toggle demo favorite'),\n onSelect: () => void handleToggleDemo(),\n icon: <Heart />,\n }}\n actions={[]}\n />\n }\n >\n <PagePurpose icon={<Heart className=\"size-4\" />}>\n {t(\n 'ggnomad.favorites.purpose',\n 'A personal shortlist that spans every GGNomad domain — save properties, tours, events and trips you’re considering so you can compare and come back to them before you book.'\n )}\n </PagePurpose>\n\n {favorites.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={t('ggnomad.favorites.emptyTitle', 'No favorites saved')}\n description={t(\n 'ggnomad.favorites.emptyDescription',\n 'Items you save across travel, hospitality and tourism will collect here.'\n )}\n />\n ) : (\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.favorites.savedItems', 'Saved items')}\n </h2>\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {favorites.map((favorite) => {\n const resolvedName = nameById.get(favorite.targetId);\n return (\n <EntityCard\n key={favorite.id}\n title={resolvedName ?? typeLabel(favorite.targetType)}\n meta={\n resolvedName\n ? typeLabel(favorite.targetType)\n : t('ggnomad.favorites.savedListing', 'Saved listing')\n }\n />\n );\n })}\n </div>\n </GlassCard>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FavoritesPage.js","names":[],"sources":["../../../src/ggnomad/pages/FavoritesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { Link } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowUpRight, Heart, HeartOff } from 'lucide-react';\nimport {\n CTAOverflowMenu,\n GlassCard,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n GGNOMAD_FAVORITES_QUERY,\n GGNOMAD_FAVORITE_TARGETS_QUERY,\n TOGGLE_GGNOMAD_FAVORITE_MUTATION,\n} from '../graphql';\nimport { humanizeEnum } from '../utils/format';\nimport { cn } from '../utils/cn';\nimport { PageShell } from './PageShell';\nimport { EntityCard, RatingSummary } from '../components';\nimport { notify } from '../utils/notify';\n\ntype FavoriteType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY' | 'TRIP';\n\n/** A favouritable listing, normalised across the five target types. */\ntype Target = {\n id: string;\n name: string;\n kind?: string;\n price?: number;\n currency?: string;\n rating?: number | null;\n reviewCount?: number;\n images?: string[];\n /** Route to the listing's detail page, relative to this page. Trips have none. */\n path?: string;\n};\n\ntype Favorite = {\n id: string;\n targetType: string;\n targetId: string;\n createdAt?: string;\n};\n\nconst TOGGLEABLE_TYPES: FavoriteType[] = ['PROPERTY', 'TOUR', 'EVENT', 'ACTIVITY'];\n\nexport function FavoritesPage() {\n const { t } = useI18n();\n const [typeFilter, setTypeFilter] = useState<FavoriteType | 'ALL'>('ALL');\n\n const typeLabel = (type: string): string => {\n switch (type.toUpperCase()) {\n case 'PROPERTY':\n return t('ggnomad.favorites.type.property', 'Property');\n case 'TOUR':\n return t('ggnomad.favorites.type.tour', 'Tour');\n case 'EVENT':\n return t('ggnomad.favorites.type.event', 'Event');\n case 'ACTIVITY':\n return t('ggnomad.favorites.type.activity', 'Activity');\n case 'TRIP':\n return t('ggnomad.favorites.type.trip', 'Trip');\n default:\n return t('ggnomad.favorites.type.saved', 'Saved item');\n }\n };\n\n const { data, refetch } = useQuery(\n GGNOMAD_FAVORITES_QUERY as TypedDocumentNode<\n { ggnomadFavorites: Favorite[] },\n Record<string, never>\n >\n );\n\n // The backend stores a favorite as targetType + targetId only, so the listing\n // catalogue is joined client-side to show the real name, cover photo, price and\n // rating instead of a bare enum + UUID.\n const { data: targetsData } = useQuery(\n GGNOMAD_FAVORITE_TARGETS_QUERY as TypedDocumentNode<\n {\n ggnomadProperties: Array<{\n id: string;\n name: string;\n type: string;\n pricePerNight: number;\n currency: string;\n rating?: number | null;\n reviewCount: number;\n images: string[];\n }>;\n ggnomadTours: Array<{\n id: string;\n name: string;\n type: string;\n price: number;\n currency: string;\n rating?: number | null;\n reviewCount: number;\n images: string[];\n }>;\n ggnomadEvents: Array<{\n id: string;\n name: string;\n category: string;\n price: number;\n currency: string;\n rating?: number | null;\n reviewCount: number;\n images: string[];\n }>;\n ggnomadActivities: Array<{\n id: string;\n name: string;\n category: string;\n price: number;\n currency: string;\n rating?: number | null;\n reviewCount: number;\n images: string[];\n }>;\n ggnomadTrips: Array<{\n id: string;\n name: string;\n transportMode: string;\n price: number;\n currency: string;\n }>;\n },\n Record<string, never>\n >\n );\n\n const [toggleFavorite, { loading: toggling }] = useMutation(\n TOGGLE_GGNOMAD_FAVORITE_MUTATION as TypedDocumentNode<\n { toggleGgnomadFavorite: boolean },\n { input: { targetType: FavoriteType; targetId: string } }\n >\n );\n\n const targetsByType = useMemo(() => {\n const map = new Map<string, Target>();\n for (const property of targetsData?.ggnomadProperties ?? []) {\n map.set(`PROPERTY:${property.id}`, {\n id: property.id,\n name: property.name,\n kind: property.type,\n price: property.pricePerNight,\n currency: property.currency,\n rating: property.rating,\n reviewCount: property.reviewCount,\n images: property.images,\n path: `../hospitality/${property.id}`,\n });\n }\n for (const tour of targetsData?.ggnomadTours ?? []) {\n map.set(`TOUR:${tour.id}`, {\n id: tour.id,\n name: tour.name,\n kind: tour.type,\n price: tour.price,\n currency: tour.currency,\n rating: tour.rating,\n reviewCount: tour.reviewCount,\n images: tour.images,\n path: `../tourism/tours/${tour.id}`,\n });\n }\n for (const event of targetsData?.ggnomadEvents ?? []) {\n map.set(`EVENT:${event.id}`, {\n id: event.id,\n name: event.name,\n kind: event.category,\n price: event.price,\n currency: event.currency,\n rating: event.rating,\n reviewCount: event.reviewCount,\n images: event.images,\n path: `../entertainment/${event.id}`,\n });\n }\n for (const activity of targetsData?.ggnomadActivities ?? []) {\n map.set(`ACTIVITY:${activity.id}`, {\n id: activity.id,\n name: activity.name,\n kind: activity.category,\n price: activity.price,\n currency: activity.currency,\n rating: activity.rating,\n reviewCount: activity.reviewCount,\n images: activity.images,\n path: `../tourism/activities/${activity.id}`,\n });\n }\n for (const trip of targetsData?.ggnomadTrips ?? []) {\n map.set(`TRIP:${trip.id}`, {\n id: trip.id,\n name: trip.name,\n kind: trip.transportMode,\n price: trip.price,\n currency: trip.currency,\n });\n }\n return map;\n }, [targetsData]);\n\n const favorites = useMemo(() => data?.ggnomadFavorites ?? [], [data?.ggnomadFavorites]);\n\n const counts = useMemo(() => {\n const tally = new Map<string, number>();\n for (const favorite of favorites) {\n const key = favorite.targetType.toUpperCase();\n tally.set(key, (tally.get(key) ?? 0) + 1);\n }\n return tally;\n }, [favorites]);\n\n const visible = favorites.filter(\n (favorite) => typeFilter === 'ALL' || favorite.targetType.toUpperCase() === typeFilter\n );\n\n const chips: Array<{ value: FavoriteType | 'ALL'; label: string; count: number }> = [\n { value: 'ALL', label: t('ggnomad.favorites.filter.all', 'All'), count: favorites.length },\n ...(['PROPERTY', 'TOUR', 'EVENT', 'ACTIVITY', 'TRIP'] as FavoriteType[]).map((type) => ({\n value: type,\n label: typeLabel(type),\n count: counts.get(type) ?? 0,\n })),\n ];\n\n const runToggle = async (type: FavoriteType, targetId: string, label: string) => {\n try {\n const result = await toggleFavorite({\n variables: { input: { targetType: type, targetId } },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.favorites.toast.errorTitle', 'Could not update favorite'),\n result.error.message\n );\n return;\n }\n const added = result.data?.toggleGgnomadFavorite === true;\n notify.success(\n added\n ? t('ggnomad.favorites.toast.added', 'Added to favorites')\n : t('ggnomad.favorites.toast.removed', 'Removed from favorites'),\n label\n );\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.favorites.toast.errorTitle', 'Could not update favorite'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n /** First available real listing, in priority order, mapped to a favorite type. */\n const resolveDemoTarget = (): { type: FavoriteType; listing: Target } | null => {\n for (const type of TOGGLEABLE_TYPES) {\n const match = [...targetsByType.entries()].find(([key]) => key.startsWith(`${type}:`));\n if (match) return { type, listing: match[1] };\n }\n return null;\n };\n\n const handleToggleDemo = async () => {\n const target = resolveDemoTarget();\n if (!target) {\n notify.warning(\n t('ggnomad.favorites.toast.noListingsTitle', 'Nothing to favorite yet'),\n t(\n 'ggnomad.favorites.toast.noListingsBody',\n 'Add a property, tour, event or activity first, then save it to your favorites.'\n )\n );\n return;\n }\n await runToggle(target.type, target.listing.id, target.listing.name);\n };\n\n return (\n <PageShell\n title={t('ggnomad.favorites.title', 'Favorites')}\n titleAccent={t('ggnomad.favorites.title.accent', 'Favorites')}\n subtitle={t(\n 'ggnomad.favorites.subtitle',\n 'Social ecosystem: saved entities across GGNomad domains'\n )}\n actions={\n <CTAOverflowMenu\n primary={{\n label: t('ggnomad.favorites.actions.toggleDemo', 'Toggle demo favorite'),\n onSelect: () => void handleToggleDemo(),\n disabled: toggling,\n icon: <Heart />,\n }}\n actions={[]}\n />\n }\n >\n <PagePurpose icon={<Heart className=\"size-4\" />}>\n {t(\n 'ggnomad.favorites.purpose',\n 'A personal shortlist that spans every GGNomad domain — save properties, tours, events and trips you’re considering so you can compare and come back to them before you book.'\n )}\n </PagePurpose>\n\n {favorites.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={t('ggnomad.favorites.emptyTitle', 'No favorites saved')}\n description={t(\n 'ggnomad.favorites.emptyDescription',\n 'Items you save across travel, hospitality and tourism will collect here.'\n )}\n />\n ) : (\n <>\n <ul className=\"flex flex-wrap gap-2\">\n {chips.map((chip) => (\n <li key={chip.value}>\n <button\n type=\"button\"\n aria-pressed={typeFilter === chip.value}\n onClick={() => setTypeFilter(chip.value)}\n className={cn(\n 'rounded-full border px-3 py-1 text-sm transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg',\n typeFilter === chip.value\n ? 'border-action-primary-bg bg-action-primary-bg text-action-primary-text'\n : 'border-border-seam bg-bg-sunken text-text-primary hover:border-border-default'\n )}\n >\n {chip.label}\n <span className=\"ml-1.5 tabular-nums opacity-80\">{chip.count}</span>\n </button>\n </li>\n ))}\n </ul>\n\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.favorites.savedItems', 'Saved items')}\n </h2>\n {visible.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {t('ggnomad.favorites.noneOfType', 'Nothing saved in this category yet.')}\n </p>\n ) : (\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {visible.map((favorite) => {\n const type = favorite.targetType.toUpperCase();\n const target = targetsByType.get(`${type}:${favorite.targetId}`);\n const removable = (TOGGLEABLE_TYPES as string[]).includes(type);\n return (\n <EntityCard\n key={favorite.id}\n image={target?.images?.[0]}\n imageAlt={target?.name}\n title={target?.name ?? typeLabel(favorite.targetType)}\n meta={\n target?.kind\n ? `${typeLabel(favorite.targetType)} · ${humanizeEnum(target.kind)}`\n : typeLabel(favorite.targetType)\n }\n badge={\n <span className=\"rounded-full bg-bg-surface/90 px-2.5 py-0.5 text-xs font-medium text-text-primary\">\n {typeLabel(favorite.targetType)}\n </span>\n }\n price={\n target?.price !== undefined && target.currency\n ? `${target.currency} ${Number(target.price).toFixed(2)}`\n : undefined\n }\n footer={\n <div className=\"space-y-2\">\n {target ? (\n <RatingSummary\n rating={target.rating}\n reviewCount={target.reviewCount}\n />\n ) : (\n <span className=\"text-sm text-text-secondary\">\n {t('ggnomad.favorites.savedListing', 'Saved listing')}\n </span>\n )}\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n {target?.path ? (\n <Link\n to={target.path}\n className=\"inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium text-action-primary-bg hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg\"\n >\n <ArrowUpRight className=\"size-3.5\" aria-hidden=\"true\" />\n {t('ggnomad.favorites.view', 'View details')}\n </Link>\n ) : (\n <span />\n )}\n {removable ? (\n <button\n type=\"button\"\n disabled={toggling}\n onClick={() =>\n void runToggle(\n type as FavoriteType,\n favorite.targetId,\n target?.name ?? typeLabel(favorite.targetType)\n )\n }\n className=\"inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium text-text-secondary hover:bg-bg-sunken hover:text-status-error-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg disabled:opacity-60\"\n >\n <HeartOff className=\"size-3.5\" aria-hidden=\"true\" />\n {t('ggnomad.favorites.remove', 'Remove')}\n </button>\n ) : null}\n </div>\n </div>\n }\n />\n );\n })}\n </div>\n )}\n </GlassCard>\n </>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HospitalityPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/HospitalityPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HospitalityPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/HospitalityPage.tsx"],"names":[],"mappings":"AAiCA,wBAAgB,eAAe,gCA6I9B"}
|
|
@@ -2,66 +2,109 @@ import { GGNOMAD_PROPERTIES_QUERY as e } from "../graphql.js";
|
|
|
2
2
|
import { PageShell as t } from "./PageShell.js";
|
|
3
3
|
import { EntityCard as n } from "../components/EntityCard.js";
|
|
4
4
|
import { LoadMoreButton as r } from "../components/LoadMoreButton.js";
|
|
5
|
+
import { RatingSummary as i } from "../components/RatingSummary.js";
|
|
6
|
+
import { EMPTY_LISTING_FILTERS as a, ListingFilters as o, hasActiveListingFilters as s } from "../components/ListingFilters.js";
|
|
5
7
|
import "../components/index.js";
|
|
6
|
-
import { useState as
|
|
7
|
-
import { Hotel as
|
|
8
|
-
import { useI18n as
|
|
9
|
-
import { useQuery as
|
|
10
|
-
import { GlassCard as
|
|
11
|
-
import { jsx as
|
|
8
|
+
import { useState as c } from "react";
|
|
9
|
+
import { Hotel as l, Leaf as u, Users as d, Zap as f } from "lucide-react";
|
|
10
|
+
import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
11
|
+
import { useQuery as m } from "@apollo/client/react";
|
|
12
|
+
import { GlassCard as h, IllustratedEmptyState as g, PagePurpose as _ } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
import { jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
12
14
|
//#region src/ggnomad/pages/HospitalityPage.tsx
|
|
13
|
-
function
|
|
14
|
-
let { t:
|
|
15
|
-
limit:
|
|
16
|
-
offset: 0
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
15
|
+
function b() {
|
|
16
|
+
let { t: b } = p(), [x, S] = c(12), [C, w] = c(a), { data: T, loading: E, error: D } = m(e, { variables: {
|
|
17
|
+
limit: x,
|
|
18
|
+
offset: 0,
|
|
19
|
+
status: C.status,
|
|
20
|
+
minSustainabilityScore: C.minSustainabilityScore,
|
|
21
|
+
hasAccessibilityFeatures: C.accessibilityFeatures.length > 0 ? C.accessibilityFeatures : void 0
|
|
22
|
+
} }), O = T?.ggnomadProperties ?? [], k = s(C);
|
|
23
|
+
return /* @__PURE__ */ y(t, {
|
|
24
|
+
title: b("ggnomad.hospitality.title", "Hospitality"),
|
|
25
|
+
titleAccent: b("ggnomad.hospitality.title.accent", "Hospitality"),
|
|
26
|
+
subtitle: b("ggnomad.hospitality.subtitle", "Property discovery powered by live GraphQL data"),
|
|
27
|
+
children: [
|
|
28
|
+
/* @__PURE__ */ v(_, {
|
|
29
|
+
icon: /* @__PURE__ */ v(l, { className: "size-4" }),
|
|
30
|
+
children: b("ggnomad.hospitality.purpose", "Discover places to stay — hotels, homestays and short-stay rentals. Open any property to review its nightly price, guest capacity and rating, then create a booking for the dates you need.")
|
|
31
|
+
}),
|
|
32
|
+
/* @__PURE__ */ v(o, {
|
|
33
|
+
idPrefix: "ggnomad-hospitality",
|
|
34
|
+
value: C,
|
|
35
|
+
onChange: (e) => {
|
|
36
|
+
S(12), w(e);
|
|
37
|
+
},
|
|
38
|
+
resultCount: O.length
|
|
39
|
+
}),
|
|
40
|
+
D ? /* @__PURE__ */ v(g, {
|
|
41
|
+
illustration: "empty-data",
|
|
42
|
+
title: b("ggnomad.hospitality.errorTitle", "Could not load properties"),
|
|
43
|
+
description: D.message
|
|
44
|
+
}) : O.length === 0 ? /* @__PURE__ */ v(g, {
|
|
45
|
+
illustration: "empty-data",
|
|
46
|
+
title: E ? b("ggnomad.hospitality.loadingTitle", "Loading properties…") : k ? b("ggnomad.hospitality.noMatchTitle", "No properties match these filters") : b("ggnomad.hospitality.emptyTitle", "No properties available"),
|
|
47
|
+
description: E ? b("ggnomad.hospitality.loadingDescription", "Fetching the live property catalogue.") : k ? b("ggnomad.hospitality.noMatchDescription", "Try clearing a filter — status, sustainability score and accessibility all narrow the catalogue.") : b("ggnomad.hospitality.emptyDescription", "Properties published by providers will be discoverable here.")
|
|
48
|
+
}) : /* @__PURE__ */ y(h, {
|
|
49
|
+
treatment: "glass",
|
|
50
|
+
glow: !0,
|
|
51
|
+
className: "border-border-seam p-5",
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ v("h2", {
|
|
54
|
+
className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
|
|
55
|
+
children: b("ggnomad.hospitality.featuredStays", "Featured stays")
|
|
56
|
+
}),
|
|
57
|
+
/* @__PURE__ */ v("div", {
|
|
58
|
+
className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-3",
|
|
59
|
+
children: O.map((e) => /* @__PURE__ */ v(n, {
|
|
60
|
+
to: e.id,
|
|
61
|
+
image: e.images?.[0],
|
|
62
|
+
imageAlt: e.name,
|
|
63
|
+
title: e.name,
|
|
64
|
+
meta: e.type,
|
|
65
|
+
badge: e.instantBook ? /* @__PURE__ */ y("span", {
|
|
66
|
+
className: "inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text",
|
|
67
|
+
children: [/* @__PURE__ */ v(f, {
|
|
68
|
+
className: "size-3",
|
|
69
|
+
"aria-hidden": "true"
|
|
70
|
+
}), b("ggnomad.listing.instantBook", "Instant book")]
|
|
71
|
+
}) : null,
|
|
72
|
+
price: `${e.currency} ${Number(e.pricePerNight).toFixed(2)} ${b("ggnomad.hospitality.card.perNight", "per night")}`,
|
|
73
|
+
footer: /* @__PURE__ */ y("div", {
|
|
74
|
+
className: "flex flex-wrap items-center gap-x-3 gap-y-1",
|
|
75
|
+
children: [
|
|
76
|
+
/* @__PURE__ */ v(i, {
|
|
77
|
+
rating: e.rating,
|
|
78
|
+
reviewCount: e.reviewCount
|
|
79
|
+
}),
|
|
80
|
+
/* @__PURE__ */ y("span", {
|
|
81
|
+
className: "inline-flex items-center gap-1 tabular-nums",
|
|
82
|
+
children: [/* @__PURE__ */ v(d, {
|
|
83
|
+
className: "size-3.5",
|
|
84
|
+
"aria-hidden": "true"
|
|
85
|
+
}), b("ggnomad.hospitality.card.guests", "Up to {{n}}").replace("{{n}}", String(e.maxGuests))]
|
|
86
|
+
}),
|
|
87
|
+
e.sustainabilityScore > 0 ? /* @__PURE__ */ y("span", {
|
|
88
|
+
className: "inline-flex items-center gap-1 tabular-nums text-status-success-text",
|
|
89
|
+
children: [/* @__PURE__ */ v(u, {
|
|
90
|
+
className: "size-3.5",
|
|
91
|
+
"aria-hidden": "true"
|
|
92
|
+
}), e.sustainabilityScore]
|
|
93
|
+
}) : null
|
|
94
|
+
]
|
|
95
|
+
})
|
|
96
|
+
}, e.id))
|
|
97
|
+
}),
|
|
98
|
+
/* @__PURE__ */ v(r, {
|
|
99
|
+
visible: O.length >= x,
|
|
100
|
+
onLoadMore: () => S((e) => e + 12)
|
|
101
|
+
})
|
|
102
|
+
]
|
|
103
|
+
})
|
|
104
|
+
]
|
|
62
105
|
});
|
|
63
106
|
}
|
|
64
107
|
//#endregion
|
|
65
|
-
export {
|
|
108
|
+
export { b as HospitalityPage };
|
|
66
109
|
|
|
67
110
|
//# sourceMappingURL=HospitalityPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HospitalityPage.js","names":[],"sources":["../../../src/ggnomad/pages/HospitalityPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Hotel,
|
|
1
|
+
{"version":3,"file":"HospitalityPage.js","names":[],"sources":["../../../src/ggnomad/pages/HospitalityPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Hotel, Leaf, Users, Zap } from 'lucide-react';\nimport { GlassCard, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_PROPERTIES_QUERY } from '../graphql';\nimport { PageShell } from './PageShell';\nimport {\n EntityCard,\n EMPTY_LISTING_FILTERS,\n ListingFilters,\n LoadMoreButton,\n RatingSummary,\n hasActiveListingFilters,\n} from '../components';\nimport type { ListingFilterState } from '../components';\n\ntype PropertyRow = {\n id: string;\n name: string;\n type: string;\n pricePerNight: number;\n currency: string;\n rating?: number | null;\n reviewCount: number;\n maxGuests: number;\n images?: string[];\n instantBook: boolean;\n sustainabilityScore: number;\n};\n\nexport function HospitalityPage() {\n const { t } = useI18n();\n const [limit, setLimit] = useState(12);\n const [filters, setFilters] = useState<ListingFilterState>(EMPTY_LISTING_FILTERS);\n\n const { data, loading, error } = useQuery(\n GGNOMAD_PROPERTIES_QUERY as TypedDocumentNode<\n { ggnomadProperties: PropertyRow[] },\n {\n limit: number;\n offset: number;\n status?: string;\n minSustainabilityScore?: number;\n hasAccessibilityFeatures?: string[];\n }\n >,\n {\n variables: {\n limit,\n offset: 0,\n status: filters.status,\n minSustainabilityScore: filters.minSustainabilityScore,\n hasAccessibilityFeatures:\n filters.accessibilityFeatures.length > 0 ? filters.accessibilityFeatures : undefined,\n },\n }\n );\n const properties = data?.ggnomadProperties ?? [];\n const filtered = hasActiveListingFilters(filters);\n\n return (\n <PageShell\n title={t('ggnomad.hospitality.title', 'Hospitality')}\n titleAccent={t('ggnomad.hospitality.title.accent', 'Hospitality')}\n subtitle={t(\n 'ggnomad.hospitality.subtitle',\n 'Property discovery powered by live GraphQL data'\n )}\n >\n <PagePurpose icon={<Hotel className=\"size-4\" />}>\n {t(\n 'ggnomad.hospitality.purpose',\n 'Discover places to stay — hotels, homestays and short-stay rentals. Open any property to review its nightly price, guest capacity and rating, then create a booking for the dates you need.'\n )}\n </PagePurpose>\n\n <ListingFilters\n idPrefix=\"ggnomad-hospitality\"\n value={filters}\n onChange={(next) => {\n setLimit(12);\n setFilters(next);\n }}\n resultCount={properties.length}\n />\n\n {error ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.hospitality.errorTitle', 'Could not load properties')}\n description={error.message}\n />\n ) : properties.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={\n loading\n ? t('ggnomad.hospitality.loadingTitle', 'Loading properties…')\n : filtered\n ? t('ggnomad.hospitality.noMatchTitle', 'No properties match these filters')\n : t('ggnomad.hospitality.emptyTitle', 'No properties available')\n }\n description={\n loading\n ? t('ggnomad.hospitality.loadingDescription', 'Fetching the live property catalogue.')\n : filtered\n ? t(\n 'ggnomad.hospitality.noMatchDescription',\n 'Try clearing a filter — status, sustainability score and accessibility all narrow the catalogue.'\n )\n : t(\n 'ggnomad.hospitality.emptyDescription',\n 'Properties published by providers will be discoverable here.'\n )\n }\n />\n ) : (\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.hospitality.featuredStays', 'Featured stays')}\n </h2>\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {properties.map((property) => (\n <EntityCard\n key={property.id}\n to={property.id}\n image={property.images?.[0]}\n imageAlt={property.name}\n title={property.name}\n meta={property.type}\n badge={\n property.instantBook ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text\">\n <Zap className=\"size-3\" aria-hidden=\"true\" />\n {t('ggnomad.listing.instantBook', 'Instant book')}\n </span>\n ) : null\n }\n price={`${property.currency} ${Number(property.pricePerNight).toFixed(2)} ${t(\n 'ggnomad.hospitality.card.perNight',\n 'per night'\n )}`}\n footer={\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1\">\n <RatingSummary rating={property.rating} reviewCount={property.reviewCount} />\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <Users className=\"size-3.5\" aria-hidden=\"true\" />\n {t('ggnomad.hospitality.card.guests', 'Up to {{n}}').replace(\n '{{n}}',\n String(property.maxGuests)\n )}\n </span>\n {property.sustainabilityScore > 0 ? (\n <span className=\"inline-flex items-center gap-1 tabular-nums text-status-success-text\">\n <Leaf className=\"size-3.5\" aria-hidden=\"true\" />\n {property.sustainabilityScore}\n </span>\n ) : null}\n </div>\n }\n />\n ))}\n </div>\n <LoadMoreButton\n visible={properties.length >= limit}\n onLoadMore={() => setLimit((l) => l + 12)}\n />\n </GlassCard>\n )}\n </PageShell>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAiCA,SAAgB,IAAkB;CAChC,IAAM,EAAE,SAAM,EAAQ,GAChB,CAAC,GAAO,KAAY,EAAS,EAAE,GAC/B,CAAC,GAAS,KAAc,EAA6B,CAAqB,GAE1E,EAAE,SAAM,YAAS,aAAU,EAC/B,GAUA,EACE,WAAW;EACT;EACA,QAAQ;EACR,QAAQ,EAAQ;EAChB,wBAAwB,EAAQ;EAChC,0BACE,EAAQ,sBAAsB,SAAS,IAAI,EAAQ,wBAAwB,KAAA;CAC/E,EACF,CACF,GACM,IAAa,GAAM,qBAAqB,CAAC,GACzC,IAAW,EAAwB,CAAO;CAEhD,OACE,kBAAC,GAAD;EACE,OAAO,EAAE,6BAA6B,aAAa;EACnD,aAAa,EAAE,oCAAoC,aAAa;EAChE,UAAU,EACR,gCACA,iDACF;YANF;GAQE,kBAAC,GAAD;IAAa,MAAM,kBAAC,GAAD,EAAO,WAAU,SAAU,CAAA;cAC3C,EACC,+BACA,6LACF;GACW,CAAA;GAEb,kBAAC,GAAD;IACE,UAAS;IACT,OAAO;IACP,WAAW,MAAS;KAElB,AADA,EAAS,EAAE,GACX,EAAW,CAAI;IACjB;IACA,aAAa,EAAW;GACzB,CAAA;GAEA,IACC,kBAAC,GAAD;IACE,cAAa;IACb,OAAO,EAAE,kCAAkC,2BAA2B;IACtE,aAAa,EAAM;GACpB,CAAA,IACC,EAAW,WAAW,IACxB,kBAAC,GAAD;IACE,cAAa;IACb,OACE,IACI,EAAE,oCAAoC,qBAAqB,IAC3D,IACE,EAAE,oCAAoC,mCAAmC,IACzE,EAAE,kCAAkC,yBAAyB;IAErE,aACE,IACI,EAAE,0CAA0C,uCAAuC,IACnF,IACE,EACE,0CACA,kGACF,IACA,EACE,wCACA,8DACF;GAET,CAAA,IAED,kBAAC,GAAD;IAAW,WAAU;IAAQ,MAAA;IAAK,WAAU;cAA5C;KACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAE,qCAAqC,gBAAgB;KACtD,CAAA;KACJ,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAW,KAAK,MACf,kBAAC,GAAD;OAEE,IAAI,EAAS;OACb,OAAO,EAAS,SAAS;OACzB,UAAU,EAAS;OACnB,OAAO,EAAS;OAChB,MAAM,EAAS;OACf,OACE,EAAS,cACP,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,GAAD;SAAK,WAAU;SAAS,eAAY;QAAQ,CAAA,GAC3C,EAAE,+BAA+B,cAAc,CAC5C;YACJ;OAEN,OAAO,GAAG,EAAS,SAAS,GAAG,OAAO,EAAS,aAAa,EAAE,QAAQ,CAAC,EAAE,GAAG,EAC1E,qCACA,WACF;OACA,QACE,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD;UAAe,QAAQ,EAAS;UAAQ,aAAa,EAAS;SAAc,CAAA;SAC5E,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACE,kBAAC,GAAD;WAAO,WAAU;WAAW,eAAY;UAAQ,CAAA,GAC/C,EAAE,mCAAmC,aAAa,EAAE,QACnD,SACA,OAAO,EAAS,SAAS,CAC3B,CACI;;SACL,EAAS,sBAAsB,IAC9B,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACE,kBAAC,GAAD;WAAM,WAAU;WAAW,eAAY;UAAQ,CAAA,GAC9C,EAAS,mBACN;cACJ;QACD;;MAER,GApCM,EAAS,EAoCf,CACF;KACE,CAAA;KACL,kBAAC,GAAD;MACE,SAAS,EAAW,UAAU;MAC9B,kBAAkB,GAAU,MAAM,IAAI,EAAE;KACzC,CAAA;IACQ;;EAEJ;;AAEf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PropertyDetailsPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/PropertyDetailsPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PropertyDetailsPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/PropertyDetailsPage.tsx"],"names":[],"mappings":"AAiEA,wBAAgB,mBAAmB,gCAsOlC"}
|