Skip to content

Commit 4d61801

Browse files
author
Herve Tribouilloy
committed
Added a new state to handle keystone graphql call using runtime config rather than environment variables
1 parent 8ec58c6 commit 4d61801

28 files changed

Lines changed: 321 additions & 101 deletions

vite_project/index.html

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,23 @@
44
<body>
55
<script type="module" src="./src/widget.ts"></script>
66

7-
<bookingsystem-widget></bookingsystem-widget>
7+
<bookingsystem-widget
8+
data-venue="green-edge"
9+
></bookingsystem-widget>
10+
11+
<script type="application/json" id="reactedge-config">
12+
{
13+
"widgets": {
14+
"booking": {
15+
"api": "https://booking-api.local/api/graphql/"
16+
}
17+
},
18+
"integrations": {
19+
"cloudflare": {
20+
"siteKey": "0x4AAAAAACIE8OqQC3iwDaZh"
21+
}
22+
}
23+
}
24+
</script>
25+
826
</body>

vite_project/index.html.live

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<head>
2+
<meta name="viewport" content="width=device-width, initial-scale=1">
3+
</head>
4+
<body>
5+
<script type="module" src="./src/widget.ts"></script>
6+
7+
<bookingsystem-widget
8+
data-venue="green-edge"
9+
></bookingsystem-widget>
10+
11+
<script type="application/json" id="reactedge-config">
12+
{
13+
"widgets": {
14+
"booking": {
15+
"api": "https://booking-api.reactedge.net/api/graphql/"
16+
}
17+
},
18+
"security": {
19+
"cloudflareKey": "0x4AAAAAACIE8OqQC3iwDaZh"
20+
}
21+
}
22+
</script>
23+
24+
</body>

vite_project/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vite_project/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
22
"name": "widget-booking",
33
"private": true,
4-
"version": "0.1.0",
4+
"version": "0.1.1",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
88
"build": "tsc -b && vite build",
9-
"build:dev": "tsc -b && vite build --mode dev",
10-
"build:prod": "tsc -b && vite build --mode prod",
9+
"build:dev": "tsc -b && vite build",
10+
"build:prod": "tsc -b && vite build",
1111
"lint": "eslint .",
1212
"preview": "vite preview"
1313
},
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
2+
export interface BookingWidgetConfig {
3+
api: string;
4+
venueId: string;
5+
cloudflareKey?: string;
6+
}
7+
8+
interface BookingSystemConfig {
9+
widgets: {
10+
booking?: {
11+
api: string;
12+
};
13+
};
14+
integrations?: {
15+
cloudflare?: {
16+
siteKey: string;
17+
};
18+
};
19+
}
20+
21+
22+
interface BookingHostConfig {
23+
venueId: string
24+
}
25+
26+
export function readBookingSystemConfig(): BookingSystemConfig {
27+
const root = document.getElementById('reactedge-config');
28+
if (!root?.textContent) {
29+
throw new Error('BookingWidget: reactedge-config not found');
30+
}
31+
32+
let config: BookingSystemConfig;
33+
try {
34+
config = JSON.parse(root.textContent);
35+
} catch {
36+
throw new Error('BookingWidget: reactedge-config contains invalid JSON');
37+
}
38+
39+
if (!config.widgets?.booking?.api) {
40+
throw new Error('BookingWidget: booking.api missing in global config');
41+
}
42+
43+
return config;
44+
}
45+
46+
export function readBookingHostConfig(host: HTMLElement): BookingHostConfig {
47+
const venueId = host.getAttribute('data-venue');
48+
49+
if (!venueId) {
50+
throw new Error('BookingWidget: data-venue attribute missing');
51+
}
52+
53+
return Object.freeze({ venueId });
54+
}
Lines changed: 14 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,22 @@
1-
import {BookingSystem} from "./components/BookingSystem.tsx";
2-
import {useEventTypeGroups} from "./hooks/domain/useEventTypeGroups.tsx";
3-
import {Spinner} from "./components/global/Spinner.tsx";
4-
import {ErrorState} from "./components/global/ErrorState.tsx";
5-
import {VisitIntentStateProvider} from "./state/Intent/VisitIntentStateProvider.tsx";
6-
import {useVenue} from "./hooks/domain/useVenue.tsx";
7-
import {useEventHosts} from "./hooks/domain/useEventHosts.tsx";
8-
import {ConfigStateProvider} from "./state/Config/ConfigStateProvider.tsx";
9-
import type {ConfigInfoState} from "./state/Config/type.ts";
10-
import {UserStateProvider} from "./state/User/UserStateProvider.tsx";
11-
import {activity} from "../activity";
1+
import {BookingSystemWrapper} from "./components/BookingSystemWrapper.tsx";
2+
import {useWidgetConfig} from "./hooks/useWidgetConfig.ts";
3+
import {SystemStateProvider} from "./state/System/SystemStateProvider.tsx";
124

