Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions components/onboarding/onboarding-wizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ import {
} from '@/lib/onboarding/flow';
import { finalizeOnboardingAction, previewCalendarImportAction } from '@/app/onboarding/actions';
import { buildFirstBrief, type FirstBrief } from '@/lib/onboarding/first-brief';
import { formatFirstBrief } from '@/lib/onboarding/first-brief-display';
import { pickFirstThing } from '@/lib/outcomes/launcher';
import { DoOneThingCard } from '@/components/outcomes/do-one-thing-card';
import { CalendarDays, Clipboard, AlertTriangle, ListChecks, Clock, Wand2, Utensils } from 'lucide-react';
import { useTranslations } from '@/components/i18n/locale-provider';
import { useLocale, useTranslations } from '@/components/i18n/locale-provider';
import { ConnectedCalendar, type CalendarProvider } from '@/components/onboarding/connected-calendar';
import { isReviewPlan, reviewBillingPath, type ReviewPlan } from '@/lib/billing/review-selection';
import type { OnboardingOwner } from '@/lib/onboarding/owner';
Expand Down Expand Up @@ -359,6 +360,7 @@ function ValuePanel({ draft, update, calendarProviders, calendarAccountId, calen
reviewPlan?: ReviewPlan | null; expectedOwner?: OnboardingOwner;
}) {
const tr = useTranslations();
const locale = useLocale();
const { error: toastError } = useToast();
const [ics, setIcs] = useState('');
const [loading, setLoading] = useState<null | 'paste' | 'demo'>(null);
Expand Down Expand Up @@ -391,6 +393,7 @@ function ValuePanel({ draft, update, calendarProviders, calendarAccountId, calen
}

if (brief) {
const view = formatFirstBrief(brief, { locale: locale.code, t: tr });
return (
<div className="space-y-4">
{draft.calendarReceipt && <p className="rounded-xl border border-border p-3 text-sm text-muted">{calendarName
Expand All @@ -400,7 +403,7 @@ function ValuePanel({ draft, update, calendarProviders, calendarAccountId, calen
<div className="mx-auto mb-1 flex h-9 w-9 items-center justify-center rounded-full bg-brand/15 text-brand-text">
<Sparkles className="h-5 w-5" />
</div>
<p className="text-base font-semibold">{brief.headline}</p>
<p className="text-base font-semibold">{view.headline}</p>
{brief.timeSavedMinutes > 0 && (
<p className="mt-1 text-sm text-muted">
{tr('onboardingWizard.estimatedPlanningTimeOfNMinutes', { minutes: brief.timeSavedMinutes })}
Expand All @@ -415,7 +418,7 @@ function ValuePanel({ draft, update, calendarProviders, calendarAccountId, calen
{brief.timeline.slice(0, 6).map((t, i) => (
<li key={i} className="flex items-baseline justify-between gap-3 text-sm">
<span className="truncate"><span className="font-medium">{t.title}</span>{t.location ? <span className="text-muted"> · {t.location}</span> : null}</span>
<span className="shrink-0 tabular-nums text-muted">{t.timeLabel}</span>
<span className="shrink-0 tabular-nums text-muted">{view.timeline[i].timeLabel}</span>
</li>
))}
</ul>
Expand All @@ -427,7 +430,7 @@ function ValuePanel({ draft, update, calendarProviders, calendarAccountId, calen
<h2 className="mb-2 flex items-center gap-2 text-sm font-semibold"><AlertTriangle className="h-4 w-4 text-amber-500" /> {tr(brief.conflicts.length === 1 ? 'onboardingCopy.clashToResolveOne' : 'onboardingCopy.clashToResolveOther', { count: brief.conflicts.length })}</h2>
<ul className="space-y-1 text-sm text-muted">
{brief.conflicts.slice(0, 3).map((c, i) => (
<li key={i}>{tr('onboardingCopy.overlap', { first: c.aTitle, second: c.bTitle, day: c.dayLabel, time: c.overlapLabel })}</li>
<li key={i}>{tr('onboardingCopy.overlap', { first: c.aTitle, second: c.bTitle, day: view.conflicts[i].dayLabel, time: view.conflicts[i].overlapLabel })}</li>
))}
</ul>
</section>
Expand All @@ -437,10 +440,10 @@ function ValuePanel({ draft, update, calendarProviders, calendarAccountId, calen
<section className="rounded-2xl border border-border p-4">
<h2 className="mb-2 flex items-center gap-2 text-sm font-semibold"><ListChecks className="h-4 w-4 text-brand-text" /> {tr('onboardingWizard.firstThingsToHandle')}</h2>
<ul className="space-y-1.5 text-sm">
{brief.actions.slice(0, 4).map((a) => (
{brief.actions.slice(0, 4).map((a, i) => (
<li key={a.id} className="flex items-start gap-2">
<Check className="mt-0.5 h-3.5 w-3.5 shrink-0 text-brand-text" />
<span><span className="font-medium">{a.label}</span> — <span className="text-muted">{a.detail}</span></span>
<span><span className="font-medium">{view.actions[i].label}</span> — <span className="text-muted">{view.actions[i].detail}</span></span>
</li>
))}
</ul>
Expand All @@ -451,8 +454,8 @@ function ValuePanel({ draft, update, calendarProviders, calendarAccountId, calen
<section className="rounded-2xl border border-border p-4">
<h2 className="mb-2 flex items-center gap-2 text-sm font-semibold"><Clock className="h-4 w-4 text-brand-text" /> {tr('onboardingCopy.workingForYouAlready')}</h2>
<ul className="space-y-1.5 text-sm">
{brief.opportunities.map((o) => (
<li key={o.id}><span className="font-medium">{o.label}</span> <span className="text-muted">· {o.detail}</span></li>
{brief.opportunities.map((o, i) => (
<li key={o.id}><span className="font-medium">{view.opportunities[i].label}</span> <span className="text-muted">· {view.opportunities[i].detail}</span></li>
))}
</ul>
</section>
Expand Down Expand Up @@ -705,6 +708,8 @@ function PinPanel({ draft, update, firstName }: { draft: OnboardingDraft; update
// ─── Step 6: Done ─────────────────────────────────────────────────────────────
function DonePanel({ draft, firstName, brief, onGo, onReview }: { draft: OnboardingDraft; firstName: string; brief: FirstBrief | null; onGo: () => void; onReview?: () => void }) {
const tr = useTranslations();
const locale = useLocale();
const briefHeadline = brief ? formatFirstBrief(brief, { locale: locale.code, t: tr }).headline : '';
const hasBrief = !!brief && (brief.todayCount > 0 || brief.dinnerIdeas.length > 0 || brief.timeSavedMinutes > 0 || brief.conflicts.length > 0);
const memberCount = draft.members.length;
const goalCount = draft.goals.length;
Expand Down Expand Up @@ -732,7 +737,7 @@ function DonePanel({ draft, firstName, brief, onGo, onReview }: { draft: Onboard
{hasBrief && brief && (
<div className="mt-6 space-y-3 text-left">
<div className="rounded-2xl border border-brand/30 bg-brand/5 p-4">
<p className="flex items-center gap-2 text-sm font-semibold"><Sparkles className="h-4 w-4 text-brand-text" /> {brief.headline}</p>
<p className="flex items-center gap-2 text-sm font-semibold"><Sparkles className="h-4 w-4 text-brand-text" /> {briefHeadline}</p>
<div className="mt-3 flex flex-wrap gap-2">
{brief.timeSavedMinutes > 0 && (
<span className="inline-flex items-center gap-1 rounded-full bg-brand/15 px-2.5 py-1 text-xs font-medium text-brand-text"><Clock className="h-3 w-3" /> {tr('onboardingWizard.nMinPlanningEstimated', { minutes: brief.timeSavedMinutes })}</span>
Expand Down
28 changes: 27 additions & 1 deletion lib/i18n/messages/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -12750,5 +12750,31 @@
"onboardingCopy.letsBegin": "Los geht’s",
"onboardingCopy.dashboardReady": "Ihre Übersicht ist auf Sie abgestimmt und bereit zum Erkunden.",
"onboardingCopy.kidLogin": "Richten Sie einen Zugang für {names} ein — mit Benutzername und PIN, ohne E-Mail-Adresse",
"onboardingCopy.primaryCalendarPreview": "Vorschau aus Ihrem Hauptkalender: die nächsten 30 Tage. Abschließen importiert diese Termine. Spätere Abrufe ergänzen anstehende Termine und übernehmen gemeldete Änderungen; nicht mehr gelieferte Termine bleiben erhalten. Kalender und Aufgaben beim Anbieter bleiben unverändert."
"onboardingCopy.primaryCalendarPreview": "Vorschau aus Ihrem Hauptkalender: die nächsten 30 Tage. Abschließen importiert diese Termine. Spätere Abrufe ergänzen anstehende Termine und übernehmen gemeldete Änderungen; nicht mehr gelieferte Termine bleiben erhalten. Kalender und Aufgaben beim Anbieter bleiben unverändert.",
"firstBriefDisplay.allDay": "Ganztägig",
"firstBriefDisplay.today": "Heute",
"firstBriefDisplay.tomorrow": "Morgen",
"firstBriefDisplay.headlineEmpty": "Alles ist eingerichtet — tragen Sie Ihre ersten Termine ein, und Bubaly übernimmt den Rest.",
"firstBriefDisplay.headlineConflicts": "Ihr {weekday} — {events}, {conflicts} zu klären.",
"firstBriefDisplay.headlineClear": "Ihr {weekday} — {events}, und alles ist gut organisiert.",
"firstBriefDisplay.events.one": "{count} Termin",
"firstBriefDisplay.events.other": "{count} Termine",
"firstBriefDisplay.clashes.one": "{count} Überschneidung",
"firstBriefDisplay.clashes.other": "{count} Überschneidungen",
"firstBriefDisplay.resolveClash": "Eine Überschneidung klären · {day}",
"firstBriefDisplay.resolveClashDetail": "{first} überschneidet sich mit {second} ({time}). Entscheiden Sie, wer was übernimmt.",
"firstBriefDisplay.addLocation": "Einen Ort hinzufügen",
"firstBriefDisplay.addLocationDetail": "„{title}“ · {day}: Es ist kein Ort eingetragen.",
"firstBriefDisplay.prepareToday": "Bereiten Sie sich auf heute vor",
"firstBriefDisplay.prepareAllDay": "Zuerst steht an: {title}.",
"firstBriefDisplay.prepareTimed": "Zuerst steht an: {title} um {time}.",
"firstBriefDisplay.recurring.one": "{count} wiederkehrender Termin auf Autopilot",
"firstBriefDisplay.recurring.other": "{count} wiederkehrende Termine auf Autopilot",
"firstBriefDisplay.recurringDetail": "Bubaly behält diese Termine in Ihrem Kalender und erinnert Sie daran — ohne erneute Eingabe.",
"firstBriefDisplay.conflictsCaught.one": "{count} Überschneidung für Sie erkannt",
"firstBriefDisplay.conflictsCaught.other": "{count} Überschneidungen für Sie erkannt",
"firstBriefDisplay.conflictsCaughtDetail": "Wir haben diese Überschneidungen erkannt, bevor es hektisch wurde.",
"firstBriefDisplay.weekOrganized": "Ihre Woche ist bereits organisiert",
"firstBriefDisplay.weekDetail.one": "{count} Termin in einem gemeinsamen Zeitplan.",
"firstBriefDisplay.weekDetail.other": "{count} Termine in einem gemeinsamen Zeitplan."
}
28 changes: 27 additions & 1 deletion lib/i18n/messages/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -12750,5 +12750,31 @@
"onboardingCopy.letsBegin": "Let’s begin",
"onboardingCopy.dashboardReady": "Your dashboard is personalized and ready to explore.",
"onboardingCopy.kidLogin": "Give {names} a login — username and PIN, no email needed",
"onboardingCopy.primaryCalendarPreview": "Preview from your primary calendar: the next 30 days. Finish imports these events. Future reads add upcoming appointments and apply reported changes; events missing from a read are kept. Your provider calendar and tasks stay unchanged."
"onboardingCopy.primaryCalendarPreview": "Preview from your primary calendar: the next 30 days. Finish imports these events. Future reads add upcoming appointments and apply reported changes; events missing from a read are kept. Your provider calendar and tasks stay unchanged.",
"firstBriefDisplay.allDay": "All day",
"firstBriefDisplay.today": "Today",
"firstBriefDisplay.tomorrow": "Tomorrow",
"firstBriefDisplay.headlineEmpty": "You’re set up — add your first plans and Bubaly takes it from here.",
"firstBriefDisplay.headlineConflicts": "Here’s your {weekday} — {events}, {conflicts} to resolve.",
"firstBriefDisplay.headlineClear": "Here’s your {weekday} — {events}, and you’re in good shape.",
"firstBriefDisplay.events.one": "{count} event",
"firstBriefDisplay.events.other": "{count} events",
"firstBriefDisplay.clashes.one": "{count} clash",
"firstBriefDisplay.clashes.other": "{count} clashes",
"firstBriefDisplay.resolveClash": "Resolve a clash · {day}",
"firstBriefDisplay.resolveClashDetail": "{first} overlaps {second} ({time}). Decide who covers what.",
"firstBriefDisplay.addLocation": "Add a location",
"firstBriefDisplay.addLocationDetail": "“{title}” · {day}: no location is set.",
"firstBriefDisplay.prepareToday": "Get ready for today",
"firstBriefDisplay.prepareAllDay": "First up: {title}.",
"firstBriefDisplay.prepareTimed": "First up: {title} at {time}.",
"firstBriefDisplay.recurring.one": "{count} recurring event on autopilot",
"firstBriefDisplay.recurring.other": "{count} recurring events on autopilot",
"firstBriefDisplay.recurringDetail": "Bubaly keeps these on your calendar and reminds you — no re-entering.",
"firstBriefDisplay.conflictsCaught.one": "{count} clash caught for you",
"firstBriefDisplay.conflictsCaught.other": "{count} clashes caught for you",
"firstBriefDisplay.conflictsCaughtDetail": "We spotted these before they became a scramble.",
"firstBriefDisplay.weekOrganized": "Your week, already organized",
"firstBriefDisplay.weekDetail.one": "{count} event sorted into one shared timeline.",
"firstBriefDisplay.weekDetail.other": "{count} events sorted into one shared timeline."
}
28 changes: 27 additions & 1 deletion lib/i18n/messages/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -12750,5 +12750,31 @@
"onboardingCopy.letsBegin": "Vamos a empezar",
"onboardingCopy.dashboardReady": "Tu panel está personalizado y listo para explorar.",
"onboardingCopy.kidLogin": "Crea un acceso para {names}: nombre de usuario y PIN, sin necesidad de correo",
"onboardingCopy.primaryCalendarPreview": "Vista previa de tu calendario principal: los próximos 30 días. Al finalizar se importan estos eventos. Las lecturas posteriores añaden próximas citas y aplican los cambios notificados; se conservan los eventos que no aparecen en una lectura. Tu calendario y tus tareas en el proveedor no cambian."
"onboardingCopy.primaryCalendarPreview": "Vista previa de tu calendario principal: los próximos 30 días. Al finalizar se importan estos eventos. Las lecturas posteriores añaden próximas citas y aplican los cambios notificados; se conservan los eventos que no aparecen en una lectura. Tu calendario y tus tareas en el proveedor no cambian.",
"firstBriefDisplay.allDay": "Todo el día",
"firstBriefDisplay.today": "Hoy",
"firstBriefDisplay.tomorrow": "Mañana",
"firstBriefDisplay.headlineEmpty": "Todo listo — añade tus primeros planes y Bubaly se encarga del resto.",
"firstBriefDisplay.headlineConflicts": "Así va tu {weekday}: {events}, {conflicts} por resolver.",
"firstBriefDisplay.headlineClear": "Así va tu {weekday}: {events}, y todo está en orden.",
"firstBriefDisplay.events.one": "{count} evento",
"firstBriefDisplay.events.other": "{count} eventos",
"firstBriefDisplay.clashes.one": "{count} solapamiento",
"firstBriefDisplay.clashes.other": "{count} solapamientos",
"firstBriefDisplay.resolveClash": "Resuelve un solapamiento · {day}",
"firstBriefDisplay.resolveClashDetail": "{first} se solapa con {second} ({time}). Decide quién se encarga de cada cosa.",
"firstBriefDisplay.addLocation": "Añade una ubicación",
"firstBriefDisplay.addLocationDetail": "«{title}» · {day}: no tiene ubicación.",
"firstBriefDisplay.prepareToday": "Prepárate para hoy",
"firstBriefDisplay.prepareAllDay": "Primero: {title}.",
"firstBriefDisplay.prepareTimed": "Primero: {title} ({time}).",
"firstBriefDisplay.recurring.one": "{count} evento recurrente en piloto automático",
"firstBriefDisplay.recurring.other": "{count} eventos recurrentes en piloto automático",
"firstBriefDisplay.recurringDetail": "Bubaly mantiene estos eventos en tu calendario y te avisa, sin tener que volver a introducirlos.",
"firstBriefDisplay.conflictsCaught.one": "{count} solapamiento detectado",
"firstBriefDisplay.conflictsCaught.other": "{count} solapamientos detectados",
"firstBriefDisplay.conflictsCaughtDetail": "Los hemos detectado antes de que se conviertan en un apuro.",
"firstBriefDisplay.weekOrganized": "Tu semana ya está organizada",
"firstBriefDisplay.weekDetail.one": "{count} evento ordenado en una agenda compartida.",
"firstBriefDisplay.weekDetail.other": "{count} eventos ordenados en una agenda compartida."
}
28 changes: 27 additions & 1 deletion lib/i18n/messages/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -12750,5 +12750,31 @@
"onboardingCopy.letsBegin": "C’est parti",
"onboardingCopy.dashboardReady": "Votre tableau de bord est personnalisé et prêt à être exploré.",
"onboardingCopy.kidLogin": "Créez un accès pour {names} — un identifiant et un code PIN suffisent, sans e-mail",
"onboardingCopy.primaryCalendarPreview": "Aperçu de votre calendrier principal : les 30 prochains jours. Terminer importe ces événements. Les lectures suivantes ajoutent les rendez-vous à venir et appliquent les modifications signalées ; les événements absents d’une lecture sont conservés. Votre calendrier et vos tâches chez le fournisseur restent inchangés."
"onboardingCopy.primaryCalendarPreview": "Aperçu de votre calendrier principal : les 30 prochains jours. Terminer importe ces événements. Les lectures suivantes ajoutent les rendez-vous à venir et appliquent les modifications signalées ; les événements absents d’une lecture sont conservés. Votre calendrier et vos tâches chez le fournisseur restent inchangés.",
"firstBriefDisplay.allDay": "Toute la journée",
"firstBriefDisplay.today": "Aujourd’hui",
"firstBriefDisplay.tomorrow": "Demain",
"firstBriefDisplay.headlineEmpty": "Tout est prêt — ajoutez vos premiers projets et Bubaly prend le relais.",
"firstBriefDisplay.headlineConflicts": "Votre {weekday} : {events}, {conflicts} à résoudre.",
"firstBriefDisplay.headlineClear": "Votre {weekday} : {events}, et tout est bien organisé.",
"firstBriefDisplay.events.one": "{count} événement",
"firstBriefDisplay.events.other": "{count} événements",
"firstBriefDisplay.clashes.one": "{count} chevauchement",
"firstBriefDisplay.clashes.other": "{count} chevauchements",
"firstBriefDisplay.resolveClash": "Résolvez un chevauchement · {day}",
"firstBriefDisplay.resolveClashDetail": "{first} chevauche {second} ({time}). Décidez qui s’occupe de quoi.",
"firstBriefDisplay.addLocation": "Ajoutez un lieu",
"firstBriefDisplay.addLocationDetail": "« {title} » · {day} : aucun lieu n’est indiqué.",
"firstBriefDisplay.prepareToday": "Préparez votre journée",
"firstBriefDisplay.prepareAllDay": "Pour commencer : {title}.",
"firstBriefDisplay.prepareTimed": "Pour commencer : {title} à {time}.",
"firstBriefDisplay.recurring.one": "{count} événement récurrent en pilote automatique",
"firstBriefDisplay.recurring.other": "{count} événements récurrents en pilote automatique",
"firstBriefDisplay.recurringDetail": "Bubaly conserve ces événements dans votre calendrier et vous les rappelle, sans nouvelle saisie.",
"firstBriefDisplay.conflictsCaught.one": "{count} chevauchement repéré pour vous",
"firstBriefDisplay.conflictsCaught.other": "{count} chevauchements repérés pour vous",
"firstBriefDisplay.conflictsCaughtDetail": "Nous les avons repérés avant qu’ils ne vous prennent de court.",
"firstBriefDisplay.weekOrganized": "Votre semaine est déjà organisée",
"firstBriefDisplay.weekDetail.one": "{count} événement dans un planning partagé.",
"firstBriefDisplay.weekDetail.other": "{count} événements dans un planning partagé."
}
Loading
Loading