feat(setup): add restore-from-backup branch to the first-run wizard

The usage step now offers 'Migrating from another PicPeak?' → a restore
step that uploads a .picpeak (reusing PicpeakRestoreCard) to clone another
instance onto this fresh one, preserving the account just created. en + de
strings added.
This commit is contained in:
Luca
2026-07-02 21:57:50 +02:00
parent 86324e7da7
commit a95ee473ae
3 changed files with 42 additions and 6 deletions
+4
View File
@@ -3505,6 +3505,10 @@
"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.",
"restoreStepSubtitle": "Aus einem Backup wiederherstellen",
"restoreEntry": "Wechsel von einer anderen PicPeak-Instanz?",
"restoreEntryHint": "Stellen Sie stattdessen ein .picpeak-Backup wieder her, anstatt neu einzurichten.",
"restoreIntro": "Laden Sie ein .picpeak-Backup hoch, um eine andere Instanz auf diese zu klonen. Dies ersetzt alles außer dem gerade erstellten Konto.",
"stepOf": "Schritt {{current}} von {{total}}",
"continue": "Weiter",
"back": "Zurück",
+4
View File
@@ -3401,6 +3401,10 @@
"usageSkip": "Continue with galleries only",
"finish": "Finish setup",
"featuresSaveFailed": "Could not save your feature choices — you can set them later in Settings → Features.",
"restoreStepSubtitle": "Restore from a backup",
"restoreEntry": "Migrating from another PicPeak?",
"restoreEntryHint": "Restore a .picpeak backup instead of setting up fresh.",
"restoreIntro": "Upload a .picpeak backup to clone another instance onto this one. This replaces everything except the account you just created.",
"stepOf": "Step {{current}} of {{total}}",
"continue": "Continue",
"back": "Back",
+34 -6
View File
@@ -9,6 +9,7 @@ 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 { PicpeakRestoreCard } from '../components/admin/PicpeakBackupCard';
import { resolveLoginLogoClasses } from '../utils/loginLogoSize';
import type { AdminUser } from '../types';
@@ -51,7 +52,7 @@ export const SetupPage: React.FC = () => {
staleTime: Infinity,
});
const [step, setStep] = useState<'token' | 'account' | 'usage'>('token');
const [step, setStep] = useState<'token' | 'account' | 'usage' | 'restore'>('token');
const [form, setForm] = useState({ token: '', email: '', password: '', confirm: '' });
const [showPassword, setShowPassword] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -253,11 +254,15 @@ export const SetupPage: React.FC = () => {
? t('setup.tokenStepSubtitle')
: step === 'account'
? t('setup.accountStepSubtitle')
: t('setup.usageSubtitle')}
</p>
<p className="mt-3 text-xs font-medium tracking-wide uppercase" style={{ color: '#171717', opacity: 0.5 }}>
{t('setup.stepOf', { current: stepNumber, total: 3 })}
: step === 'restore'
? t('setup.restoreStepSubtitle')
: t('setup.usageSubtitle')}
</p>
{step !== 'restore' && (
<p className="mt-3 text-xs font-medium tracking-wide uppercase" style={{ color: '#171717', opacity: 0.5 }}>
{t('setup.stepOf', { current: stepNumber, total: 3 })}
</p>
)}
</div>
<Card padding="lg">
@@ -397,12 +402,21 @@ export const SetupPage: React.FC = () => {
</Button>
</div>
</form>
) : (
) : step === 'usage' ? (
<div className="space-y-6">
<p className="rounded-lg bg-neutral-50 border border-neutral-200 px-3 py-2 text-xs text-neutral-600">
{t('setup.usageAlwaysOn')}
</p>
<button
type="button"
onClick={() => setStep('restore')}
className="w-full rounded-lg border border-dashed border-neutral-300 p-3 text-left hover:bg-neutral-50 transition-colors"
>
<span className="block text-sm font-medium text-neutral-800">{t('setup.restoreEntry')}</span>
<span className="block text-xs text-neutral-500">{t('setup.restoreEntryHint')}</span>
</button>
{USAGE_GROUPS.map((group) => (
<div key={group.id}>
<h3 className="text-sm font-semibold text-neutral-800 mb-2">{t(group.titleKey)}</h3>
@@ -447,6 +461,20 @@ export const SetupPage: React.FC = () => {
{selectedFeatures.size > 0 ? t('setup.finish') : t('setup.usageSkip')}
</Button>
</div>
) : (
<div className="space-y-6">
<p className="text-sm text-neutral-600">{t('setup.restoreIntro')}</p>
<PicpeakRestoreCard />
<Button
type="button"
variant="outline"
size="lg"
onClick={() => setStep('usage')}
leftIcon={<ArrowLeft className="w-4 h-4" />}
>
{t('setup.back')}
</Button>
</div>
)}
</Card>
</div>