♻️ Tweaks in frontend (#1273)
This commit is contained in:
@@ -20,7 +20,7 @@ import { type SubmitHandler, useForm } from "react-hook-form"
|
|||||||
import { type UserCreate, UsersService } from "../../client"
|
import { type UserCreate, UsersService } from "../../client"
|
||||||
import type { ApiError } from "../../client/core/ApiError"
|
import type { ApiError } from "../../client/core/ApiError"
|
||||||
import useCustomToast from "../../hooks/useCustomToast"
|
import useCustomToast from "../../hooks/useCustomToast"
|
||||||
import { emailPattern } from "../../utils"
|
import { emailPattern, handleError } from "../../utils"
|
||||||
|
|
||||||
interface AddUserProps {
|
interface AddUserProps {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
@@ -62,8 +62,7 @@ const AddUser = ({ isOpen, onClose }: AddUserProps) => {
|
|||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
onError: (err: ApiError) => {
|
onError: (err: ApiError) => {
|
||||||
const errDetail = (err.body as any)?.detail
|
handleError(err, showToast)
|
||||||
showToast("Something went wrong.", `${errDetail}`, "error")
|
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["users"] })
|
queryClient.invalidateQueries({ queryKey: ["users"] })
|
||||||
|
@@ -24,7 +24,7 @@ import {
|
|||||||
UsersService,
|
UsersService,
|
||||||
} from "../../client"
|
} from "../../client"
|
||||||
import useCustomToast from "../../hooks/useCustomToast"
|
import useCustomToast from "../../hooks/useCustomToast"
|
||||||
import { emailPattern } from "../../utils"
|
import { emailPattern, handleError } from "../../utils"
|
||||||
|
|
||||||
interface EditUserProps {
|
interface EditUserProps {
|
||||||
user: UserPublic
|
user: UserPublic
|
||||||
@@ -60,8 +60,7 @@ const EditUser = ({ user, isOpen, onClose }: EditUserProps) => {
|
|||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
onError: (err: ApiError) => {
|
onError: (err: ApiError) => {
|
||||||
const errDetail = (err.body as any)?.detail
|
handleError(err, showToast)
|
||||||
showToast("Something went wrong.", `${errDetail}`, "error")
|
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["users"] })
|
queryClient.invalidateQueries({ queryKey: ["users"] })
|
||||||
|
@@ -17,6 +17,7 @@ import { type SubmitHandler, useForm } from "react-hook-form"
|
|||||||
|
|
||||||
import { type ApiError, type ItemCreate, ItemsService } from "../../client"
|
import { type ApiError, type ItemCreate, ItemsService } from "../../client"
|
||||||
import useCustomToast from "../../hooks/useCustomToast"
|
import useCustomToast from "../../hooks/useCustomToast"
|
||||||
|
import { handleError } from "../../utils"
|
||||||
|
|
||||||
interface AddItemProps {
|
interface AddItemProps {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
@@ -49,8 +50,7 @@ const AddItem = ({ isOpen, onClose }: AddItemProps) => {
|
|||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
onError: (err: ApiError) => {
|
onError: (err: ApiError) => {
|
||||||
const errDetail = (err.body as any)?.detail
|
handleError(err, showToast)
|
||||||
showToast("Something went wrong.", `${errDetail}`, "error")
|
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["items"] })
|
queryClient.invalidateQueries({ queryKey: ["items"] })
|
||||||
|
@@ -22,6 +22,7 @@ import {
|
|||||||
ItemsService,
|
ItemsService,
|
||||||
} from "../../client"
|
} from "../../client"
|
||||||
import useCustomToast from "../../hooks/useCustomToast"
|
import useCustomToast from "../../hooks/useCustomToast"
|
||||||
|
import { handleError } from "../../utils"
|
||||||
|
|
||||||
interface EditItemProps {
|
interface EditItemProps {
|
||||||
item: ItemPublic
|
item: ItemPublic
|
||||||
@@ -51,8 +52,7 @@ const EditItem = ({ item, isOpen, onClose }: EditItemProps) => {
|
|||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
onError: (err: ApiError) => {
|
onError: (err: ApiError) => {
|
||||||
const errDetail = (err.body as any)?.detail
|
handleError(err, showToast)
|
||||||
showToast("Something went wrong.", `${errDetail}`, "error")
|
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["items"] })
|
queryClient.invalidateQueries({ queryKey: ["items"] })
|
||||||
|
@@ -14,7 +14,7 @@ import { type SubmitHandler, useForm } from "react-hook-form"
|
|||||||
|
|
||||||
import { type ApiError, type UpdatePassword, UsersService } from "../../client"
|
import { type ApiError, type UpdatePassword, UsersService } from "../../client"
|
||||||
import useCustomToast from "../../hooks/useCustomToast"
|
import useCustomToast from "../../hooks/useCustomToast"
|
||||||
import { confirmPasswordRules, passwordRules } from "../../utils"
|
import { confirmPasswordRules, handleError, passwordRules } from "../../utils"
|
||||||
|
|
||||||
interface UpdatePasswordForm extends UpdatePassword {
|
interface UpdatePasswordForm extends UpdatePassword {
|
||||||
confirm_password: string
|
confirm_password: string
|
||||||
@@ -42,8 +42,7 @@ const ChangePassword = () => {
|
|||||||
reset()
|
reset()
|
||||||
},
|
},
|
||||||
onError: (err: ApiError) => {
|
onError: (err: ApiError) => {
|
||||||
const errDetail = (err.body as any)?.detail
|
handleError(err, showToast)
|
||||||
showToast("Something went wrong.", `${errDetail}`, "error")
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@ import { useForm } from "react-hook-form"
|
|||||||
import { type ApiError, UsersService } from "../../client"
|
import { type ApiError, UsersService } from "../../client"
|
||||||
import useAuth from "../../hooks/useAuth"
|
import useAuth from "../../hooks/useAuth"
|
||||||
import useCustomToast from "../../hooks/useCustomToast"
|
import useCustomToast from "../../hooks/useCustomToast"
|
||||||
|
import { handleError } from "../../utils"
|
||||||
|
|
||||||
interface DeleteProps {
|
interface DeleteProps {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
@@ -42,8 +43,7 @@ const DeleteConfirmation = ({ isOpen, onClose }: DeleteProps) => {
|
|||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
onError: (err: ApiError) => {
|
onError: (err: ApiError) => {
|
||||||
const errDetail = (err.body as any)?.detail
|
handleError(err, showToast)
|
||||||
showToast("Something went wrong.", `${errDetail}`, "error")
|
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["currentUser"] })
|
queryClient.invalidateQueries({ queryKey: ["currentUser"] })
|
||||||
|
@@ -23,7 +23,7 @@ import {
|
|||||||
} from "../../client"
|
} from "../../client"
|
||||||
import useAuth from "../../hooks/useAuth"
|
import useAuth from "../../hooks/useAuth"
|
||||||
import useCustomToast from "../../hooks/useCustomToast"
|
import useCustomToast from "../../hooks/useCustomToast"
|
||||||
import { emailPattern } from "../../utils"
|
import { emailPattern, handleError } from "../../utils"
|
||||||
|
|
||||||
const UserInformation = () => {
|
const UserInformation = () => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
@@ -57,13 +57,10 @@ const UserInformation = () => {
|
|||||||
showToast("Success!", "User updated successfully.", "success")
|
showToast("Success!", "User updated successfully.", "success")
|
||||||
},
|
},
|
||||||
onError: (err: ApiError) => {
|
onError: (err: ApiError) => {
|
||||||
const errDetail = (err.body as any)?.detail
|
handleError(err, showToast)
|
||||||
showToast("Something went wrong.", `${errDetail}`, "error")
|
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
// TODO: can we do just one call now?
|
queryClient.invalidateQueries()
|
||||||
queryClient.invalidateQueries({ queryKey: ["users"] })
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["currentUser"] })
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -104,6 +101,8 @@ const UserInformation = () => {
|
|||||||
size="md"
|
size="md"
|
||||||
py={2}
|
py={2}
|
||||||
color={!currentUser?.full_name ? "ui.dim" : "inherit"}
|
color={!currentUser?.full_name ? "ui.dim" : "inherit"}
|
||||||
|
isTruncated
|
||||||
|
maxWidth="250px"
|
||||||
>
|
>
|
||||||
{currentUser?.full_name || "N/A"}
|
{currentUser?.full_name || "N/A"}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -125,7 +124,7 @@ const UserInformation = () => {
|
|||||||
w="auto"
|
w="auto"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text size="md" py={2}>
|
<Text size="md" py={2} isTruncated maxWidth="250px">
|
||||||
{currentUser?.email}
|
{currentUser?.email}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
@@ -47,7 +47,7 @@ const useAuth = () => {
|
|||||||
errDetail = err.message
|
errDetail = err.message
|
||||||
}
|
}
|
||||||
|
|
||||||
showToast("Something went wrong.", `${errDetail}`, "error")
|
showToast("Something went wrong.", errDetail, "error")
|
||||||
},
|
},
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["users"] })
|
queryClient.invalidateQueries({ queryKey: ["users"] })
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Box,
|
Box,
|
||||||
|
Button,
|
||||||
Container,
|
Container,
|
||||||
Flex,
|
Flex,
|
||||||
Heading,
|
Heading,
|
||||||
@@ -13,90 +14,65 @@ import {
|
|||||||
Thead,
|
Thead,
|
||||||
Tr,
|
Tr,
|
||||||
} from "@chakra-ui/react"
|
} from "@chakra-ui/react"
|
||||||
import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query"
|
import { useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute, useNavigate } from "@tanstack/react-router"
|
||||||
|
import { useEffect } from "react"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
import { Suspense } from "react"
|
|
||||||
import { type UserPublic, UsersService } from "../../client"
|
import { type UserPublic, UsersService } from "../../client"
|
||||||
import AddUser from "../../components/Admin/AddUser"
|
import AddUser from "../../components/Admin/AddUser"
|
||||||
import ActionsMenu from "../../components/Common/ActionsMenu"
|
import ActionsMenu from "../../components/Common/ActionsMenu"
|
||||||
import Navbar from "../../components/Common/Navbar"
|
import Navbar from "../../components/Common/Navbar"
|
||||||
|
|
||||||
export const Route = createFileRoute("/_layout/admin")({
|
const usersSearchSchema = z.object({
|
||||||
component: Admin,
|
page: z.number().catch(1),
|
||||||
})
|
})
|
||||||
|
|
||||||
const MembersTableBody = () => {
|
export const Route = createFileRoute("/_layout/admin")({
|
||||||
|
component: Admin,
|
||||||
|
validateSearch: (search) => usersSearchSchema.parse(search),
|
||||||
|
})
|
||||||
|
|
||||||
|
const PER_PAGE = 5
|
||||||
|
|
||||||
|
function getUsersQueryOptions({ page }: { page: number }) {
|
||||||
|
return {
|
||||||
|
queryFn: () =>
|
||||||
|
UsersService.readUsers({ skip: (page - 1) * PER_PAGE, limit: PER_PAGE }),
|
||||||
|
queryKey: ["users", { page }],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function UsersTable() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const currentUser = queryClient.getQueryData<UserPublic>(["currentUser"])
|
const currentUser = queryClient.getQueryData<UserPublic>(["currentUser"])
|
||||||
|
const { page } = Route.useSearch()
|
||||||
|
const navigate = useNavigate({ from: Route.fullPath })
|
||||||
|
const setPage = (page: number) =>
|
||||||
|
navigate({ search: (prev) => ({ ...prev, page }) })
|
||||||
|
|
||||||
const { data: users } = useSuspenseQuery({
|
const {
|
||||||
queryKey: ["users"],
|
data: users,
|
||||||
queryFn: () => UsersService.readUsers({}),
|
isPending,
|
||||||
|
isPlaceholderData,
|
||||||
|
} = useQuery({
|
||||||
|
...getUsersQueryOptions({ page }),
|
||||||
|
placeholderData: (prevData) => prevData,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
const hasNextPage = !isPlaceholderData && users?.data.length === PER_PAGE
|
||||||
<Tbody>
|
const hasPreviousPage = page > 1
|
||||||
{users.data.map((user) => (
|
|
||||||
<Tr key={user.id}>
|
|
||||||
<Td color={!user.full_name ? "ui.dim" : "inherit"}>
|
|
||||||
{user.full_name || "N/A"}
|
|
||||||
{currentUser?.id === user.id && (
|
|
||||||
<Badge ml="1" colorScheme="teal">
|
|
||||||
You
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</Td>
|
|
||||||
<Td>{user.email}</Td>
|
|
||||||
<Td>{user.is_superuser ? "Superuser" : "User"}</Td>
|
|
||||||
<Td>
|
|
||||||
<Flex gap={2}>
|
|
||||||
<Box
|
|
||||||
w="2"
|
|
||||||
h="2"
|
|
||||||
borderRadius="50%"
|
|
||||||
bg={user.is_active ? "ui.success" : "ui.danger"}
|
|
||||||
alignSelf="center"
|
|
||||||
/>
|
|
||||||
{user.is_active ? "Active" : "Inactive"}
|
|
||||||
</Flex>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<ActionsMenu
|
|
||||||
type="User"
|
|
||||||
value={user}
|
|
||||||
disabled={currentUser?.id === user.id ? true : false}
|
|
||||||
/>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
))}
|
|
||||||
</Tbody>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const MembersBodySkeleton = () => {
|
useEffect(() => {
|
||||||
return (
|
if (hasNextPage) {
|
||||||
<Tbody>
|
queryClient.prefetchQuery(getUsersQueryOptions({ page: page + 1 }))
|
||||||
<Tr>
|
}
|
||||||
{new Array(5).fill(null).map((_, index) => (
|
}, [page, queryClient, hasNextPage])
|
||||||
<Td key={index}>
|
|
||||||
<SkeletonText noOfLines={1} paddingBlock="16px" />
|
|
||||||
</Td>
|
|
||||||
))}
|
|
||||||
</Tr>
|
|
||||||
</Tbody>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Admin() {
|
|
||||||
return (
|
return (
|
||||||
<Container maxW="full">
|
<>
|
||||||
<Heading size="lg" textAlign={{ base: "center", md: "left" }} pt={12}>
|
|
||||||
User Management
|
|
||||||
</Heading>
|
|
||||||
<Navbar type={"User"} addModalAs={AddUser} />
|
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table fontSize="md" size={{ base: "sm", md: "md" }}>
|
<Table size={{ base: "sm", md: "md" }}>
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th width="20%">Full name</Th>
|
<Th width="20%">Full name</Th>
|
||||||
@@ -106,11 +82,89 @@ function Admin() {
|
|||||||
<Th width="10%">Actions</Th>
|
<Th width="10%">Actions</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
<Suspense fallback={<MembersBodySkeleton />}>
|
{isPending ? (
|
||||||
<MembersTableBody />
|
<Tbody>
|
||||||
</Suspense>
|
<Tr>
|
||||||
|
{new Array(4).fill(null).map((_, index) => (
|
||||||
|
<Td key={index}>
|
||||||
|
<SkeletonText noOfLines={1} paddingBlock="16px" />
|
||||||
|
</Td>
|
||||||
|
))}
|
||||||
|
</Tr>
|
||||||
|
</Tbody>
|
||||||
|
) : (
|
||||||
|
<Tbody>
|
||||||
|
{users?.data.map((user) => (
|
||||||
|
<Tr key={user.id}>
|
||||||
|
<Td
|
||||||
|
color={!user.full_name ? "ui.dim" : "inherit"}
|
||||||
|
isTruncated
|
||||||
|
maxWidth="150px"
|
||||||
|
>
|
||||||
|
{user.full_name || "N/A"}
|
||||||
|
{currentUser?.id === user.id && (
|
||||||
|
<Badge ml="1" colorScheme="teal">
|
||||||
|
You
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td isTruncated maxWidth="150px">
|
||||||
|
{user.email}
|
||||||
|
</Td>
|
||||||
|
<Td>{user.is_superuser ? "Superuser" : "User"}</Td>
|
||||||
|
<Td>
|
||||||
|
<Flex gap={2}>
|
||||||
|
<Box
|
||||||
|
w="2"
|
||||||
|
h="2"
|
||||||
|
borderRadius="50%"
|
||||||
|
bg={user.is_active ? "ui.success" : "ui.danger"}
|
||||||
|
alignSelf="center"
|
||||||
|
/>
|
||||||
|
{user.is_active ? "Active" : "Inactive"}
|
||||||
|
</Flex>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<ActionsMenu
|
||||||
|
type="User"
|
||||||
|
value={user}
|
||||||
|
disabled={currentUser?.id === user.id ? true : false}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
)}
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
<Flex
|
||||||
|
gap={4}
|
||||||
|
alignItems="center"
|
||||||
|
mt={4}
|
||||||
|
direction="row"
|
||||||
|
justifyContent="flex-end"
|
||||||
|
>
|
||||||
|
<Button onClick={() => setPage(page - 1)} isDisabled={!hasPreviousPage}>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
<span>Page {page}</span>
|
||||||
|
<Button isDisabled={!hasNextPage} onClick={() => setPage(page + 1)}>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Admin() {
|
||||||
|
return (
|
||||||
|
<Container maxW="full">
|
||||||
|
<Heading size="lg" textAlign={{ base: "center", md: "left" }} pt={12}>
|
||||||
|
Users Management
|
||||||
|
</Heading>
|
||||||
|
|
||||||
|
<Navbar type={"User"} addModalAs={AddUser} />
|
||||||
|
<UsersTable />
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ import {
|
|||||||
Container,
|
Container,
|
||||||
Flex,
|
Flex,
|
||||||
Heading,
|
Heading,
|
||||||
Skeleton,
|
SkeletonText,
|
||||||
Table,
|
Table,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
Tbody,
|
Tbody,
|
||||||
@@ -64,7 +64,7 @@ function ItemsTable() {
|
|||||||
if (hasNextPage) {
|
if (hasNextPage) {
|
||||||
queryClient.prefetchQuery(getItemsQueryOptions({ page: page + 1 }))
|
queryClient.prefetchQuery(getItemsQueryOptions({ page: page + 1 }))
|
||||||
}
|
}
|
||||||
}, [page, queryClient])
|
}, [page, queryClient, hasNextPage])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -80,25 +80,27 @@ function ItemsTable() {
|
|||||||
</Thead>
|
</Thead>
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<Tbody>
|
<Tbody>
|
||||||
{new Array(5).fill(null).map((_, index) => (
|
<Tr>
|
||||||
<Tr key={index}>
|
{new Array(4).fill(null).map((_, index) => (
|
||||||
{new Array(4).fill(null).map((_, index) => (
|
<Td key={index}>
|
||||||
<Td key={index}>
|
<SkeletonText noOfLines={1} paddingBlock="16px" />
|
||||||
<Flex>
|
</Td>
|
||||||
<Skeleton height="20px" width="20px" />
|
))}
|
||||||
</Flex>
|
</Tr>
|
||||||
</Td>
|
|
||||||
))}
|
|
||||||
</Tr>
|
|
||||||
))}
|
|
||||||
</Tbody>
|
</Tbody>
|
||||||
) : (
|
) : (
|
||||||
<Tbody>
|
<Tbody>
|
||||||
{items?.data.map((item) => (
|
{items?.data.map((item) => (
|
||||||
<Tr key={item.id} opacity={isPlaceholderData ? 0.5 : 1}>
|
<Tr key={item.id} opacity={isPlaceholderData ? 0.5 : 1}>
|
||||||
<Td>{item.id}</Td>
|
<Td>{item.id}</Td>
|
||||||
<Td>{item.title}</Td>
|
<Td isTruncated maxWidth="150px">
|
||||||
<Td color={!item.description ? "ui.dim" : "inherit"}>
|
{item.title}
|
||||||
|
</Td>
|
||||||
|
<Td
|
||||||
|
color={!item.description ? "ui.dim" : "inherit"}
|
||||||
|
isTruncated
|
||||||
|
maxWidth="150px"
|
||||||
|
>
|
||||||
{item.description || "N/A"}
|
{item.description || "N/A"}
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
|
@@ -14,7 +14,7 @@ import { type SubmitHandler, useForm } from "react-hook-form"
|
|||||||
import { type ApiError, LoginService } from "../client"
|
import { type ApiError, LoginService } from "../client"
|
||||||
import { isLoggedIn } from "../hooks/useAuth"
|
import { isLoggedIn } from "../hooks/useAuth"
|
||||||
import useCustomToast from "../hooks/useCustomToast"
|
import useCustomToast from "../hooks/useCustomToast"
|
||||||
import { emailPattern } from "../utils"
|
import { emailPattern, handleError } from "../utils"
|
||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
email: string
|
email: string
|
||||||
@@ -57,8 +57,7 @@ function RecoverPassword() {
|
|||||||
reset()
|
reset()
|
||||||
},
|
},
|
||||||
onError: (err: ApiError) => {
|
onError: (err: ApiError) => {
|
||||||
const errDetail = (err.body as any)?.detail
|
handleError(err, showToast)
|
||||||
showToast("Something went wrong.", `${errDetail}`, "error")
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -15,7 +15,7 @@ import { type SubmitHandler, useForm } from "react-hook-form"
|
|||||||
import { type ApiError, LoginService, type NewPassword } from "../client"
|
import { type ApiError, LoginService, type NewPassword } from "../client"
|
||||||
import { isLoggedIn } from "../hooks/useAuth"
|
import { isLoggedIn } from "../hooks/useAuth"
|
||||||
import useCustomToast from "../hooks/useCustomToast"
|
import useCustomToast from "../hooks/useCustomToast"
|
||||||
import { confirmPasswordRules, passwordRules } from "../utils"
|
import { confirmPasswordRules, handleError, passwordRules } from "../utils"
|
||||||
|
|
||||||
interface NewPasswordForm extends NewPassword {
|
interface NewPasswordForm extends NewPassword {
|
||||||
confirm_password: string
|
confirm_password: string
|
||||||
@@ -65,8 +65,7 @@ function ResetPassword() {
|
|||||||
navigate({ to: "/login" })
|
navigate({ to: "/login" })
|
||||||
},
|
},
|
||||||
onError: (err: ApiError) => {
|
onError: (err: ApiError) => {
|
||||||
const errDetail = (err.body as any)?.detail
|
handleError(err, showToast)
|
||||||
showToast("Something went wrong.", `${errDetail}`, "error")
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import type { ApiError } from "./client"
|
||||||
|
|
||||||
export const emailPattern = {
|
export const emailPattern = {
|
||||||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||||
message: "Invalid email address",
|
message: "Invalid email address",
|
||||||
@@ -40,3 +42,12 @@ export const confirmPasswordRules = (
|
|||||||
|
|
||||||
return rules
|
return rules
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const handleError = (err: ApiError, showToast: any) => {
|
||||||
|
const errDetail = (err.body as any)?.detail
|
||||||
|
let errorMessage = errDetail || "Something went wrong."
|
||||||
|
if (Array.isArray(errDetail) && errDetail.length > 0) {
|
||||||
|
errorMessage = errDetail[0].msg
|
||||||
|
}
|
||||||
|
showToast("Error", errorMessage, "error")
|
||||||
|
}
|
||||||
|
@@ -3,7 +3,6 @@ import { firstSuperuser, firstSuperuserPassword } from "./config.ts"
|
|||||||
|
|
||||||
const authFile = "playwright/.auth/user.json"
|
const authFile = "playwright/.auth/user.json"
|
||||||
|
|
||||||
|
|
||||||
setup("authenticate", async ({ page }) => {
|
setup("authenticate", async ({ page }) => {
|
||||||
await page.goto("/login")
|
await page.goto("/login")
|
||||||
await page.getByPlaceholder("Email").fill(firstSuperuser)
|
await page.getByPlaceholder("Email").fill(firstSuperuser)
|
||||||
|
@@ -1,21 +1,21 @@
|
|||||||
import dotenv from 'dotenv';
|
import path from "node:path"
|
||||||
import path from 'path';
|
import { fileURLToPath } from "node:url"
|
||||||
import { fileURLToPath } from 'url';
|
import dotenv from "dotenv"
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename)
|
||||||
|
|
||||||
dotenv.config({ path: path.join(__dirname, '../../.env') });
|
dotenv.config({ path: path.join(__dirname, "../../.env") })
|
||||||
|
|
||||||
const { FIRST_SUPERUSER, FIRST_SUPERUSER_PASSWORD } = process.env;
|
const { FIRST_SUPERUSER, FIRST_SUPERUSER_PASSWORD } = process.env
|
||||||
|
|
||||||
if (typeof FIRST_SUPERUSER !== "string") {
|
if (typeof FIRST_SUPERUSER !== "string") {
|
||||||
throw new Error("Environment variable FIRST_SUPERUSER is undefined");
|
throw new Error("Environment variable FIRST_SUPERUSER is undefined")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof FIRST_SUPERUSER_PASSWORD !== "string") {
|
if (typeof FIRST_SUPERUSER_PASSWORD !== "string") {
|
||||||
throw new Error("Environment variable FIRST_SUPERUSER_PASSWORD is undefined");
|
throw new Error("Environment variable FIRST_SUPERUSER_PASSWORD is undefined")
|
||||||
}
|
}
|
||||||
|
|
||||||
export const firstSuperuser = FIRST_SUPERUSER as string;
|
export const firstSuperuser = FIRST_SUPERUSER as string
|
||||||
export const firstSuperuserPassword = FIRST_SUPERUSER_PASSWORD as string;
|
export const firstSuperuserPassword = FIRST_SUPERUSER_PASSWORD as string
|
||||||
|
Reference in New Issue
Block a user