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",
|
"usageSkip": "Nur mit Galerien fortfahren",
|
||||||
"finish": "Einrichtung abschließen",
|
"finish": "Einrichtung abschließen",
|
||||||
"featuresSaveFailed": "Ihre Funktionsauswahl konnte nicht gespeichert werden — Sie können sie später unter Einstellungen → Funktionen festlegen.",
|
"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}}",
|
"stepOf": "Schritt {{current}} von {{total}}",
|
||||||
"continue": "Weiter",
|
"continue": "Weiter",
|
||||||
"back": "Zurück",
|
"back": "Zurück",
|
||||||
|
|||||||
@@ -3401,6 +3401,10 @@
|
|||||||
"usageSkip": "Continue with galleries only",
|
"usageSkip": "Continue with galleries only",
|
||||||
"finish": "Finish setup",
|
"finish": "Finish setup",
|
||||||
"featuresSaveFailed": "Could not save your feature choices — you can set them later in Settings → Features.",
|
"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}}",
|
"stepOf": "Step {{current}} of {{total}}",
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Button, Input, Card, Loading } from '../components/common';
|
|||||||
import { useAdminAuth } from '../contexts';
|
import { useAdminAuth } from '../contexts';
|
||||||
import { setupService } from '../services/setup.service';
|
import { setupService } from '../services/setup.service';
|
||||||
import { featureFlagsService, type FeatureFlags, type FeatureKey } from '../services/featureFlags.service';
|
import { featureFlagsService, type FeatureFlags, type FeatureKey } from '../services/featureFlags.service';
|
||||||
|
import { PicpeakRestoreCard } from '../components/admin/PicpeakBackupCard';
|
||||||
import { resolveLoginLogoClasses } from '../utils/loginLogoSize';
|
import { resolveLoginLogoClasses } from '../utils/loginLogoSize';
|
||||||
import type { AdminUser } from '../types';
|
import type { AdminUser } from '../types';
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ export const SetupPage: React.FC = () => {
|
|||||||
staleTime: Infinity,
|
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 [form, setForm] = useState({ token: '', email: '', password: '', confirm: '' });
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
@@ -253,11 +254,15 @@ export const SetupPage: React.FC = () => {
|
|||||||
? t('setup.tokenStepSubtitle')
|
? t('setup.tokenStepSubtitle')
|
||||||
: step === 'account'
|
: step === 'account'
|
||||||
? t('setup.accountStepSubtitle')
|
? t('setup.accountStepSubtitle')
|
||||||
|
: step === 'restore'
|
||||||
|
? t('setup.restoreStepSubtitle')
|
||||||
: t('setup.usageSubtitle')}
|
: t('setup.usageSubtitle')}
|
||||||
</p>
|
</p>
|
||||||
|
{step !== 'restore' && (
|
||||||
<p className="mt-3 text-xs font-medium tracking-wide uppercase" style={{ color: '#171717', opacity: 0.5 }}>
|
<p className="mt-3 text-xs font-medium tracking-wide uppercase" style={{ color: '#171717', opacity: 0.5 }}>
|
||||||
{t('setup.stepOf', { current: stepNumber, total: 3 })}
|
{t('setup.stepOf', { current: stepNumber, total: 3 })}
|
||||||
</p>
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card padding="lg">
|
<Card padding="lg">
|
||||||
@@ -397,12 +402,21 @@ export const SetupPage: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
) : (
|
) : step === 'usage' ? (
|
||||||
<div className="space-y-6">
|
<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">
|
<p className="rounded-lg bg-neutral-50 border border-neutral-200 px-3 py-2 text-xs text-neutral-600">
|
||||||
{t('setup.usageAlwaysOn')}
|
{t('setup.usageAlwaysOn')}
|
||||||
</p>
|
</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) => (
|
{USAGE_GROUPS.map((group) => (
|
||||||
<div key={group.id}>
|
<div key={group.id}>
|
||||||
<h3 className="text-sm font-semibold text-neutral-800 mb-2">{t(group.titleKey)}</h3>
|
<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')}
|
{selectedFeatures.size > 0 ? t('setup.finish') : t('setup.usageSkip')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user