Skip to content

Commit e9e60ae

Browse files
authored
Merge pull request #295 from ury-erp/fix/redirect-to-setup-wizard
Fix/redirect to setup wizard
2 parents 51c7d92 + e2ec10e commit e9e60ae

14 files changed

Lines changed: 753 additions & 249 deletions

File tree

frontend/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<div id="root"></div>
1414
<script>
1515
window.csrf_token = "{{ csrf_token }}";
16+
window.dev_server = {{ dev_server or 0 }};
1617
if (!window.frappe) window.frappe = {};
1718
window.app_name = "{{ app_name }}";
1819
frappe.boot = JSON.parse({{ boot }});

frontend/src/components/setup/ProgressModal.tsx

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,80 @@ interface ProgressModalProps {
99
error?: string | null;
1010
steps?: string[];
1111
eventName?: string;
12+
description?: string;
1213
onStepChange?: (index: number) => void;
1314
/** Called after the realtime subscription is attached — start the API call here */
1415
onReady?: () => void;
16+
/** Frappe setup_task status === "ok" (background setup) */
17+
onComplete?: () => void;
18+
onFail?: (message: string) => void;
19+
}
20+
21+
type SetupTaskPayload = {
22+
step?: number;
23+
status?: string;
24+
progress?: [number, number];
25+
stage_status?: string;
26+
fail_msg?: string;
27+
message?: SetupTaskPayload;
28+
};
29+
30+
function unwrapPayload(data: unknown): SetupTaskPayload {
31+
if (!data || typeof data !== 'object') {
32+
return {};
33+
}
34+
const obj = data as SetupTaskPayload;
35+
if (obj.message && typeof obj.message === 'object') {
36+
return obj.message;
37+
}
38+
return obj;
1539
}
1640

