Skip to content

Commit

Permalink
spilt form
Browse files Browse the repository at this point in the history
  • Loading branch information
hyoban committed Jan 16, 2025
1 parent bbc494b commit e6750ee
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 72 deletions.
5 changes: 2 additions & 3 deletions apps/renderer/src/modules/auth/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { z } from "zod"

import { useCurrentModal, useModalStack } from "~/components/ui/modal/stacked/hooks"

import { PasswordForm } from "../profile/two-factor"
import { TOTPForm } from "../profile/two-factor"

const formSchema = z.object({
email: z.string().email(),
Expand Down Expand Up @@ -56,8 +56,7 @@ export function LoginWithPassword({ runtime }: { runtime?: LoginRuntime }) {
title: tSettings("profile.totp_code.title"),
content: () => {
return (
<PasswordForm
valueType="totp"
<TOTPForm
onSubmitMutationFn={async (values) => {
const { data, error } = await twoFactor.verifyTotp({ code: values.code })
if (!data || error) {
Expand Down
18 changes: 7 additions & 11 deletions apps/renderer/src/modules/profile/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { defineQuery } from "~/lib/defineQuery"
import { getFetchErrorInfo } from "~/lib/error-parser"
import { users } from "~/queries/users"

import { PasswordForm, TwoFactorForm } from "./two-factor"
import { TOTPForm, TwoFactorForm } from "./two-factor"

const LazyUserProfileModalContent = lazy(() =>
import("./user-profile-modal").then((mod) => ({ default: mod.UserProfileModalContent })),
Expand Down Expand Up @@ -127,17 +127,13 @@ export function useTOTPModalWrapper<T>(
present({
title: t("profile.totp_code.title"),
content: ({ dismiss }) => {
return createElement(PasswordForm, {
valueType: "totp",
return createElement(TOTPForm, {
async onSubmitMutationFn(values) {
if ("code" in values) {
await callback({
...input,
TOTPCode: values.code,
})
dismiss()
}
return new Promise((resolve) => resolve())
await callback({
...input,
TOTPCode: values.code,
})
dismiss()
},
})
},
Expand Down
147 changes: 89 additions & 58 deletions apps/renderer/src/modules/profile/two-factor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
import { Input, InputOTP, InputOTPGroup, InputOTPSlot } from "@follow/components/ui/input/index.js"
import { Label } from "@follow/components/ui/label/index.js"
import { twoFactor } from "@follow/shared/auth"
import { cn } from "@follow/utils/utils"
import { zodResolver } from "@hookform/resolvers/zod"
import { useMutation } from "@tanstack/react-query"
import { m, useAnimation } from "framer-motion"
Expand Down Expand Up @@ -41,8 +40,7 @@ const totpFormSchema = z.object({
})
type TOTPFormValues = z.infer<typeof totpFormSchema>

export type PasswordFormProps<T, V> = {
valueType: T
type PasswordFormProps<V> = {
onSubmitMutationFn: (values: V) => Promise<void>
message?: {
placeholder?: string
Expand All @@ -63,38 +61,36 @@ const shakeVariants = {
},
}

export function PasswordForm<
T extends "password" | "totp",
V extends T extends "password" ? PasswordFormValues : TOTPFormValues,
>({ valueType, message, onSubmitMutationFn, onSuccess }: PasswordFormProps<T, V>) {
const isPassword = valueType === "password"
export function TOTPForm({
message,
onSubmitMutationFn,
onSuccess,
}: PasswordFormProps<TOTPFormValues>) {
const { t } = useTranslation("settings")
const controls = useAnimation()

const form = useForm<V>({
resolver: zodResolver(isPassword ? passwordFormSchema : totpFormSchema),
defaultValues: (isPassword ? { password: "" } : { code: "" }) as any,
const form = useForm<TOTPFormValues>({
resolver: zodResolver(totpFormSchema),
defaultValues: { code: "" },
})

const updateMutation = useMutation({
mutationFn: onSubmitMutationFn,
onError: (error) => {
const { code } = getFetchErrorInfo(error)
if (!isPassword && (error.message === "invalid two factor authentication" || code === 4007)) {
if (error.message === "invalid two factor authentication" || code === 4007) {
form.resetField("code" as any)
form.setError("code" as any, {
type: "manual",
message: t("profile.totp_code.invalid"),
})
controls.start("shake")
} else {
toast.error(error.message)
}
},
onSuccess,
})

function onSubmit(values: V) {
function onSubmit(values: TOTPFormValues) {
updateMutation.mutate(values)
}

Expand All @@ -103,59 +99,96 @@ export function PasswordForm<
<form onSubmit={form.handleSubmit(onSubmit)} className="w-[35ch] max-w-full space-y-4">
<FormField
control={form.control}
name={(isPassword ? "password" : "code") as any}
name={"code"}
render={({ field }) => (
<FormItem className={cn("flex flex-col", !isPassword ? "items-center" : "")}>
<FormItem className="flex flex-col items-center">
<FormLabel className="shrink-0">
{message?.label ??
(isPassword ? t("profile.current_password.label") : t("profile.totp_code.label"))}
{message?.label ?? t("profile.totp_code.label")}
</FormLabel>
<FormControl>
{isPassword ? (
<Input
<m.div variants={shakeVariants} animate={controls} className="flex justify-center">
<InputOTP
disabled={updateMutation.isPending}
autoFocus
type="password"
placeholder={message?.placeholder ?? t("profile.current_password.label")}
className="!w-full"
maxLength={6}
onComplete={() => form.handleSubmit(onSubmit)()}
{...field}
/>
) : (
<m.div
variants={shakeVariants}
animate={controls}
className="flex justify-center"
>
<InputOTP
disabled={updateMutation.isPending}
autoFocus
className="!w-full"
maxLength={6}
onComplete={() => form.handleSubmit(onSubmit)()}
{...field}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</m.div>
)}
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</m.div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{isPassword && (
<div className="text-right">
<Button type="submit" isLoading={updateMutation.isPending}>
{t("profile.submit")}
</Button>
</div>
)}
</form>
</Form>
)
}

export function PasswordForm({
message,
onSubmitMutationFn,
onSuccess,
}: PasswordFormProps<PasswordFormValues>) {
const { t } = useTranslation("settings")

const form = useForm<PasswordFormValues>({
resolver: zodResolver(passwordFormSchema),
defaultValues: { password: "" },
})

const updateMutation = useMutation({
mutationFn: onSubmitMutationFn,
onError: (error) => {
toast.error(error.message)
},
onSuccess,
})

function onSubmit(values: PasswordFormValues) {
updateMutation.mutate(values)
}

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-[35ch] max-w-full space-y-4">
<FormField
control={form.control}
name={"password"}
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel className="shrink-0">
{message?.label ?? t("profile.current_password.label")}
</FormLabel>
<FormControl>
<Input
disabled={updateMutation.isPending}
autoFocus
type="password"
placeholder={message?.placeholder ?? t("profile.current_password.label")}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<div className="text-right">
<Button type="submit" isLoading={updateMutation.isPending}>
{t("profile.submit")}
</Button>
</div>
</form>
</Form>
)
Expand All @@ -171,8 +204,7 @@ export const TwoFactorForm = () => {
<div className="flex items-center justify-center">
<QRCode value={totpURI} />
</div>
<PasswordForm
valueType="totp"
<TOTPForm
message={{
label: t("profile.totp_code.init"),
}}
Expand All @@ -192,7 +224,6 @@ export const TwoFactorForm = () => {
</div>
) : (
<PasswordForm
valueType="password"
onSubmitMutationFn={async (values) => {
const res = user?.twoFactorEnabled
? await twoFactor.disable({ password: values.password })
Expand Down

0 comments on commit e6750ee

Please sign in to comment.