13-
export function BookingSystemWidget() {
14-
const { venue, venueError: venueError } = useVenue();
15-
const venueId = venue?.id;
5+
type Props = {
6+
host: HTMLElement;
7+
};
168

17-
const {
18-
eventHosts,
19-
hostsError,
20-
} = useEventHosts(venueId);
9+
export function BookingSystemWidget({ host }: Props) {
10+
const config = useWidgetConfig(host);
2111

22-
const {
23-
groups,
24-
eventTypeGroupError
25-
} = useEventTypeGroups(venueId);
26-
27-
if (venueError || hostsError || eventTypeGroupError) {
28-
return <ErrorState />;
29-
}
30-
31-
if (!venue || !eventHosts || !groups) {
32-
activity('config-load', 'Config Data not loaded',{venue, eventHosts, groups});
33-
return <Spinner />;
12+
if (!config) {
13+
console.warn('[ContactUs] Widget is not correctly configured');
14+
return null;
3415
}
3516

36-
activity('config-load', 'Config Data',{venue, eventHosts, groups});
37-
38-
const config: ConfigInfoState = {
39-
venue,
40-
eventHosts,
41-
eventTypeGroups: groups
42-
};
43-
4417
return (
45-
<ConfigStateProvider config={config}>
46-
<VisitIntentStateProvider eventTypeGroups={config.eventTypeGroups} eventHosts={config.eventHosts}>
47-
<UserStateProvider>
48-
<BookingSystem />
49-
</UserStateProvider>
50-
</VisitIntentStateProvider>
51-
</ConfigStateProvider>
18+
<SystemStateProvider config={config}>
19+
<BookingSystemWrapper venueId={config.venueId} />
20+
</SystemStateProvider>
5221
);
5322
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {useVenue} from "../hooks/domain/useVenue.tsx";
2+
import {useEventHosts} from "../hooks/domain/useEventHosts.tsx";
3+
import {useEventTypeGroups} from "../hooks/domain/useEventTypeGroups.tsx";
4+
import {ErrorState} from "./global/ErrorState.tsx";
5+
import {activity} from "../../activity";
6+
import {Spinner} from "./global/Spinner.tsx";
7+
import type {ConfigInfoState} from "../state/Config/type.ts";
8+
import {VisitIntentStateProvider} from "../state/Intent/VisitIntentStateProvider.tsx";
9+
import {UserStateProvider} from "../state/User/UserStateProvider.tsx";
10+
import {BookingSystem} from "./BookingSystem.tsx";
11+
import {ConfigStateProvider} from "../state/Config/ConfigStateProvider.tsx";
12+
13+
interface Props {
14+
venueId: string
15+
}
16+
17+
export function BookingSystemWrapper({venueId}: Props) {
18+
const { venue, venueError: venueError } = useVenue(venueId);
19+
20+
const {
21+
eventHosts,
22+
hostsError,
23+
} = useEventHosts(venue?.id);
24+
25+
const {
26+
groups,
27+
eventTypeGroupError
28+
} = useEventTypeGroups(venue?.id);
29+
30+
if (venueError || hostsError || eventTypeGroupError) {
31+
return <ErrorState />;
32+
}
33+
34+
if (!venue || !eventHosts || !groups) {
35+
activity('config-load', 'Config Data not loaded',{venue, eventHosts, groups});
36+
return <Spinner />;
37+
}
38+
39+
activity('config-load', 'Config Data',{venue, eventHosts, groups});
40+
41+
const config: ConfigInfoState = {
42+
venue,
43+
eventHosts,
44+
eventTypeGroups: groups
45+
};
46+
47+
return (
48+
<ConfigStateProvider config={config}>
49+
<VisitIntentStateProvider eventTypeGroups={config.eventTypeGroups} eventHosts={config.eventHosts}>
50+
<UserStateProvider>
51+
<BookingSystem />
52+
</UserStateProvider>
53+
</VisitIntentStateProvider>
54+
</ConfigStateProvider>
55+
);
56+
}

vite_project/src/components/Types.ts

Whitespace-only changes.

vite_project/src/hooks/domain/useVenue.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// domain/hooks/useEventTypeGroups.ts
22
import {useKeystoneVenue} from "../infra/useKeystoneVenue.tsx";
3+
import {activity} from "../../../activity";
34

4-
const VENUE_IDENTIFIER = import.meta.env.VITE_VENUE_CODE;
5-
6-
export function useVenue() {
5+
export function useVenue(venueId: string) {
6+
activity('venue', 'Venue loaded', {venueId});
77
const { keystoneVenue, loading, error, refetch } =
8-
useKeystoneVenue(VENUE_IDENTIFIER);
8+
useKeystoneVenue(venueId);
99

1010
const venue = keystoneVenue? {
1111
id: keystoneVenue.id,

vite_project/src/hooks/infra/useKeystoneAddToCart.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {useCallback, useState} from "react";
2-
import {graphqlRequest} from "../../lib/graphql.ts";
32
import type {KeystoneCartEventParams} from "../../types/infra/keystone";
3+
import {useSystemState} from "../../state/System/useSystemState.ts";
44

55
const MUTATION = `
66
mutation AddToCart($eventId: ID!, $eventTypeId: ID!, $shampoo: Int, $turnstileToken: String!) {
@@ -11,14 +11,15 @@ const MUTATION = `
1111
export const useKeystoneAddToCart = () => {
1212
const [loading, setLoading] = useState(false);
1313
const [error, setError] = useState<Error | null>(null);
14+
const { graphqlClient } = useSystemState()
1415

1516
const addToCart = useCallback(
1617
async (variables: KeystoneCartEventParams) => {
1718
setLoading(true);
1819
setError(null);
1920

2021
try {
21-
const result = await graphqlRequest<{
22+
const result = await graphqlClient<{
2223
addToCart: string;
2324
}>(MUTATION, variables);
2425

0 commit comments

Comments
 (0)