Page {table.getState().pagination.pageIndex + 1} of{" "}
{table.getPageCount()}
diff --git a/src/components/Disable2FaForm.tsx b/src/components/Disable2FaForm.tsx
new file mode 100644
index 00000000..e3e9f168
--- /dev/null
+++ b/src/components/Disable2FaForm.tsx
@@ -0,0 +1,227 @@
+"use client";
+
+import { useState } from "react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { createApiClient } from "@app/api";
+import { useEnvContext } from "@app/hooks/useEnvContext";
+import { AxiosResponse } from "axios";
+import { Disable2faBody, Disable2faResponse } from "@server/routers/auth";
+import { z } from "zod";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage
+} from "@app/components/ui/form";
+import {
+ Credenza,
+ CredenzaBody,
+ CredenzaClose,
+ CredenzaContent,
+ CredenzaDescription,
+ CredenzaFooter,
+ CredenzaHeader,
+ CredenzaTitle
+} from "@app/components/Credenza";
+import { useToast } from "@app/hooks/useToast";
+import { formatAxiosError } from "@app/lib/utils";
+import { useUserContext } from "@app/hooks/useUserContext";
+import { InputOTP, InputOTPGroup, InputOTPSlot } from "./ui/input-otp";
+import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
+import { CheckCircle2 } from "lucide-react";
+
+const disableSchema = z.object({
+ password: z.string().min(1, { message: "Password is required" }),
+ code: z.string().min(1, { message: "Code is required" })
+});
+
+type Disable2FaProps = {
+ open: boolean;
+ setOpen: (val: boolean) => void;
+};
+
+export default function Disable2FaForm({ open, setOpen }: Disable2FaProps) {
+ const [loading, setLoading] = useState(false);
+
+ const [step, setStep] = useState<"password" | "success">("password");
+
+ const { toast } = useToast();
+
+ const { user, updateUser } = useUserContext();
+
+ const api = createApiClient(useEnvContext());
+
+ const disableForm = useForm
>({
+ resolver: zodResolver(disableSchema),
+ defaultValues: {
+ password: "",
+ code: ""
+ }
+ });
+
+ const request2fa = async (values: z.infer) => {
+ setLoading(true);
+
+ const res = await api
+ .post>(`/auth/2fa/disable`, {
+ password: values.password,
+ code: values.code
+ } as Disable2faBody)
+ .catch((e) => {
+ toast({
+ title: "Unable to disable 2FA",
+ description: formatAxiosError(
+ e,
+ "An error occurred while disabling 2FA"
+ ),
+ variant: "destructive"
+ });
+ });
+
+ if (res) {
+ // toast({
+ // title: "Two-factor disabled",
+ // description:
+ // "Two-factor authentication has been disabled for your account"
+ // });
+ updateUser({ twoFactorEnabled: false });
+ setStep("success");
+ }
+
+ setLoading(false);
+ };
+
+ return (
+ {
+ setOpen(val);
+ setLoading(false);
+ }}
+ >
+
+
+
+ Disable Two-factor Authentication
+
+
+ Disable two-factor authentication for your account
+
+
+
+ {step === "password" && (
+
+
+ )}
+
+ {step === "success" && (
+
+
+
+ Two-Factor Authentication Disabled
+
+
+ Two-factor authentication has been disabled for
+ your account. You can enable it again at any
+ time.
+
+
+ )}
+
+
+ {step === "password" && (
+
+ )}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Enable2FaForm.tsx b/src/components/Enable2FaForm.tsx
index 9266edca..0bdaf1fd 100644
--- a/src/components/Enable2FaForm.tsx
+++ b/src/components/Enable2FaForm.tsx
@@ -39,7 +39,7 @@ import { useToast } from "@app/hooks/useToast";
import { formatAxiosError } from "@app/lib/utils";
import CopyTextBox from "@app/components/CopyTextBox";
import { QRCodeSVG } from "qrcode.react";
-import { userUserContext } from "@app/hooks/useUserContext";
+import { useUserContext } from "@app/hooks/useUserContext";
const enableSchema = z.object({
password: z.string().min(1, { message: "Password is required" })
@@ -57,6 +57,7 @@ type Enable2FaProps = {
export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
const [step, setStep] = useState(1);
const [secretKey, setSecretKey] = useState("");
+ const [secretUri, setSecretUri] = useState("");
const [verificationCode, setVerificationCode] = useState("");
const [error, setError] = useState("");
const [success, setSuccess] = useState(false);
@@ -65,7 +66,7 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
const { toast } = useToast();
- const { user, updateUser } = userUserContext();
+ const { user, updateUser } = useUserContext();
const api = createApiClient(useEnvContext());
@@ -106,6 +107,7 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
if (res && res.data.data.secret) {
setSecretKey(res.data.data.secret);
+ setSecretUri(res.data.data.uri);
setStep(2);
}
@@ -132,7 +134,7 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
if (res && res.data.data.valid) {
setBackupCodes(res.data.data.backupCodes || []);
- updateUser({ twoFactorEnabled: true })
+ updateUser({ twoFactorEnabled: true });
setStep(3);
}
@@ -203,11 +205,11 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
{step === 2 && (
- scan this qr code with your authenticator app or
+ Scan this QR code with your authenticator app or
enter the secret key manually:
-
-
+
+
(
- Verification Code
+ Authenticator Code
(
@@ -62,6 +63,7 @@ export function Header({ orgId, orgs }: HeaderProps) {
);
const [openEnable2fa, setOpenEnable2fa] = useState(false);
+ const [openDisable2fa, setOpenDisable2fa] = useState(false);
const router = useRouter();
@@ -93,6 +95,7 @@ export function Header({ orgId, orgs }: HeaderProps) {
return (
<>
+
@@ -133,7 +136,9 @@ export function Header({ orgId, orgs }: HeaderProps) {
)}
{user.twoFactorEnabled && (
-
+ setOpenDisable2fa(true)}
+ >
Disable Two-factor
)}
diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx
index 3da37fec..0d9402f3 100644
--- a/src/components/LoginForm.tsx
+++ b/src/components/LoginForm.tsx
@@ -103,8 +103,6 @@ export default function LoginForm({ redirect, onLogin }: LoginFormProps) {
const data = res.data.data;
- console.log(data);
-
if (data?.codeRequested) {
setMfaRequested(true);
setLoading(false);
@@ -136,6 +134,7 @@ export default function LoginForm({ redirect, onLogin }: LoginFormProps) {
-
- {error && (
-
- {error}
-
- )}
-
)}
{mfaRequested && (
-
-
+ <>
+
+
+ Two-Factor Authentication
+
+
+ Enter the code from your authenticator app.
+
+
+
+
+ >
)}
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+ {mfaRequested && (
+
+ )}
+
+ {!mfaRequested && (
+
+ )}
+
+ {mfaRequested && (
+
+ )}
+
);
}
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
index 2f4da515..d4dca564 100644
--- a/src/components/ui/input.tsx
+++ b/src/components/ui/input.tsx
@@ -10,18 +10,10 @@ const Input = React.forwardRef
(
const [showPassword, setShowPassword] = React.useState(false);
const togglePasswordVisibility = () => setShowPassword(!showPassword);
- console.log("type", type);
-
- return (
+ return type === "password" ? (
(
ref={ref}
{...props}
/>
- {type === "password" && (
-
- {showPassword ? (
-
- ) : (
-
- )}
-
- )}
+
+ {showPassword ? (
+
+ ) : (
+
+ )}
+
+ ) : (
+
);
}
);
diff --git a/src/hooks/useUserContext.ts b/src/hooks/useUserContext.ts
index cf90217d..e0b64b4b 100644
--- a/src/hooks/useUserContext.ts
+++ b/src/hooks/useUserContext.ts
@@ -1,7 +1,7 @@
import UserContext from "@app/contexts/userContext";
import { useContext } from "react";
-export function userUserContext() {
+export function useUserContext() {
const context = useContext(UserContext);
if (context === undefined) {
throw new Error("useUserContext must be used within a UserProvider");
From e0b1aa98e00966f1e63fbbfca9941b6531e92bdc Mon Sep 17 00:00:00 2001
From: Milo Schwartz
Date: Tue, 24 Dec 2024 15:43:48 -0500
Subject: [PATCH 2/2] add custom 404 page
---
src/app/not-found.tsx | 15 +++++++++++++++
1 file changed, 15 insertions(+)
create mode 100644 src/app/not-found.tsx
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
new file mode 100644
index 00000000..eb0aea84
--- /dev/null
+++ b/src/app/not-found.tsx
@@ -0,0 +1,15 @@
+import Link from "next/link";
+
+export default async function NotFound() {
+ return (
+
+
404
+
+ Page Not Found
+
+
+ Oops! The page you're looking for doesn't exist.
+
+
+ );
+}