From 422dfe1cc88ae277dc170e95b4746e507c31b23a Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Thu, 2 Jul 2026 19:31:10 +0200 Subject: [PATCH] feat(setup): add "How will you use PicPeak?" feature-selection step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the admin account is created (and we're logged in), the wizard now shows an opt-in feature step instead of jumping straight to the dashboard. Grouped ticks (Client management / Accounting / Automation) map to the existing feature flags; galleries/analytics/userManagement stay always-on and are noted, not listed. - Selection is saved via the existing authenticated PUT /admin/feature-flags, whose server-side applyDependencyRules resolves dependencies (e.g. Invoices pulls in Accounting) — the wizard only sends raw ticks. - Labels/descriptions reuse settings.features..title/description so translations stay in sync (en + de verified for all 14 features). - Saving is best-effort: on failure the admin still enters the app and can set features later in Settings. - New en/de strings for the usage step. Option A (lean wizard): this is the feature-selection foundation; per-feature hard-required config steps + the restore-from-backup branch come next. --- frontend/src/i18n/locales/de.json | 9 +++ frontend/src/i18n/locales/en.json | 9 +++ frontend/src/pages/SetupPage.tsx | 111 ++++++++++++++++++++++++++++-- 3 files changed, 123 insertions(+), 6 deletions(-) diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index c231bd27..9842f66b 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -3467,6 +3467,15 @@ "subtitle": "Erstellen Sie Ihr Administrator-Konto, um loszulegen", "tokenStepSubtitle": "Geben Sie zunächst Ihren einmaligen Setup-Token ein", "accountStepSubtitle": "Erstellen Sie nun Ihr Administrator-Konto", + "usageSubtitle": "Wie möchten Sie PicPeak nutzen?", + "usageAlwaysOn": "Galerien, Analysen und Benutzerverwaltung sind immer enthalten. Wählen Sie unten optionale Funktionen — Sie können dies jederzeit in den Einstellungen ändern.", + "usageGroupCrm": "Kundenverwaltung", + "usageGroupAccounting": "Buchhaltung", + "usageGroupAutomation": "Automatisierung & Versand", + "usageDepsNote": "Rechnungen aktivieren automatisch den Buchhaltungsbereich.", + "usageSkip": "Nur mit Galerien fortfahren", + "finish": "Einrichtung abschließen", + "featuresSaveFailed": "Ihre Funktionsauswahl konnte nicht gespeichert werden — Sie können sie später unter Einstellungen → Funktionen festlegen.", "stepOf": "Schritt {{current}} von {{total}}", "continue": "Weiter", "back": "Zurück", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index e5c89510..403b4f9d 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -3363,6 +3363,15 @@ "subtitle": "Create your administrator account to get started", "tokenStepSubtitle": "First, enter your one-time setup token", "accountStepSubtitle": "Now create your administrator account", + "usageSubtitle": "How will you use PicPeak?", + "usageAlwaysOn": "Galleries, analytics and user management are always included. Pick any extras below — you can change these anytime in Settings.", + "usageGroupCrm": "Client management", + "usageGroupAccounting": "Accounting", + "usageGroupAutomation": "Automation & delivery", + "usageDepsNote": "Invoices automatically enable the Accounting area.", + "usageSkip": "Continue with galleries only", + "finish": "Finish setup", + "featuresSaveFailed": "Could not save your feature choices — you can set them later in Settings → Features.", "stepOf": "Step {{current}} of {{total}}", "continue": "Continue", "back": "Back", diff --git a/frontend/src/pages/SetupPage.tsx b/frontend/src/pages/SetupPage.tsx index 789a8db4..4cca4fa0 100644 --- a/frontend/src/pages/SetupPage.tsx +++ b/frontend/src/pages/SetupPage.tsx @@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next'; import { Button, Input, Card, Loading } from '../components/common'; import { useAdminAuth } from '../contexts'; import { setupService } from '../services/setup.service'; +import { featureFlagsService, type FeatureFlags, type FeatureKey } from '../services/featureFlags.service'; import { resolveLoginLogoClasses } from '../utils/loginLogoSize'; import type { AdminUser } from '../types'; @@ -16,6 +17,19 @@ import type { AdminUser } from '../types'; const SETUP_DOCS_URL = 'https://github.com/PicPeak/picpeak/blob/main/README.md#first-run--create-your-admin-account'; +// "How will you use PicPeak?" — the opt-in feature groups shown after the admin +// account is created. galleries/analytics/userManagement are always on and not +// listed. Labels/descriptions reuse the existing Settings→Features i18n keys +// (`settings.features..title/description`) so translations stay in sync. +// Server-side applyDependencyRules resolves dependencies (e.g. Invoices pulls in +// Accounting) when we PUT the selection, so we only send the raw ticks. +const USAGE_GROUPS: { id: string; titleKey: string; features: FeatureKey[] }[] = [ + { id: 'crm', titleKey: 'setup.usageGroupCrm', features: ['quotes', 'contracts', 'bills', 'hoursLogging', 'customerPortal', 'calendar'] }, + { id: 'accounting', titleKey: 'setup.usageGroupAccounting', features: ['taxReport', 'incomingInvoices', 'expenses'] }, + { id: 'automation', titleKey: 'setup.usageGroupAutomation', features: ['reminderEmails', 'slideshow', 'workflows', 'whatsapp', 'incomingMail'] }, +]; +const ALL_USAGE_FEATURES: FeatureKey[] = USAGE_GROUPS.flatMap((g) => g.features); + // First-run screen. Reached on a fresh instance where no admin account exists // yet — creates the first (super_admin) account from the browser using the // one-time setup token printed to the server logs. Once an admin exists the @@ -37,13 +51,15 @@ export const SetupPage: React.FC = () => { staleTime: Infinity, }); - const [step, setStep] = useState<'token' | 'account'>('token'); + const [step, setStep] = useState<'token' | 'account' | 'usage'>('token'); const [form, setForm] = useState({ token: '', email: '', password: '', confirm: '' }); const [showPassword, setShowPassword] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [isVerifyingToken, setIsVerifyingToken] = useState(false); const [copied, setCopied] = useState(false); const [errors, setErrors] = useState>({}); + const [selectedFeatures, setSelectedFeatures] = useState>(new Set()); + const [isSavingFeatures, setIsSavingFeatures] = useState(false); if (statusLoading) { return ; @@ -144,7 +160,10 @@ export const SetupPage: React.FC = () => { }; login('', adminUser); toast.success(t('setup.success')); - navigate('/admin/dashboard', { replace: true }); + // Admin now exists and we're logged in (cookie set) — advance to the + // opt-in "How will you use PicPeak?" step rather than jumping straight to + // the dashboard. Authenticated calls (feature flags) work from here. + setStep('usage'); } catch (error: any) { const httpStatus = error.response?.status; const data = error.response?.data; @@ -184,7 +203,33 @@ export const SetupPage: React.FC = () => { } }; - const stepNumber = step === 'token' ? 1 : 2; + const toggleFeature = (key: FeatureKey) => { + setSelectedFeatures((prev) => { + const next = new Set(prev); + if (next.has(key)) next.delete(key); + else next.add(key); + return next; + }); + }; + + // Persist the feature selection, then enter the app. Saving is best-effort — + // if it fails the admin can still flip features later in Settings, so we don't + // trap them on the setup screen. + const finishSetup = async () => { + setIsSavingFeatures(true); + try { + const flags: Partial = {}; + for (const key of ALL_USAGE_FEATURES) flags[key] = selectedFeatures.has(key); + await featureFlagsService.update(flags); + } catch (_) { + toast.warn(t('setup.featuresSaveFailed')); + } finally { + setIsSavingFeatures(false); + navigate('/admin/dashboard', { replace: true }); + } + }; + + const stepNumber = step === 'token' ? 1 : step === 'account' ? 2 : 3; return (
@@ -204,10 +249,14 @@ export const SetupPage: React.FC = () => { })()}

{t('setup.title')}

- {step === 'token' ? t('setup.tokenStepSubtitle') : t('setup.accountStepSubtitle')} + {step === 'token' + ? t('setup.tokenStepSubtitle') + : step === 'account' + ? t('setup.accountStepSubtitle') + : t('setup.usageSubtitle')}

- {t('setup.stepOf', { current: stepNumber, total: 2 })} + {t('setup.stepOf', { current: stepNumber, total: 3 })}

@@ -271,7 +320,7 @@ export const SetupPage: React.FC = () => { {t('setup.continue')} - ) : ( + ) : step === 'account' ? (
+ ) : ( +
+

+ {t('setup.usageAlwaysOn')} +

+ + {USAGE_GROUPS.map((group) => ( +
+

{t(group.titleKey)}

+
+ {group.features.map((key) => ( + + ))} +
+
+ ))} + + {selectedFeatures.has('bills') && !selectedFeatures.has('taxReport') && ( +

{t('setup.usageDepsNote')}

+ )} + + +
)}