From 2bc8bc6365c55b742945c4b7f45f76ae8047c968 Mon Sep 17 00:00:00 2001 From: ccruzkauppila Date: Tue, 5 Nov 2024 18:01:37 +0000 Subject: [PATCH] Implement account migration after initial signup --- .../account/AccountMigrationDialog.tsx | 67 +++++++++++++++++++ src/app/(loggedin)/account/page.tsx | 12 +++- src/server/actions/account/migration.ts | 57 ++++++++++++++++ src/server/db/queries/account.ts | 12 ++++ 4 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 src/app/(loggedin)/account/AccountMigrationDialog.tsx diff --git a/src/app/(loggedin)/account/AccountMigrationDialog.tsx b/src/app/(loggedin)/account/AccountMigrationDialog.tsx new file mode 100644 index 00000000..a5413cda --- /dev/null +++ b/src/app/(loggedin)/account/AccountMigrationDialog.tsx @@ -0,0 +1,67 @@ +import { useActionState, useEffect, useRef, useState } from "react"; +import toast from "react-hot-toast"; + +import { AnimatedPopup, PopupRefActions } from "@/components/ui/AnimatedPopup"; +import { FatButton } from "@/components/ui/Buttons/FatButton"; +import { LineButton } from "@/components/ui/Buttons/LineButton"; +import { DialogTitleWithButton } from "@/components/ui/DialogTitleWithButton"; +import { MigrationCombobox } from "@/components/ui/MigrationCombobox"; +import { migrateAccountAction } from "@/server/actions/account/migration"; + +export const AccountMigrationDialog = () => { + const [step, setStep] = useState(0); + const [result, formAction, isPending] = useActionState(migrateAccountAction, { + success: false, + error: undefined, + }); + + useEffect(() => { + if (result?.error) { + toast.error(result.error); + } else if (result?.success) { + toast.success("Account migrated successfully!"); + closeModal(); + } + }, [result]); + const popupRef = useRef(undefined); + const closeModal = () => { + popupRef?.current?.closeContainer(); + setStep(0); + }; + + const setupButton = ( + + ); + + return ( + +
+ +

+ Have an old Namukilke account you want to migrate? Use the form below + to move your old account's funds to this one.
+

+

+ Note that you can only migrate one account. +

+
+ + + +
+
+ ); +}; diff --git a/src/app/(loggedin)/account/page.tsx b/src/app/(loggedin)/account/page.tsx index 03b3b6a1..94bd5b09 100644 --- a/src/app/(loggedin)/account/page.tsx +++ b/src/app/(loggedin)/account/page.tsx @@ -15,7 +15,12 @@ import { RfidSetupDialog } from "@/components/ui/RfidSetupDialog"; import { SectionTitle } from "@/components/ui/SectionTitle"; import { getCurrentUserBalance } from "@/server/actions/account/getBalance"; import { logoutAction } from "@/server/actions/auth/logout"; -import { getCurrentUser } from "@/server/db/queries/account"; +import { + getCurrentUser, + getCurrentUserMigrationStatus, +} from "@/server/db/queries/account"; + +import { AccountMigrationDialog } from "./AccountMigrationDialog"; const AccountPage = () => { const pathName = usePathname(); @@ -23,6 +28,7 @@ const AccountPage = () => { null, ); const [userBalance, setUserBalance] = useState(null); + const [userMigrated, setUserMigrated] = useState(true); useEffect(() => { const checkNfcConnection = async () => { const user = await getCurrentUser(); @@ -38,6 +44,9 @@ const AccountPage = () => { getCurrentUserBalance().then((balance) => { setUserBalance(formatCurrency(balance)); }); + getCurrentUserMigrationStatus().then((migrated) => { + setUserMigrated(migrated); + }); }, []); return (
@@ -70,6 +79,7 @@ const AccountPage = () => { + {!userMigrated && } => { const users = await db.legacyUser.findMany({ @@ -15,3 +21,54 @@ export const getUnmigratedAccounts = async (): Promise => { balance: user.balance.toNumber(), })); }; + +export const migrateAccountAction = async ( + prevState: any, + formData: FormData, +) => { + try { + const session = await getSession(); + const currentUserId = session?.user?.userId; + if (!currentUserId) { + throw new Error("Unauthorized"); + } + const formUserId = formData.get("legacyAccountId") as string; + const legacyId = parseInt(formUserId); + if (!legacyId) { + throw new ValueError({ + cause: "invalid_value", + message: "Couldn't find legacy user to migrate", + }); + } + + await db.$transaction(async (tx) => { + const legacyUser = await tx.legacyUser.update({ + where: { + id: legacyId, + }, + data: { + newAccountId: currentUserId, + }, + }); + if (!legacyUser) { + throw new ValueError({ + cause: "invalid_value", + message: "Couldn't find legacy user to migrate", + }); + } + const legacyBalance = legacyUser.balance.toNumber(); + await newDeposit(tx as PrismaClient, currentUserId, legacyBalance); + }); + revalidatePath("/account"); + return { success: true }; + } catch (error: any) { + if (error instanceof Prisma.PrismaClientKnownRequestError) { + if (error.code === "P2002") { + return { + error: "You've already migrated a legacy account!", + }; + } + } + return { error: error?.message || "Unknown error occurred" }; + } +}; diff --git a/src/server/db/queries/account.ts b/src/server/db/queries/account.ts index 0249c470..14093ea9 100644 --- a/src/server/db/queries/account.ts +++ b/src/server/db/queries/account.ts @@ -210,3 +210,15 @@ export const migrateLegacyUser = async ( }, }); }; + +export const getCurrentUserMigrationStatus = async () => { + const user = await getCurrentUser(); + if (!user.ok) return false; + const legacyUser = await db.legacyUser.findFirst({ + where: { + newAccountId: user.user.id, + }, + }); + console.log("got legacy user", legacyUser); + return !!legacyUser; +};