Update Direct page, add migrate button, and clean up

This commit is contained in:
Ivan Evans 2025-01-05 21:41:27 -07:00
parent 718acfd76a
commit c48602a9fc
5 changed files with 174 additions and 72 deletions

View file

@ -217,6 +217,24 @@
"action": "Download data"
}
}
},
"direct": {
"title": "Direct migration",
"description": "Enter the destination backend URL to migrate your current account data to a new backend. This keeps your passphrase the same!",
"backendLabel": "Destination Backend URL",
"recaptchaLabel": "ReCaptcha Key (Optional)",
"toggleLable": "Needs ReCaptcha?",
"loginRequired": "You must be logged in to migrate your data! Please go back and login to continue.",
"status": {
"error": "Failed to migrate your data. 😿",
"success": "Your data has been migrated successfully! 🎉"
},
"button": {
"migrate": "Migrate",
"processing": "Processing...",
"home": "Go home",
"login": "Continue to login"
}
}
},
"navigation": {
@ -596,7 +614,11 @@
"server": {
"description": "If you would like to connect to a custom backend to store your data, enable this and provide the URL. <0>Instructions.</0>",
"label": "Custom server",
"urlLabel": "Custom server URL"
"urlLabel": "Custom server URL",
"migration": {
"description": "<0>Migrate my data</0> to a new server. ",
"link": "Migrate my data"
}
},
"setup": {
"doSetup": "Do setup",

View file

@ -86,12 +86,12 @@ export function useMigration() {
};
const migrate = useCallback(
async (backendUrl: string, recaptchaToken: string) => {
async (backendUrl: string, recaptchaToken?: string) => {
if (!currentAccount) return;
const { challenge } = await getRegisterChallengeToken(
backendUrl,
recaptchaToken,
recaptchaToken || undefined, // Pass undefined if token is not provided
);
const keys = await keysFromSeed(base64ToBuffer(currentAccount.seed));
const signature = await signChallenge(keys, challenge);

View file

@ -1,12 +1,139 @@
import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { Button } from "@/components/buttons/Button";
import { SettingsCard } from "@/components/layout/SettingsCard";
import { CenterContainer } from "@/components/layout/ThinContainer";
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
import { Divider } from "@/components/utils/Divider";
import { Heading2, Paragraph } from "@/components/utils/Text";
import { useAuth } from "@/hooks/auth/useAuth";
import { useMigration } from "@/hooks/auth/useMigration";
import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
import { PageTitle } from "@/pages/parts/util/PageTitle";
import { useAuthStore } from "@/stores/auth";
export function MigrationDirectPage() {
const { t } = useTranslation();
const user = useAuthStore();
const { logout } = useAuth();
const navigate = useNavigate();
const { migrate } = useMigration();
const [backendUrl, setBackendUrl] = useState("");
const [status, setStatus] = useState<
"idle" | "success" | "error" | "processing"
>("idle");
const updateBackendUrl = useAuthStore((state) => state.setBackendUrl);
const handleMigration = useCallback(async () => {
if (!backendUrl) {
// eslint-disable-next-line no-alert
alert("Please provide a Backend URL.");
return;
}
try {
setStatus("processing");
const account = await migrate(backendUrl);
if (account) {
setStatus("success");
await logout();
updateBackendUrl(backendUrl);
} else {
setStatus("error");
}
} catch (error) {
console.error("Error during migration:", error);
setStatus("error");
}
}, [backendUrl, migrate, updateBackendUrl, logout]);
const continueButton = () => {
if (status === "success") {
navigate("/login");
}
};
return (
<MinimalPageLayout>
<PageTitle subpage k="global.pages.migration" />
<CenterContainer>Hi</CenterContainer>
<CenterContainer>
{user.account ? (
<div>
<Heading2 className="!text-4xl">
{" "}
{t("migration.direct.title")}
</Heading2>
<div className="space-y-6 max-w-3xl mx-auto">
<Paragraph className="text-lg max-w-md">
{t("migration.direct.description")}
</Paragraph>
<SettingsCard>
<div className="flex justify-between items-center">
<p className="font-bold text-white">
{t("migration.direct.backendLabel")}
</p>
</div>
{backendUrl !== null && (
<>
<Divider marginClass="my-6 px-8 box-content -mx-8" />
<AuthInputBox
placeholder="https://"
value={backendUrl ?? ""}
onChange={setBackendUrl}
/>
</>
)}
</SettingsCard>
<div className="text-center">
{status !== "success" && (
<Button theme="purple" onClick={handleMigration}>
{status === "processing"
? t("migration.direct.button.processing")
: t("migration.direct.button.migrate")}
</Button>
)}
{status === "success" && (
<div>
<Button
theme="purple"
className="mt-4"
onClick={continueButton}
>
{t("migration.direct.button.login")}
</Button>
<p className="text-green-600 mt-4">
{t("migration.direct.status.success")}
</p>
</div>
)}
{status === "error" && (
<p className="text-red-600 mt-4">
{t("migration.direct.status.error")}
</p>
)}
</div>
</div>
</div>
) : (
<div className="flex flex-col items-center text-center mb-8">
<Paragraph className="max-w-[320px] text-md">
{t("migration.direct.loginRequired")}
</Paragraph>
<Button
theme="purple"
className="mt-4"
onClick={() => navigate("/")}
>
{t("migration.direct.button.home")}
</Button>
</div>
)}
</CenterContainer>
</MinimalPageLayout>
);
}

View file

@ -10,6 +10,7 @@ import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
import { Divider } from "@/components/utils/Divider";
import { Heading1 } from "@/components/utils/Text";
// import { SetupPart } from "@/pages/parts/settings/SetupPart";
import { useAuthStore } from "@/stores/auth";
interface ProxyEditProps {
proxyUrls: string[] | null;
@ -116,6 +117,7 @@ function ProxyEdit({ proxyUrls, setProxyUrls }: ProxyEditProps) {
function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
const { t } = useTranslation();
const user = useAuthStore();
return (
<SettingsCard>
<div className="flex justify-between items-center gap-4">
@ -130,6 +132,18 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
</MwLink>
</Trans>
</p>
{user.account && (
<div>
<br />
<p className="max-w-[20rem] font-medium">
<Trans i18nKey="settings.connections.server.migration.description">
<MwLink to="/migration">
{t("settings.connections.server.migration.link")}
</MwLink>
</Trans>
</p>
</div>
)}
</div>
<div>
<Toggle

View file

@ -148,6 +148,13 @@ function App() {
element={<OnboardingExtensionPage />}
/>
<Route path="/onboarding/proxy" element={<OnboardingProxyPage />} />
<Route path="/migration" element={<MigrationDirectPage />} />
{/* Migration pages - awaiting import and export fixes
<Route path="/migration" element={<MigrationPage />} />
<Route path="/migration/direct" element={<MigrationDirectPage />} />
*/}
{shouldHaveDmcaPage() ? (
<Route path="/dmca" element={<DmcaPage />} />
) : null}
@ -180,74 +187,6 @@ function App() {
{showDowntime && (
<MaintenancePage onHomeButtonClick={handleButtonClick} />
)}
<Routes>
{/* functional routes */}
<Route path="/s/:query" element={<QuickSearch />} />
<Route path="/search/:type" element={<Navigate to="/browse" />} />
<Route path="/search/:type/:query?" element={<QueryView />} />
{/* pages */}
<Route
path="/media/:media"
element={
<LegacyUrlView>
<Suspense fallback={null}>
<PlayerView />
</Suspense>
</LegacyUrlView>
}
/>
<Route
path="/media/:media/:season/:episode"
element={
<LegacyUrlView>
<Suspense fallback={null}>
<PlayerView />
</Suspense>
</LegacyUrlView>
}
/>
<Route path="/browse/:query?" element={<HomePage />} />
<Route path="/" element={<HomePage />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/onboarding" element={<OnboardingPage />} />
<Route
path="/onboarding/extension"
element={<OnboardingExtensionPage />}
/>
<Route path="/onboarding/proxy" element={<OnboardingProxyPage />} />
<Route path="/migration" element={<MigrationPage />} />
<Route path="/migration/direct" element={<MigrationDirectPage />} />
{shouldHaveDmcaPage() ? (
<Route path="/dmca" element={<DmcaPage />} />
) : null}
{/* Settings page */}
<Route
path="/settings"
element={
<Suspense fallback={null}>
<SettingsPage />
</Suspense>
}
/>
{/* admin routes */}
<Route path="/admin" element={<AdminPage />} />
{/* other */}
<Route path="/dev" element={<DeveloperPage />} />
<Route path="/dev/video" element={<VideoTesterView />} />
{/* developer routes that can abuse workers are disabled in production */}
{process.env.NODE_ENV === "development" ? (
<Route path="/dev/test" element={<TestView />} />
) : null}
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Layout>
);
}