1741
export function ProgressModal({
1842
visible,
1943
activeIndex,
2044
error,
2145
steps = PROGRESS_STEPS,
22-
eventName = 'ury_setup_progress',
46+
eventName = 'setup_task',
47+
description = 'Setting things up, this usually takes less than a minute.',
2348
onStepChange,
2449
onReady,
50+
onComplete,
51+
onFail,
2552
}: ProgressModalProps) {
26-
// Keep onStepChange stable in a ref so the socket handler closure doesn't
27-
// capture a stale version on every render.
2853
const onStepChangeRef = useRef(onStepChange);
2954
const onReadyRef = useRef(onReady);
55+
const onCompleteRef = useRef(onComplete);
56+
const onFailRef = useRef(onFail);
3057
useEffect(() => {
3158
onStepChangeRef.current = onStepChange;
3259
onReadyRef.current = onReady;
60+
onCompleteRef.current = onComplete;
61+
onFailRef.current = onFail;
3362
});
3463

3564
useEffect(() => {
3665
if (!visible) return;
3766

3867
const handler = (data: unknown) => {
39-
const payload = data as { step?: number; status?: string };
40-
if (typeof payload?.step !== 'number') return;
68+
const payload = unwrapPayload(data);
69+
70+
if (payload.fail_msg || payload.status === 'fail') {
71+
onFailRef.current?.(payload.fail_msg || 'Setup failed');
72+
return;
73+
}
74+
75+
if (payload.status === 'ok') {
76+
onCompleteRef.current?.();
77+
return;
78+
}
79+
80+
if (Array.isArray(payload.progress) && typeof payload.progress[0] === 'number') {
81+
onStepChangeRef.current?.(payload.progress[0]);
82+
return;
83+
}
84+
85+
if (typeof payload.step !== 'number') return;
4186

4287
if (payload.status === 'loading') {
4388
onStepChangeRef.current?.(payload.step);
@@ -46,17 +91,11 @@ export function ProgressModal({
4691
}
4792
};
4893

49-
// Subscribe first, then signal the parent that it is safe to start the
50-
// backend API call. subscribeRealtimeEvent is async internally (connects
51-
// the socket then calls .on()), but it registers the handler synchronously
52-
// on the socket once the connection resolves. We call onReady() after
53-
// kicking off the subscription so the caller can await the socket before
54-
// starting the API. subscribeRealtimeEvent returns a cleanup fn.
5594
const unsubscribe = subscribeRealtimeEvent(eventName, handler, () => {
5695
onReadyRef.current?.();
5796
});
97+
5898
return unsubscribe;
59-
// Re-subscribe only if the event name changes or visibility toggles
6099
}, [visible, eventName]);
61100

62101
if (!visible) return null;
@@ -69,7 +108,7 @@ export function ProgressModal({
69108

70109
{/* Segmented Top Bar */}
71110
<div className="flex px-10 pt-10 pb-6 gap-1">
72-
{Array.from({ length: totalSteps }).map((_, i) => {
111+
{Array.from({ length: Math.max(totalSteps, 1) }).map((_, i) => {
73112
const segmentProgress = i <= activeIndex ? 'bg-primary' : 'bg-gray-200';
74113
return (
75114
<div key={i} className={`flex-1 h-1.5 rounded-full ${segmentProgress}`} />
@@ -80,16 +119,16 @@ export function ProgressModal({
80119
<div className="px-10 pb-8">
81120
<h2 className="text-2xl font-semibold text-foreground mb-1">Setting up your restaurant</h2>
82121
<p className="text-sm text-muted-foreground mb-6">
83-
Setting things up ,this usually takes less than a minute.
122+
{description}
84123
</p>
85124

86-
<div className="flex flex-col mb-4">
125+
<div className="flex flex-col mb-4 max-h-[50vh] overflow-y-auto">
87126
{steps.map((step, idx) => {
88127
const isDone = idx < activeIndex;
89128
const isActive = idx === activeIndex;
90129

91130
return (
92-
<div key={idx} className="flex items-center gap-4 py-3 border-b border-gray-100 last:border-0 h-12">
131+
<div key={idx} className="flex items-center gap-4 py-3 border-b border-gray-100 last:border-0 min-h-12">
93132
<div className="w-6 h-6 flex items-center justify-center shrink-0">
94133
{isDone ? (
95134
<CheckCircle2 className="w-6 h-6 text-white fill-green-500" />

frontend/src/components/setup/WizardLayout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function WizardLayout({
5454
</main>
5555

5656
{/* Footer nav bar */}
57-
<footer className="w-full border-t border-border bg-card sticky bottom-0 h-12 flex items-center">
57+
<footer className="py-6 w-full border-t border-border bg-card sticky bottom-0 flex items-center">
5858
<div className={`${SHELL_WIDTH} w-full h-full flex items-center justify-between gap-4`}>
5959
<div className="flex items-center h-full">
6060
{step === 2 && onPrev && (

frontend/src/lib/realtimeClient.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,51 @@
44
* The /ury SPA is a standalone Vite bundle that does NOT load Frappe's own
55
* socketio_client.js, so `window.frappe.realtime` is never initialised here.
66
* Instead we create a Socket.IO connection directly to the Frappe realtime
7-
* server, mirroring the pattern used in the URY POS (pos/src/lib/realtime.ts).
7+
* server, mirroring Frappe desk's get_host() (including the dedicated
8+
* socketio_port used by `bench start`).
89
*
910
* The site name is taken from `frappe.boot.sitename` which is injected into
1011
* index.html at request time:
1112
* frappe.boot = JSON.parse({{ boot }});
12-
*
13-
* Socket events published via `frappe.publish_realtime(event, payload, user=…)`
14-
* on the backend are delivered directly as socket.io events with the same name.
1513
*/
1614

1715
import { io, type Socket } from 'socket.io-client';
1816

1917
let socket: Socket | null = null;
2018
let connectPromise: Promise<Socket> | null = null;
2119

22-
function getSiteName(): string {
20+
function getBoot(): Record<string, any> {
2321
try {
24-
return (window as any).frappe?.boot?.sitename ?? '';
22+
return (window as any).frappe?.boot ?? {};
2523
} catch {
26-
return '';
24+
return {};
2725
}
2826
}
2927

28+
function getSiteName(): string {
29+
return getBoot().sitename ?? '';
30+
}
31+
32+
/**
33+
* Same rules as frappe/public/js/frappe/socketio_client.js get_host():
34+
* on the dev server, socket.io listens on boot.socketio_port (usually 9000),
35+
* not on the web port.
36+
*/
3037
function buildSocketUrl(): string {
38+
const boot = getBoot();
3139
const siteName = getSiteName();
32-
const { protocol, hostname, port } = window.location;
33-
const base = port ? `${protocol}//${hostname}:${port}` : `${protocol}//${hostname}`;
34-
// Frappe namespaces the socket per-site
35-
return siteName ? `${base}/${siteName}` : base;
40+
const { protocol, hostname } = window.location;
41+
const devServer = Boolean((window as any).dev_server);
42+
const socketioPort = boot.socketio_port;
43+
44+
let host = `${protocol}//${hostname}`;
45+
if (devServer && socketioPort) {
46+
host = `${protocol}//${hostname}:${socketioPort}`;
47+
} else if (window.location.port) {
48+
host = `${protocol}//${hostname}:${window.location.port}`;
49+
}
50+
51+
return siteName ? `${host}/${siteName}` : host;
3652
}
3753

3854
/**
@@ -116,4 +132,3 @@ export function subscribeRealtimeEvent(
116132
activeSocket?.off(eventName, handler);
117133
};
118134
}
119-

0 commit comments

Comments
 (0)