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:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user