Skip to content

Commit

Permalink
Refactor auth hook (#865)
Browse files Browse the repository at this point in the history
  • Loading branch information
InfiniteStash authored Dec 20, 2024
1 parent d9be7b9 commit c898ef4
Show file tree
Hide file tree
Showing 38 changed files with 189 additions and 177 deletions.
8 changes: 4 additions & 4 deletions frontend/src/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { FC, useContext, useState } from "react";
import { FC, useState } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { useForm } from "react-hook-form";
import { Button, Col, Form, Row } from "react-bootstrap";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import cx from "classnames";

import AuthContext, { ContextType } from "src/AuthContext";
import { ROUTE_REGISTER, ROUTE_FORGOT_PASSWORD } from "src/constants/route";
import { getPlatformURL, getCredentialsSetting } from "src/utils/createClient";

import "./App.scss";
import { useCurrentUser } from "./hooks";

const schema = yup.object({
username: yup.string().required("Username is required"),
Expand All @@ -30,7 +30,7 @@ const Login: FC = () => {
const [loginError, setLoginError] = useState("");
const msg = new URLSearchParams(location.search).get("msg");
const redirect = new URLSearchParams(location.search).get("redirect");
const Auth = useContext<ContextType>(AuthContext);
const { isAuthenticated } = useCurrentUser();
const {
register,
handleSubmit,
Expand All @@ -39,7 +39,7 @@ const Login: FC = () => {
resolver: yupResolver(schema),
});

if (Auth.authenticated) navigate("/");
if (isAuthenticated) navigate("/");

const onSubmit = async (formData: LoginFormData) => {
setLoading(true);
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { faBell as faBellOutlined } from "@fortawesome/free-regular-svg-icons";

import SearchField, { SearchType } from "src/components/searchField";
import { getPlatformURL, getCredentialsSetting } from "src/utils/createClient";
import { isAdmin, canEdit, userHref, setCachedUser } from "src/utils";
import { userHref, setCachedUser, canEdit, isAdmin } from "src/utils";
import { useAuth } from "src/hooks";
import { Icon } from "src/components/fragments";
import { useConfig, useUnreadNotificationsCount } from "src/graphql";
Expand All @@ -28,7 +28,7 @@ import {
ROUTE_DRAFTS,
ROUTE_NOTIFICATIONS,
} from "src/constants/route";
import AuthContext from "./AuthContext";
import AuthContext from "./context";

interface Props {
children?: React.ReactNode;
Expand Down
10 changes: 4 additions & 6 deletions frontend/src/components/deleteButton/DeleteButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { FC, useState, useContext } from "react";
import { FC, useState } from "react";
import { Button } from "react-bootstrap";

import AuthContext from "src/AuthContext";
import { isAdmin } from "src/utils";

import Modal from "src/components/modal";
import { useCurrentUser } from "src/hooks";

interface DeleteButtonProps {
message?: string;
Expand All @@ -19,8 +17,8 @@ const DeleteButton: FC<DeleteButtonProps> = ({
disabled = false,
className,
}) => {
const { isAdmin } = useCurrentUser();
const [showDelete, setShowDelete] = useState(false);
const auth = useContext(AuthContext);

const toggleModal = () => setShowDelete(true);
const handleDelete = (status: boolean): void => {
Expand All @@ -40,7 +38,7 @@ const DeleteButton: FC<DeleteButtonProps> = ({
return (
<>
{deleteModal}
{isAdmin(auth.user) && (
{isAdmin && (
<Button
variant="danger"
disabled={showDelete || disabled}
Expand Down
9 changes: 4 additions & 5 deletions frontend/src/components/editCard/AddComment.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { FC, useContext, useState } from "react";
import { FC, useState } from "react";
import { Button, Form } from "react-bootstrap";
import { GraphQLFormattedError } from "graphql";
import { useEditComment } from "src/graphql";
import cx from "classnames";

import AuthContext from "src/AuthContext";
import { canEdit } from "src/utils";
import { NoteInput } from "src/components/form";
import { useCurrentUser } from "src/hooks";

interface IProps {
editID: string;
}

const AddComment: FC<IProps> = ({ editID }) => {
const auth = useContext(AuthContext);
const { isEditor } = useCurrentUser();
const [showInput, setShowInput] = useState(false);
const [errors, setErrors] = useState<readonly GraphQLFormattedError[]>([]);
const [comment, setComment] = useState("");
Expand All @@ -22,7 +21,7 @@ const AddComment: FC<IProps> = ({ editID }) => {
if (!showInput)
return (
<div className="d-flex">
{!showInput && canEdit(auth.user) && (
{!showInput && isEditor && (
<Button
className="ms-auto minimal"
variant="link"
Expand Down
14 changes: 5 additions & 9 deletions frontend/src/components/editCard/VoteBar.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { FC, useContext, useState } from "react";
import { FC, useState } from "react";
import { Button, Form } from "react-bootstrap";
import { faCheck } from "@fortawesome/free-solid-svg-icons";
import cx from "classnames";

import AuthContext from "src/AuthContext";
import {
VoteStatusEnum,
VoteTypeEnum,
useVote,
EditFragment,
} from "src/graphql";
import { Icon } from "src/components/fragments";
import { canVote } from "src/utils";
import { useCurrentUser } from "src/hooks";

const CLASSNAME = "VoteBar";
const CLASSNAME_BUTTON = `${CLASSNAME}-button`;
Expand All @@ -23,10 +22,8 @@ interface Props {
}

const VoteBar: FC<Props> = ({ edit }) => {
const auth = useContext(AuthContext);
const userVote = (edit.votes ?? []).find(
(v) => v.user?.id && v.user.id === auth.user?.id,
);
const { isVoter, isSelf } = useCurrentUser();
const userVote = (edit.votes ?? []).find((v) => v.user?.id && isSelf(v.user));
const [vote, setVote] = useState<VoteTypeEnum | null>(userVote?.vote ?? null);
const [submitVote, { loading: savingVote }] = useVote();

Expand All @@ -42,8 +39,7 @@ const VoteBar: FC<Props> = ({ edit }) => {
);

// Only show vote total for edit owner and users without vote role
if (!canVote(auth.user) || auth.user?.id === edit.user?.id)
return <div>{currentVote}</div>;
if (!isVoter || isSelf(edit.user)) return <div>{currentVote}</div>;

const handleSave = () => {
if (!vote) return;
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/form/NoteInput.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { FC, ChangeEvent, useContext, useState } from "react";
import { FC, ChangeEvent, useState } from "react";
import { Form, Tabs, Tab } from "react-bootstrap";
import cx from "classnames";

import AuthContext from "src/AuthContext";
import EditComment from "src/components/editCard/EditComment";
import { UseFormRegister } from "react-hook-form";
import { useCurrentUser } from "src/hooks";

interface IProps {
onChange?: (text: string) => void;
Expand All @@ -20,7 +20,7 @@ const NoteInput: FC<IProps> = ({
register,
hasError = false,
}) => {
const auth = useContext(AuthContext);
const { user } = useCurrentUser();
const [comment, setComment] = useState("");

const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
Expand All @@ -45,10 +45,10 @@ const NoteInput: FC<IProps> = ({
</Tab>
<Tab eventKey="preview" title="Preview" unmountOnExit mountOnEnter>
<EditComment
id={`${auth.user?.id}-${now}`}
id={`${user?.id}-${now}`}
comment={comment}
date={now}
user={auth.user}
user={user}
/>
</Tab>
</Tabs>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/AuthContext.tsx → frontend/src/context.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext } from "react";
import { createContext, useContext } from "react";

import { RoleEnum } from "src/graphql";

Expand All @@ -17,4 +17,6 @@ const AuthContext = createContext<ContextType>({
authenticated: false,
});

export const useAuthContext = () => useContext(AuthContext);

export default AuthContext;
11 changes: 4 additions & 7 deletions frontend/src/graphql/queries/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { useContext } from "react";
import {
useQuery,
useLazyQuery,
QueryHookOptions,
LazyQueryHookOptions,
} from "@apollo/client";

import AuthContext from "src/AuthContext";
import { isAdmin } from "src/utils";

import {
CategoryDocument,
CategoryQueryVariables,
Expand Down Expand Up @@ -79,6 +75,7 @@ import {
NotificationsQueryVariables,
UnreadNotificationCountDocument,
} from "../types";
import { useCurrentUser } from "src/hooks";

export const useCategory = (variables: CategoryQueryVariables, skip = false) =>
useQuery(CategoryDocument, {
Expand Down Expand Up @@ -237,9 +234,9 @@ export const usePublicUser = (
});

export const useUser = (variables: UserQueryVariables, skip = false) => {
const Auth = useContext(AuthContext);
const isUser = () => Auth.user?.name === variables.name;
const showPrivate = isUser() || isAdmin(Auth.user);
const { isAdmin, user } = useCurrentUser();
const isUser = () => user?.name === variables.name;
const showPrivate = isUser() || isAdmin;

const privateUser = usePrivateUser(variables, skip || !showPrivate);
const publicUser = usePublicUser(variables, skip || showPrivate);
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { default as useEditFilter } from "./useEditFilter";
export { default as useAuth } from "./useAuth";
export { useToast } from "./useToast";
export { useQueryParams } from "./useQueryParams";
export { useCurrentUser } from "./useCurrentUser";
2 changes: 1 addition & 1 deletion frontend/src/hooks/useAuth.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMe } from "src/graphql";
import { getCachedUser, setCachedUser } from "src/utils";
import { User } from "../AuthContext";
import { User } from "../context";

interface AuthResult {
loading: boolean;
Expand Down
29 changes: 29 additions & 0 deletions frontend/src/hooks/useCurrentUser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useContext, useMemo, useCallback } from "react";

import AuthContext from "src/context";
import { isAdmin as userIsAdmin, canEdit, canVote } from "src/utils";

export const useCurrentUser = () => {
const auth = useContext(AuthContext);

const isAdmin = useMemo(() => userIsAdmin(auth.user), [auth.user]);
const isEditor = useMemo(() => canEdit(auth.user), [auth.user]);
const isVoter = useMemo(() => canVote(auth.user), [auth.user]);
const isSelf = useCallback(
(user?: (typeof auth)["user"] | string | null) => {
if (!auth.user?.id || !user) return false;
if (typeof user === "string") return user === auth.user.id;
return user?.id === auth.user.id;
},
[auth.user],
);

return {
isAdmin,
isSelf,
isEditor,
isVoter,
isAuthenticated: auth.authenticated,
user: auth.user,
};
};
8 changes: 4 additions & 4 deletions frontend/src/pages/activateUser/ActivateUser.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { FC, useContext, useState } from "react";
import { FC, useState } from "react";
import { ApolloError } from "@apollo/client";
import { yupResolver } from "@hookform/resolvers/yup";
import { useForm } from "react-hook-form";
import { useNavigate, useLocation } from "react-router-dom";
import { Button, Form, Row, Col } from "react-bootstrap";
import AuthContext, { ContextType } from "src/AuthContext";
import * as yup from "yup";
import cx from "classnames";

import { useActivateUser } from "src/graphql";
import { ROUTE_HOME, ROUTE_LOGIN } from "src/constants/route";
import Title from "src/components/title";
import { useCurrentUser } from "src/hooks";

const schema = yup.object({
name: yup.string().required("Username is required"),
Expand All @@ -26,7 +26,7 @@ function useQuery() {
const ActivateNewUserPage: FC = () => {
const query = useQuery();
const navigate = useNavigate();
const Auth = useContext<ContextType>(AuthContext);
const { isAuthenticated } = useCurrentUser();
const [submitError, setSubmitError] = useState<string | undefined>();

const {
Expand All @@ -39,7 +39,7 @@ const ActivateNewUserPage: FC = () => {

const [activateNewUser] = useActivateUser();

if (Auth.authenticated) navigate(ROUTE_HOME);
if (isAuthenticated) navigate(ROUTE_HOME);

const onSubmit = (formData: ActivateNewUserFormData) => {
const userData = {
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/pages/categories/Categories.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { FC, useContext } from "react";
import { FC } from "react";
import { Link } from "react-router-dom";
import { Button, Card } from "react-bootstrap";
import { sortBy, groupBy } from "lodash-es";

import { useCategories } from "src/graphql";
import { LoadingIndicator } from "src/components/fragments";
import { isAdmin, createHref } from "src/utils";
import { createHref } from "src/utils";
import { ROUTE_CATEGORY, ROUTE_CATEGORY_ADD } from "src/constants/route";
import AuthContext from "src/AuthContext";
import { useCurrentUser } from "src/hooks";

const CategoryList: FC = () => {
const auth = useContext(AuthContext);
const { isAdmin } = useCurrentUser();
const { loading, data } = useCategories();

const categoryGroups = groupBy(
Expand Down Expand Up @@ -43,7 +43,7 @@ const CategoryList: FC = () => {
<>
<div className="d-flex">
<h3 className="me-4">Categories</h3>
{isAdmin(auth.user) && (
{isAdmin && (
<Link to={ROUTE_CATEGORY_ADD} className="ms-auto">
<Button>Create</Button>
</Link>
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/pages/categories/Category.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { FC, useContext } from "react";
import { FC } from "react";
import { Link, useNavigate } from "react-router-dom";
import { Button, Row } from "react-bootstrap";

import { useDeleteCategory, CategoryQuery } from "src/graphql";
import AuthContext from "src/AuthContext";
import { isAdmin, createHref } from "src/utils";
import { createHref } from "src/utils";
import DeleteButton from "src/components/deleteButton";
import { TagList } from "src/components/list";
import { ROUTE_CATEGORIES, ROUTE_CATEGORY_EDIT } from "src/constants/route";
import { useCurrentUser } from "src/hooks";

type Category = NonNullable<CategoryQuery["findTagCategory"]>;

Expand All @@ -17,7 +17,7 @@ interface Props {

const CategoryComponent: FC<Props> = ({ category }) => {
const navigate = useNavigate();
const auth = useContext(AuthContext);
const { isAdmin } = useCurrentUser();

const [deleteCategory, { loading: deleting }] = useDeleteCategory({
onCompleted: (result) => {
Expand All @@ -43,7 +43,7 @@ const CategoryComponent: FC<Props> = ({ category }) => {
<em>{category.name}</em>
</h3>
<div className="ms-auto">
{isAdmin(auth.user) && (
{isAdmin && (
<>
<Link
to={createHref(ROUTE_CATEGORY_EDIT, category)}
Expand Down
Loading

0 comments on commit c898ef4

Please sign in to comment.