- Integrated `next-intl` for toast messages and locale handling in various components, including `Providers`, `CanvasUserMenu`, and `CreditOverview`. - Replaced hardcoded strings with translation keys to enhance localization capabilities. - Updated `RootLayout` to dynamically set the language attribute based on the user's locale. - Ensured consistent user feedback through localized toast messages in actions such as sign-out, canvas operations, and billing notifications.
360 lines
10 KiB
TypeScript
360 lines
10 KiB
TypeScript
'use client';
|
|
|
|
import { useTranslations } from 'next-intl';
|
|
import { gooeyToast, type GooeyPromiseData } from 'goey-toast';
|
|
|
|
const DURATION = {
|
|
success: 4000,
|
|
successShort: 2000,
|
|
error: 6000,
|
|
warning: 5000,
|
|
info: 4000,
|
|
} as const;
|
|
|
|
type ToastTranslations = ReturnType<typeof useTranslations<'toasts'>>;
|
|
|
|
export type ToastDurationOverrides = {
|
|
duration?: number;
|
|
};
|
|
|
|
export const toast = {
|
|
success(message: string, description?: string, opts?: ToastDurationOverrides) {
|
|
return gooeyToast.success(message, {
|
|
description,
|
|
duration: opts?.duration ?? DURATION.success,
|
|
});
|
|
},
|
|
error(message: string, description?: string, opts?: ToastDurationOverrides) {
|
|
return gooeyToast.error(message, {
|
|
description,
|
|
duration: opts?.duration ?? DURATION.error,
|
|
});
|
|
},
|
|
warning(message: string, description?: string, opts?: ToastDurationOverrides) {
|
|
return gooeyToast.warning(message, {
|
|
description,
|
|
duration: opts?.duration ?? DURATION.warning,
|
|
});
|
|
},
|
|
info(message: string, description?: string, opts?: ToastDurationOverrides) {
|
|
return gooeyToast.info(message, {
|
|
description,
|
|
duration: opts?.duration ?? DURATION.info,
|
|
});
|
|
},
|
|
promise<T>(promise: Promise<T>, data: GooeyPromiseData<T>) {
|
|
return gooeyToast.promise(promise, data);
|
|
},
|
|
action(message: string, opts: {
|
|
description?: string;
|
|
label: string;
|
|
onClick: () => void;
|
|
successLabel?: string;
|
|
type?: "success" | "info" | "warning";
|
|
duration?: number;
|
|
}) {
|
|
const t = opts.type ?? "info";
|
|
return gooeyToast[t](message, {
|
|
description: opts.description,
|
|
duration: opts.duration ?? (t === "success" ? DURATION.success : DURATION.info),
|
|
action: {
|
|
label: opts.label,
|
|
onClick: opts.onClick,
|
|
successLabel: opts.successLabel,
|
|
},
|
|
});
|
|
},
|
|
update(id: string | number, opts: {
|
|
title?: string;
|
|
description?: string;
|
|
type?: "default" | "success" | "error" | "warning" | "info";
|
|
}) {
|
|
gooeyToast.update(id, opts);
|
|
},
|
|
dismiss(id?: string | number) {
|
|
gooeyToast.dismiss(id);
|
|
},
|
|
};
|
|
|
|
export const toastDuration = {
|
|
success: DURATION.success,
|
|
successShort: DURATION.successShort,
|
|
error: DURATION.error,
|
|
warning: DURATION.warning,
|
|
info: DURATION.info,
|
|
} as const;
|
|
|
|
export type CanvasNodeDeleteBlockReason = 'optimistic';
|
|
|
|
export function showImageUploadedToast(t: ToastTranslations) {
|
|
toast.success(t('canvas.imageUploaded'));
|
|
}
|
|
|
|
export function showUploadFailedToast(t: ToastTranslations, reason?: string) {
|
|
if (reason) {
|
|
toast.error(t('canvas.uploadFailed'), reason);
|
|
} else {
|
|
toast.error(t('canvas.uploadFailed'));
|
|
}
|
|
}
|
|
|
|
export function showUploadFormatError(t: ToastTranslations, format: string) {
|
|
toast.error(t('canvas.uploadFailed'), t('canvas.uploadFormatError', { format }));
|
|
}
|
|
|
|
export function showUploadSizeError(t: ToastTranslations, maxMb: number) {
|
|
toast.error(t('canvas.uploadFailed'), t('canvas.uploadSizeError', { maxMb }));
|
|
}
|
|
|
|
export function showNodeRemovedToast(t: ToastTranslations) {
|
|
toast.success(t('canvas.nodeRemoved'));
|
|
}
|
|
|
|
export function showNodesRemovedToast(t: ToastTranslations, count: number) {
|
|
const title = t('canvas.nodesRemoved', { count });
|
|
toast.success(title);
|
|
}
|
|
|
|
export function canvasNodeDeleteWhy(
|
|
t: ToastTranslations,
|
|
reasons: Set<CanvasNodeDeleteBlockReason>,
|
|
) {
|
|
if (reasons.size === 0) {
|
|
return {
|
|
title: t('canvas.nodeDeleteBlockedTitle'),
|
|
desc: t('canvas.nodeDeleteBlockedDesc'),
|
|
};
|
|
}
|
|
if (reasons.size === 1) {
|
|
const only = [...reasons][0]!;
|
|
if (only === 'optimistic') {
|
|
return {
|
|
title: t('canvas.nodeDeleteOptimisticTitle'),
|
|
desc: t('canvas.nodeDeleteOptimisticDesc'),
|
|
};
|
|
}
|
|
return {
|
|
title: t('canvas.nodeDeleteBlockedTitle'),
|
|
desc: t('canvas.nodeDeleteBlockedDesc'),
|
|
};
|
|
}
|
|
return {
|
|
title: t('canvas.nodeDeleteBlockedTitle'),
|
|
desc: t('canvas.nodeDeleteBlockedMultiDesc'),
|
|
};
|
|
}
|
|
|
|
export function canvasNodeDeleteBlockedPartial(
|
|
t: ToastTranslations,
|
|
blockedCount: number,
|
|
reasons: Set<CanvasNodeDeleteBlockReason>,
|
|
) {
|
|
const why = canvasNodeDeleteWhy(t, reasons);
|
|
const suffix =
|
|
blockedCount === 1
|
|
? t('canvas.nodeDeleteBlockedPartialSuffixOne')
|
|
: t('canvas.nodeDeleteBlockedPartialSuffixOther', { count: blockedCount });
|
|
return {
|
|
title: t('canvas.nodeDeleteBlockedPartialTitle'),
|
|
desc: `${why.desc} ${suffix}`,
|
|
};
|
|
}
|
|
|
|
export function showGeneratingToast(t: ToastTranslations) {
|
|
gooeyToast.info(t('ai.generating'), { duration: Infinity });
|
|
}
|
|
|
|
export function showGeneratedToast(
|
|
t: ToastTranslations,
|
|
credits: number,
|
|
) {
|
|
toast.success(t('ai.generated'), t('ai.generatedDesc', { credits }));
|
|
}
|
|
|
|
export function showGenerationQueuedToast(t: ToastTranslations) {
|
|
toast.success(t('ai.generationQueued'), t('ai.generationQueuedDesc'));
|
|
}
|
|
|
|
export function showGenerationFailedToast(t: ToastTranslations) {
|
|
toast.error(t('ai.generationFailed'));
|
|
}
|
|
|
|
export function showCreditsNotChargedToast(t: ToastTranslations) {
|
|
toast.warning(t('ai.creditsNotCharged'));
|
|
}
|
|
|
|
export function showInsufficientCreditsToast(
|
|
t: ToastTranslations,
|
|
needed: number,
|
|
available: number,
|
|
) {
|
|
toast.error(t('ai.insufficientCreditsTitle'), t('ai.insufficientCreditsDesc', { needed, available }));
|
|
}
|
|
|
|
export function showModelUnavailableToast(t: ToastTranslations) {
|
|
toast.error(t('ai.modelUnavailableTitle'), t('ai.modelUnavailableDesc'));
|
|
}
|
|
|
|
export function showContentPolicyBlockedToast(t: ToastTranslations) {
|
|
toast.error(t('ai.contentPolicyTitle'), t('ai.contentPolicyDesc'));
|
|
}
|
|
|
|
export function showTimeoutToast(t: ToastTranslations) {
|
|
toast.error(t('ai.timeoutTitle'), t('ai.timeoutDesc'));
|
|
}
|
|
|
|
export function showOpenrouterIssuesToast(t: ToastTranslations) {
|
|
toast.error(t('ai.openrouterIssuesTitle'), t('ai.openrouterIssuesDesc'));
|
|
}
|
|
|
|
export function showConcurrentLimitReachedToast(t: ToastTranslations) {
|
|
toast.error(t('ai.concurrentLimitReachedTitle'), t('ai.concurrentLimitReachedDesc'));
|
|
}
|
|
|
|
export function showFrameExportedToast(t: ToastTranslations) {
|
|
toast.success(t('export.frameExported'));
|
|
}
|
|
|
|
export function showExportingFramesToast(t: ToastTranslations) {
|
|
gooeyToast.info(t('export.exportingFrames'), { duration: Infinity });
|
|
}
|
|
|
|
export function showZipReadyToast(t: ToastTranslations) {
|
|
toast.success(t('export.zipReady'));
|
|
}
|
|
|
|
export function showExportFailedToast(t: ToastTranslations) {
|
|
toast.error(t('export.exportFailed'));
|
|
}
|
|
|
|
export function showFrameEmptyToast(t: ToastTranslations) {
|
|
toast.error(t('export.frameEmptyTitle'), t('export.frameEmptyDesc'));
|
|
}
|
|
|
|
export function showNoFramesOnCanvasToast(t: ToastTranslations) {
|
|
toast.error(t('export.noFramesOnCanvasTitle'), t('export.noFramesOnCanvasDesc'));
|
|
}
|
|
|
|
export function showDownloadToast(t: ToastTranslations) {
|
|
toast.success(t('export.downloaded'), t('export.download'));
|
|
}
|
|
|
|
export function showWelcomeBackToast(t: ToastTranslations) {
|
|
toast.success(t('auth.welcomeBack'));
|
|
}
|
|
|
|
export function showWelcomeOnDashboardToast(t: ToastTranslations) {
|
|
toast.success(t('auth.welcomeOnDashboard'));
|
|
}
|
|
|
|
export function showCheckEmailToast(t: ToastTranslations, email: string) {
|
|
toast.success(t('auth.checkEmailTitle'), t('auth.checkEmailDesc', { email }));
|
|
}
|
|
|
|
export function showSessionExpiredToast(t: ToastTranslations) {
|
|
toast.error(t('auth.sessionExpiredTitle'), t('auth.sessionExpiredDesc'));
|
|
}
|
|
|
|
export function showSignedOutToast(t: ToastTranslations) {
|
|
toast.success(t('auth.signedOut'));
|
|
}
|
|
|
|
export function showSignInToast(t: ToastTranslations) {
|
|
toast.success(t('auth.signIn'));
|
|
}
|
|
|
|
export function showInitialSetupToast(t: ToastTranslations) {
|
|
toast.success(t('auth.initialSetupTitle'), t('auth.initialSetupDesc'));
|
|
}
|
|
|
|
export function showSubscriptionActivatedToast(
|
|
t: ToastTranslations,
|
|
credits: number,
|
|
) {
|
|
toast.success(t('billing.subscriptionActivatedTitle'), t('billing.subscriptionActivatedDesc', { credits }));
|
|
}
|
|
|
|
export function showCreditsAddedToast(t: ToastTranslations, credits: number) {
|
|
toast.success(t('billing.creditsAddedTitle'), t('billing.creditsAddedDesc', { credits }));
|
|
}
|
|
|
|
export function showSubscriptionCancelledToast(
|
|
t: ToastTranslations,
|
|
periodEnd: string,
|
|
) {
|
|
gooeyToast.info(t('billing.subscriptionCancelledTitle'), { description: t('billing.subscriptionCancelledDesc', { periodEnd }) });
|
|
}
|
|
|
|
export function showPaymentFailedToast(t: ToastTranslations) {
|
|
toast.error(t('billing.paymentFailedTitle'), t('billing.paymentFailedDesc'));
|
|
}
|
|
|
|
export function showDailyLimitReachedToast(t: ToastTranslations, limit: number) {
|
|
toast.error(t('billing.dailyLimitReachedTitle'), t('billing.dailyLimitReachedDesc', { limit }));
|
|
}
|
|
|
|
export function showLowCreditsToast(t: ToastTranslations, remaining: number) {
|
|
toast.warning(t('billing.lowCreditsTitle'), t('billing.lowCreditsDesc', { remaining }));
|
|
}
|
|
|
|
export function showTopUpToast(t: ToastTranslations) {
|
|
toast.success(t('billing.topUp'));
|
|
}
|
|
|
|
export function showUpgradeToast(t: ToastTranslations) {
|
|
toast.success(t('billing.upgrade'));
|
|
}
|
|
|
|
export function showManageToast(t: ToastTranslations) {
|
|
toast.success(t('billing.manage'));
|
|
}
|
|
|
|
export function showRedirectingToCheckoutToast(t: ToastTranslations) {
|
|
gooeyToast.info(t('billing.redirectingToCheckoutTitle'), { description: t('billing.redirectingToCheckoutDesc') });
|
|
}
|
|
|
|
export function showOpeningPortalToast(t: ToastTranslations) {
|
|
gooeyToast.info(t('billing.openingPortalTitle'), { description: t('billing.openingPortalDesc') });
|
|
}
|
|
|
|
export function showTestGrantFailedToast(t: ToastTranslations) {
|
|
toast.error(t('billing.testGrantFailedTitle'));
|
|
}
|
|
|
|
export function showReconnectedToast(t: ToastTranslations) {
|
|
toast.success(t('system.reconnected'));
|
|
}
|
|
|
|
export function showConnectionLostToast(t: ToastTranslations) {
|
|
toast.error(t('system.connectionLostTitle'), t('system.connectionLostDesc'));
|
|
}
|
|
|
|
export function showCopiedToClipboardToast(t: ToastTranslations) {
|
|
toast.success(t('system.copiedToClipboard'));
|
|
}
|
|
|
|
export function showRenameEmptyToast(t: ToastTranslations) {
|
|
toast.error(t('dashboard.renameEmptyTitle'), t('dashboard.renameEmptyDesc'));
|
|
}
|
|
|
|
export function showRenameSuccessToast(t: ToastTranslations) {
|
|
toast.success(t('dashboard.renameSuccess'));
|
|
}
|
|
|
|
export function showRenameFailedToast(t: ToastTranslations) {
|
|
toast.error(t('dashboard.renameFailed'));
|
|
}
|
|
|
|
export function showDeleteSuccessToast(t: ToastTranslations) {
|
|
toast.success(t('dashboard.deleteSuccess'));
|
|
}
|
|
|
|
export function showDeleteFailedToast(t: ToastTranslations) {
|
|
toast.error(t('dashboard.deleteFailed'));
|
|
}
|
|
|
|
export function getToastTranslations() {
|
|
const t = useTranslations('toasts');
|
|
return t as ToastTranslations;
|
|
}
|