diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index 90ecb318..2550a9b4 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -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.", "label": "Custom server", - "urlLabel": "Custom server URL" + "urlLabel": "Custom server URL", + "migration": { + "description": "<0>Migrate my data to a new server. ", + "link": "Migrate my data" + } }, "setup": { "doSetup": "Do setup", diff --git a/src/hooks/auth/useMigration.ts b/src/hooks/auth/useMigration.ts index bfe9bbf1..446ac92e 100644 --- a/src/hooks/auth/useMigration.ts +++ b/src/hooks/auth/useMigration.ts @@ -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); diff --git a/src/pages/migration/MigrationDirect.tsx b/src/pages/migration/MigrationDirect.tsx index 9b3a3702..22c6a745 100644 --- a/src/pages/migration/MigrationDirect.tsx +++ b/src/pages/migration/MigrationDirect.tsx @@ -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 ( - Hi + + {user.account ? ( +
+ + {" "} + {t("migration.direct.title")} + +
+ + {t("migration.direct.description")} + + +
+

+ {t("migration.direct.backendLabel")} +

+
+ {backendUrl !== null && ( + <> + + + + )} +
+ +
+ {status !== "success" && ( + + )} + + {status === "success" && ( +
+ +

+ {t("migration.direct.status.success")} +

+
+ )} + + {status === "error" && ( +

+ {t("migration.direct.status.error")} +

+ )} +
+
+
+ ) : ( +
+ + {t("migration.direct.loginRequired")} + + +
+ )} +
); } diff --git a/src/pages/parts/settings/ConnectionsPart.tsx b/src/pages/parts/settings/ConnectionsPart.tsx index a0e0e4fd..30b9019b 100644 --- a/src/pages/parts/settings/ConnectionsPart.tsx +++ b/src/pages/parts/settings/ConnectionsPart.tsx @@ -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 (
@@ -130,6 +132,18 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {

+ {user.account && ( +
+
+

+ + + {t("settings.connections.server.migration.link")} + + +

+
+ )}
} /> } /> + + } /> + {/* Migration pages - awaiting import and export fixes + } /> + } /> + */} + {shouldHaveDmcaPage() ? ( } /> ) : null} @@ -180,74 +187,6 @@ function App() { {showDowntime && ( )} - - {/* functional routes */} - } /> - } /> - } /> - - {/* pages */} - - - - - - } - /> - - - - - - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - - } /> - } /> - - {shouldHaveDmcaPage() ? ( - } /> - ) : null} - - {/* Settings page */} - - - - } - /> - - {/* admin routes */} - } /> - - {/* other */} - } /> - } /> - {/* developer routes that can abuse workers are disabled in production */} - {process.env.NODE_ENV === "development" ? ( - } /> - ) : null} - } /> - ); }