Skip to content
Snippets Groups Projects
Commit 62bac45b authored by ajp434's avatar ajp434
Browse files

Admin can verify users

parent f7376c9f
No related branches found
No related tags found
1 merge request!8Admin can verify users
......@@ -124,7 +124,7 @@ gen-auth-client:
#Print Admin
print-admin:
@echo "Email: admin@example.com"
@echo "Email: admin@drexel.edu"
@echo "Password: Password123"
# Function to open link
......
......
......@@ -27,7 +27,7 @@ export const devHook = async (app: any, routes: any) => {
auth.api
.createUser({
body: {
email: 'admin@example.com',
email: 'admin@drexel.edu',
name: 'Admin',
password: 'Password123',
role: 'Admin',
......
......
......@@ -9,6 +9,7 @@ import { DialogRoot, DialogTrigger, DialogContent, DialogHeader, DialogTitle, Di
import { Field } from "@/components/ui/field";
import { createUserMutation } from "@/auth-client/@tanstack/react-query.gen";
import { User } from "@/auth-client";
import { drexelEmailPattern } from "@/utils";
interface UserCreateForm {
email: string;
......@@ -112,10 +113,7 @@ export default function AddUser({ roleOptions, refetchAction }: AddUserProps) {
placeholder="user@example.com"
{...register("email", {
required: "Email is required",
pattern: {
value: /\S+@\S+\.\S+/,
message: "Invalid email format",
},
pattern: drexelEmailPattern,
})}
/>
</Field>
......
......
// components/Admin/VeriyUser.tsx
import { useState } from "react";
import { QueryObserverResult, RefetchOptions, useMutation } from "@tanstack/react-query";
import { Button, Text, IconButton, Dialog } from "@chakra-ui/react";
import { Tooltip } from "@/components/ui/tooltip"
import { FaCheckCircle, FaTimes } from "react-icons/fa";
import useCustomToast from "@/hooks/useCustomToast";
import { User } from "@/auth-client/types.gen";
import { postUserVerifyByUserIdMutation } from "@/client/@tanstack/react-query.gen";
interface VerifyUserProps {
user: User;
isDisabled?: boolean;
refetchAction: (options?: RefetchOptions) => Promise<QueryObserverResult<{
users: Array<User>;
total: number;
limit?: number;
offset?: number;
}, Error>>;
}
export default function VerifyUser({ user, isDisabled = false, refetchAction }: VerifyUserProps) {
const { showSuccessToast, showErrorToast } = useCustomToast();
const [open, setOpen] = useState(false);
const verifyUserMutation = useMutation({
...postUserVerifyByUserIdMutation(),
onSuccess: () => {
showSuccessToast("User verified successfully");
setOpen(false);
setTimeout(() => {
refetchAction();
}, 600);
},
onError: (error) => {
showErrorToast(error.message || "Verification failed");
},
});
const handleUserVerify = () => {
console.log("Attempting to verify user:", user.id);
verifyUserMutation.mutate({
path: { user_id: user.id }
});
}
return (
<>
<Dialog.Root
open={open}
onOpenChange={(open) => setOpen(open)}
size={{ base: "xs", md: "md" }}
lazyMount
>
<Tooltip content="Verify User">
<Dialog.Trigger asChild>
<IconButton
aria-label="Verify user"
size="sm"
variant="ghost"
colorPalette="green"
disabled={isDisabled}
>
<FaCheckCircle />
</IconButton>
</Dialog.Trigger>
</Tooltip>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Verify User</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Text mb={4}>
Are you sure you want to verify user <strong>{user.email}</strong>?
</Text>
</Dialog.Body>
<Dialog.Footer>
<Dialog.ActionTrigger asChild>
<Button
variant="subtle"
colorPalette="gray"
>
Cancel
</Button>
</Dialog.ActionTrigger>
<Button
variant="solid"
colorPalette="green"
onClick={handleUserVerify}
loading={verifyUserMutation.isPending}
>
Verify User
</Button>
</Dialog.Footer>
<Dialog.CloseTrigger asChild>
<IconButton
aria-label="Close"
variant="ghost"
size="sm"
position="absolute"
top={5}
right={5}
>
<FaTimes />
</IconButton>
</Dialog.CloseTrigger>
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>
</>
);
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ import { type SubmitHandler, useForm } from "react-hook-form"
import useAuth from "@/hooks/useAuth"
import useCustomToast from "@/hooks/useCustomToast"
import { emailPattern, handleError } from "@/utils"
import { drexelEmailPattern, handleError } from "@/utils"
import { Field } from "../ui/field"
import { postChangeEmailMutation, postUpdateUserMutation } from "@/auth-client/@tanstack/react-query.gen"
......@@ -136,7 +136,7 @@ const UserInformation = () => {
<Input
{...register("email", {
required: "Email is required",
pattern: emailPattern,
pattern: drexelEmailPattern,
})}
type="email"
size="md"
......
......
//admin.tsx
import { Badge, Box, Container, createListCollection, Flex, Heading, Input, Table } from "@chakra-ui/react"
import { Badge, Box, Container, createListCollection, Flex, Heading, Input, Table, Checkbox } from "@chakra-ui/react"
import { useQuery } from "@tanstack/react-query"
import { createFileRoute, useNavigate } from "@tanstack/react-router"
import { z } from "zod"
......@@ -17,6 +17,7 @@ import EditUser from "@/components/Admin/EditUser";
import SetUserRole from "@/components/Admin/SetUserRole";
import DeleteUser from "@/components/Admin/DeleteUser";
import UserSessions from "@/components/Admin/UserSessions";
import VerifyUser from "@/components/Admin/VerifyUser";
import useAuth from "@/hooks/useAuth"
import { Select } from "@chakra-ui/react";
......@@ -55,7 +56,9 @@ export const Route = createFileRoute("/_protected/_main/admin")({
offset: ((page - 1) * PER_PAGE).toString(),
searchValue: filter || undefined,
filterField: role ? "role" : undefined,
filterValue: role || undefined
filterValue: role || undefined,
sortBy: "emailVerified",
sortDirection: "asc",
}
})),
component: () => {
......@@ -63,7 +66,6 @@ export const Route = createFileRoute("/_protected/_main/admin")({
const navigate = useNavigate({ from: Route.fullPath })
const { page, filter, role } = Route.useSearch()
const {
data: userData,
isLoading,
......@@ -75,7 +77,9 @@ export const Route = createFileRoute("/_protected/_main/admin")({
offset: ((page - 1) * PER_PAGE).toString(),
searchValue: filter || undefined,
filterField: role ? "role" : undefined,
filterValue: role || undefined
filterValue: role || undefined,
sortBy: "emailVerified",
sortDirection: "asc",
}
}),
placeholderData: (prevData) => prevData,
......@@ -93,7 +97,6 @@ export const Route = createFileRoute("/_protected/_main/admin")({
})
}
if (isLoading) {
return <PendingUsers />
}
......@@ -193,6 +196,11 @@ export const Route = createFileRoute("/_protected/_main/admin")({
isDisabled={currentUser?.id === user.id}
refetchAction={refetchAction}
/>
<VerifyUser
user={user}
isDisabled={user.emailVerified}
refetchAction={refetchAction}
/>
</Flex>
</Table.Cell>
</Table.Row>
......
......
......@@ -12,7 +12,7 @@ import { Field } from "@/components/ui/field"
import { InputGroup } from "@/components/ui/input-group"
import { PasswordInput } from "@/components/ui/password-input"
import Logo from "/assets/images/Logo.png"
import { emailPattern, passwordRules } from "../utils"
import { drexelEmailPattern, passwordRules } from "../utils"
import { getGetSessionOptions, postSignInEmailMutation } from "@/auth-client/@tanstack/react-query.gen"
import { useMutation } from "@tanstack/react-query"
import { handleError } from "@/utils"
......@@ -87,7 +87,7 @@ export const Route = createFileRoute("/login")({
id="email"
{...register("email", {
required: "email is required",
pattern: emailPattern,
pattern: drexelEmailPattern,
})}
placeholder="Email"
type="email"
......
......
......@@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button"
import { Field } from "@/components/ui/field"
import { InputGroup } from "@/components/ui/input-group"
import useCustomToast from "@/hooks/useCustomToast"
import { emailPattern, handleError } from "@/utils"
import { drexelEmailPattern, handleError } from "@/utils"
import { getGetSessionOptions, postForgetPasswordMutation } from "@/auth-client/@tanstack/react-query.gen"
interface FormData {
......@@ -67,7 +67,7 @@ export const Route = createFileRoute("/recover-password")({
id="email"
{...register("email", {
required: "Email is required",
pattern: emailPattern,
pattern: drexelEmailPattern,
})}
placeholder="Email"
type="email"
......
......
......@@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button"
import { Field } from "@/components/ui/field"
import { InputGroup } from "@/components/ui/input-group"
import { PasswordInput } from "@/components/ui/password-input"
import { confirmPasswordRules, emailPattern, handleError, passwordRules } from "@/utils"
import { confirmPasswordRules, drexelEmailPattern, handleError, passwordRules } from "@/utils"
import Logo from "/assets/images/Logo.png"
import { getGetSessionOptions, postSignUpEmailMutation } from "@/auth-client/@tanstack/react-query.gen"
import { useMutation } from "@tanstack/react-query"
......@@ -103,7 +103,7 @@ export const Route = createFileRoute("/signup")({
id="email"
{...register("email", {
required: "Email is required",
pattern: emailPattern,
pattern: drexelEmailPattern,
})}
placeholder="Email"
type="email"
......
......
export const emailPattern = {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "Invalid email address",
export const drexelEmailPattern = {
value: /^[a-zA-Z0-9._%+-]+@drexel\.edu$/,
message: "Invalid Drexel address",
};
export const namePattern = {
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment