feat: add onboarding

This commit is contained in:
zhom
2026-06-01 01:05:35 +04:00
parent 3a3f201065
commit 98f1c7452a
67 changed files with 3157 additions and 369 deletions
+12
View File
@@ -28,6 +28,10 @@ export type BackendErrorCode =
| "CANNOT_MODIFY_CLOUD_MANAGED_PROXY"
| "SYNC_LOCKED_BY_PROFILE"
| "SYNC_NOT_CONFIGURED"
| "FINGERPRINT_REQUIRES_PRO"
| "PROXY_NOT_WORKING"
| "PROXY_PAYMENT_REQUIRED"
| "VPN_NOT_WORKING"
| "INTERNAL_ERROR";
export interface BackendError {
@@ -120,6 +124,14 @@ export function translateBackendError(t: TFunction, err: unknown): string {
return t("backendErrors.syncLockedByProfile");
case "SYNC_NOT_CONFIGURED":
return t("backendErrors.syncNotConfigured");
case "FINGERPRINT_REQUIRES_PRO":
return t("backendErrors.fingerprintRequiresPro");
case "PROXY_NOT_WORKING":
return t("backendErrors.proxyNotWorking");
case "PROXY_PAYMENT_REQUIRED":
return t("backendErrors.proxyPaymentRequired");
case "VPN_NOT_WORKING":
return t("backendErrors.vpnNotWorking");
case "INTERNAL_ERROR":
return t("backendErrors.internal", {
detail: parsed.params?.detail ?? "",
+18
View File
@@ -0,0 +1,18 @@
// Lightweight module-level flag for whether the first-run onboarding is in
// progress. The welcome dialog shows its own browser-setup progress, so the
// global browser-download toasts (from use-browser-download) are suppressed
// while this is true to avoid a competing toast during onboarding.
let active = false;
export function setOnboardingActive(value: boolean): void {
active = value;
}
export function isOnboardingActive(): boolean {
return active;
}
// Dispatched on `window` when the product tour reaches its end and the user
// clicks "Finish" (not when they skip early). The page listens for it to show
// the celebratory thank-you dialog.
export const ONBOARDING_TOUR_FINISHED_EVENT = "donut:onboarding-tour-finished";
+9 -3
View File
@@ -10,6 +10,9 @@ interface BaseToastProps {
duration?: number;
action?: ExternalToast["action"];
onCancel?: () => void;
// When false, the toast cannot be dismissed by the user (no swipe; combine
// with duration: Infinity and no onCancel to make it fully non-closable).
dismissible?: boolean;
}
interface LoadingToastProps extends BaseToastProps {
@@ -110,12 +113,13 @@ export function showToast(props: ToastProps & { id?: string }) {
sonnerToast.custom(() => React.createElement(UnifiedToast, props), {
id: toastId,
duration,
dismissible: props.dismissible,
style: {
background: "transparent",
border: "none",
boxShadow: "none",
padding: 0,
zIndex: 99999,
zIndex: 10001,
pointerEvents: "auto",
},
});
@@ -123,12 +127,13 @@ export function showToast(props: ToastProps & { id?: string }) {
sonnerToast.custom(() => React.createElement(UnifiedToast, props), {
id: toastId,
duration,
dismissible: props.dismissible,
style: {
background: "transparent",
border: "none",
boxShadow: "none",
padding: 0,
zIndex: 99999,
zIndex: 10001,
pointerEvents: "auto",
},
});
@@ -136,12 +141,13 @@ export function showToast(props: ToastProps & { id?: string }) {
sonnerToast.custom(() => React.createElement(UnifiedToast, props), {
id: toastId,
duration,
dismissible: props.dismissible,
style: {
background: "transparent",
border: "none",
boxShadow: "none",
padding: 0,
zIndex: 99999,
zIndex: 10001,
pointerEvents: "auto",
},
});