completed the frontend dashboard component and bound it to the tenant admin pwa for the optimal onboarding experience.. Added a profile page.

This commit is contained in:
Codex Agent
2025-11-04 22:28:37 +01:00
parent fe380689fb
commit b32413b108
29 changed files with 1416 additions and 425 deletions

View File

@@ -78,6 +78,13 @@ export default function WelcomePackagesPage() {
isSubscription: Boolean(active.package_limits?.subscription),
}
: null,
serverStep: active ? "package_selected" : undefined,
meta: active
? {
packages: [active.package_id],
is_active: active.active,
}
: undefined,
});
}
}, [packagesState, markStep, currencyFormatter, t]);
@@ -96,6 +103,8 @@ export default function WelcomePackagesPage() {
priceText,
isSubscription: Boolean(pkg.features?.subscription),
},
serverStep: "package_selected",
meta: { packages: [pkg.id] },
});
navigate(ADMIN_WELCOME_SUMMARY_PATH, { state: { packageId: pkg.id } });

View File

@@ -1,10 +1,12 @@
import React from 'react';
import { fetchOnboardingStatus, trackOnboarding } from '../api';
export type OnboardingProgress = {
welcomeSeen: boolean;
packageSelected: boolean;
eventCreated: boolean;
lastStep?: string | null;
adminAppOpenedAt?: string | null;
selectedPackage?: {
id: number;
name: string;
@@ -13,10 +15,15 @@ export type OnboardingProgress = {
} | null;
};
type OnboardingUpdate = Partial<OnboardingProgress> & {
serverStep?: string;
meta?: Record<string, unknown>;
};
type OnboardingContextValue = {
progress: OnboardingProgress;
setProgress: (updater: (prev: OnboardingProgress) => OnboardingProgress) => void;
markStep: (step: Partial<OnboardingProgress>) => void;
markStep: (step: OnboardingUpdate) => void;
reset: () => void;
};
@@ -25,6 +32,7 @@ const DEFAULT_PROGRESS: OnboardingProgress = {
packageSelected: false,
eventCreated: false,
lastStep: null,
adminAppOpenedAt: null,
selectedPackage: null,
};
@@ -65,6 +73,45 @@ function writeStoredProgress(progress: OnboardingProgress) {
export function OnboardingProgressProvider({ children }: { children: React.ReactNode }) {
const [progress, setProgressState] = React.useState<OnboardingProgress>(() => readStoredProgress());
const [synced, setSynced] = React.useState(false);
React.useEffect(() => {
if (synced) {
return;
}
fetchOnboardingStatus().then((status) => {
if (!status) {
setSynced(true);
return;
}
setProgressState((prev) => {
const next: OnboardingProgress = {
...prev,
adminAppOpenedAt: status.steps.admin_app_opened_at ?? prev.adminAppOpenedAt ?? null,
eventCreated: Boolean(status.steps.event_created ?? prev.eventCreated),
packageSelected: Boolean(status.steps.selected_packages ?? prev.packageSelected),
};
writeStoredProgress(next);
return next;
});
if (!status.steps.admin_app_opened_at) {
const timestamp = new Date().toISOString();
trackOnboarding('admin_app_opened').catch(() => {});
setProgressState((prev) => {
const next = { ...prev, adminAppOpenedAt: timestamp };
writeStoredProgress(next);
return next;
});
}
setSynced(true);
});
}, [synced]);
const setProgress = React.useCallback((updater: (prev: OnboardingProgress) => OnboardingProgress) => {
setProgressState((prev) => {
@@ -74,12 +121,18 @@ export function OnboardingProgressProvider({ children }: { children: React.React
});
}, []);
const markStep = React.useCallback((step: Partial<OnboardingProgress>) => {
const markStep = React.useCallback((step: OnboardingUpdate) => {
const { serverStep, meta, ...rest } = step;
setProgress((prev) => ({
...prev,
...step,
lastStep: typeof step.lastStep === 'undefined' ? prev.lastStep : step.lastStep,
...rest,
lastStep: typeof rest.lastStep === 'undefined' ? prev.lastStep : rest.lastStep,
}));
if (serverStep) {
trackOnboarding(serverStep, meta).catch(() => {});
}
}, [setProgress]);
const reset = React.useCallback(() => {