Skip to content

Commit

Permalink
feat: Update SignUp and SimpleLoginPage components to use updated use…
Browse files Browse the repository at this point in the history
…SignUpAllowed hook
  • Loading branch information
PintoGideon committed Nov 5, 2024
1 parent 4ff0e74 commit dd045b3
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 49 deletions.
84 changes: 38 additions & 46 deletions src/components/Login/index.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,43 @@
import ChrisApiClient from "@fnndsc/chrisapi";
import {
HelperText,
HelperTextItem,
ListItem,
ListVariant,
LoginFooterItem,
LoginForm,
LoginMainFooterBandItem,
LoginPage,
} from "@patternfly/react-core";
import { App } from "antd";
import queryString from "query-string";
import React from "react";
// src/components/LoginPage/SimpleLoginPage.tsx
import type React from "react";
import { useState } from "react";
import { useCookies } from "react-cookie";
import { Link, useLocation, useNavigate } from "react-router-dom";
import ChrisAPIClient from "../../api/chrisapiclient";
import ChRIS_Logo_Inline from "../../assets/chris-logo-inline.png";
import ChRIS_Logo from "../../assets/chris-logo.png";
import { useAppDispatch } from "../../store/hooks.ts";
import { setAuthTokenSuccess } from "../../store/user/userSlice";
import { ExclamationCircleIcon } from "../Icons";
import "./Login.css";
import { useAppDispatch } from "../../store/hooks.ts";
import { useSignUpAllowed } from "../../store/hooks.ts";

type Status = "idle" | "loading" | "success" | "error";

export const SimpleLoginPage: React.FunctionComponent = () => {
const navigate = useNavigate();
const location = useLocation();
const dispatch = useAppDispatch();
const [_cookies, setCookie] = useCookies<string>([""]);
const [showHelperText, setShowHelperText] = React.useState(false);
const [username, setUsername] = React.useState("");
const [isValidUsername, setIsValidUsername] = React.useState(true);
const [password, setPassword] = React.useState("");
const [isValidPassword, setIsValidPassword] = React.useState(true);
const [errorMessage, setErrorMessage] = React.useState("");

enum LoginErrorMessage {
invalidCredentials = "Invalid Credentials",
serverError = "There was a problem connecting to the server!",
}
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [status, setStatus] = useState<Status>("idle");

// Use the custom hook
const { signUpAllowed } = useSignUpAllowed();

// Use the message API from Ant Design
const { message } = App.useApp();

async function handleSubmit(
event:
Expand All @@ -46,10 +47,15 @@ export const SimpleLoginPage: React.FunctionComponent = () => {
event.preventDefault();

const authURL = import.meta.env.VITE_CHRIS_UI_AUTH_URL;
let token: string;

setStatus("loading");

try {
token = await ChrisApiClient.getAuthToken(authURL, username, password);
const token = await ChrisApiClient.getAuthToken(
authURL,
username,
password,
);
if (token && username) {
const oneDayToSeconds = 24 * 60 * 60;
setCookie(`${username}_token`, token, {
Expand All @@ -74,6 +80,8 @@ export const SimpleLoginPage: React.FunctionComponent = () => {
}),
);

setStatus("success");

const { redirectTo } = queryString.parse(location.search) as {
redirectTo: string;
};
Expand All @@ -93,17 +101,13 @@ export const SimpleLoginPage: React.FunctionComponent = () => {
navigate("/");
}
}
} catch (error: unknown) {
setShowHelperText(true);
// Allows error message to be displayed in red
setIsValidUsername(false);
setIsValidPassword(false);

setErrorMessage(() =>
//@ts-ignore
} catch (error: any) {
setStatus("error");
message.error(
error.response
? LoginErrorMessage.invalidCredentials
: LoginErrorMessage.serverError,
? "Invalid Credentials"
: "There was a problem connecting to the server!",
3,
);
}
}
Expand All @@ -122,20 +126,12 @@ export const SimpleLoginPage: React.FunctionComponent = () => {
setPassword(value);
};

let helperText: React.ReactNode = null;
if (showHelperText) {
helperText = (
<HelperText>
<HelperTextItem variant="error">{errorMessage}</HelperTextItem>
</HelperText>
);
}

const signUpForAccountMessage = (
// Conditionally render the "Sign up" link based on signUpAllowed state
const signUpForAccountMessage = signUpAllowed ? (
<LoginMainFooterBandItem>
Need an account? <Link to="/signup">Sign up.</Link>
</LoginMainFooterBandItem>
);
) : null;

const forgotCredentials = (
<LoginMainFooterBandItem>
Expand All @@ -144,7 +140,7 @@ export const SimpleLoginPage: React.FunctionComponent = () => {
);

const listItem = (
<React.Fragment>
<>
<ListItem>
<LoginFooterItem href="https://web.chrisproject.org/">
Terms of Use{" "}
Expand All @@ -160,24 +156,20 @@ export const SimpleLoginPage: React.FunctionComponent = () => {
Privacy Policy
</LoginFooterItem>
</ListItem>
</React.Fragment>
</>
);

const loginForm = (
<LoginForm
showHelperText={showHelperText}
helperText={helperText}
helperTextIcon={<ExclamationCircleIcon />}
usernameLabel="Username"
usernameValue={username}
onChangeUsername={handleUsernameChange}
isValidUsername={isValidUsername}
passwordLabel="Password"
passwordValue={password}
onChangePassword={handlePasswordChange}
isValidPassword={isValidPassword}
onLoginButtonClick={handleSubmit}
loginButtonLabel="Log in"
isLoginButtonDisabled={status === "loading"}
/>
);

Expand Down
66 changes: 64 additions & 2 deletions src/components/Signup/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,75 @@
import { LoginPage } from "@patternfly/react-core";
import { App, Spin } from "antd";
import React from "react";
import { useNavigate } from "react-router-dom";
import { useSignUpAllowed } from "../../store/hooks";
import SignUpForm from "./SignUpForm";

const SignUp = () => {
const SignUp: React.FunctionComponent = () => {
const { signUpAllowed, isLoading, isError } = useSignUpAllowed();
const navigate = useNavigate();

// Use the message API from Ant Design
const { message } = App.useApp();

React.useEffect(() => {
let timer: NodeJS.Timeout;

if (!isLoading && !signUpAllowed) {
// If sign-ups are not allowed, show error and redirect after delay
message.error(
"Anonymous sign-ups are not allowed on this platform. Redirecting to login page...",
3, // Duration in seconds
);
timer = setTimeout(() => {
navigate("/login");
}, 3000); // Redirect after 3 seconds
} else if (isError) {
// If there was an error checking sign-up availability
message.error(
"Failed to check sign-up availability. Please try again later. Redirecting to login page...",
3,
);
timer = setTimeout(() => {
navigate("/login");
}, 3000);
}

// Cleanup the timer if the component unmounts
return () => {
if (timer) {
clearTimeout(timer);
}
};
}, [signUpAllowed, isLoading, isError, navigate, message]);

// Determine what content to render inside the LoginPage
let content: React.ReactNode;
if (isLoading) {
// Display a loading spinner while checking sign-up availability
content = (
<div style={{ textAlign: "center", marginTop: "50px" }}>
<Spin size="large" tip="Checking sign-up availability..." />
</div>
);
} else if ((!isLoading && !signUpAllowed) || isError) {
// Show a message indicating redirecting
content = (
<div>
<p>Redirecting to the login page...</p>
</div>
);
} else {
// If sign-ups are allowed, render the sign-up form
content = <SignUpForm />;
}

return (
<LoginPage
className="login pf-background"
loginTitle="Sign up for a new account"
>
<SignUpForm />
{content}
</LoginPage>
);
};
Expand Down
69 changes: 68 additions & 1 deletion src/store/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { Feed, FileBrowserFolderFile } from "@fnndsc/chrisapi";
import { useMutation } from "@tanstack/react-query";
import {
type UseQueryOptions,
useMutation,
useQuery,
} from "@tanstack/react-query";
import { App } from "antd";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import ChrisAPIClient from "../api/chrisapiclient";
import { getFileName } from "../api/common";
Expand Down Expand Up @@ -70,3 +76,64 @@ const useDownload = (feed?: Feed) => {
};

export default useDownload;

// src/hooks/useSignUpAllowed.ts

export const useSignUpAllowed = () => {
// Use the message API from Ant Design
const { message } = App.useApp();

const fetchSignUpAllowed = async (): Promise<boolean> => {
const apiUrl = import.meta.env.VITE_CHRIS_UI_USERS_URL;

if (!apiUrl) {
throw new Error("URL for fetching the users is not set correctly");
}

// Make an unauthenticated GET request to api/v1/users/
const response = await fetch(apiUrl, {
method: "GET",
});

if (!response.ok) {
throw new Error(`Server responded with status ${response.status}`);
}

// Get the Allow header from the response
const allowHeader = response.headers.get("Allow");

return allowHeader?.includes("POST") || false;
};

// Explicitly define the type for options
const queryOptions: UseQueryOptions<
boolean,
Error,
boolean,
["signUpAllowed"]
> = {
queryKey: ["signUpAllowed"],
queryFn: fetchSignUpAllowed,
retry: false,
};

const {
data: signUpAllowed,
isLoading,
isError,
error,
} = useQuery<boolean, Error, boolean, ["signUpAllowed"]>(queryOptions);

// Handle errors using useEffect
useEffect(() => {
if (isError && error) {
message.error(
error.message ||
"Failed to check sign-up availability. Please try again later.",
3, // Duration in seconds
);
}
}, [isError, error, message]);

return { signUpAllowed, isLoading, isError, error };
};

0 comments on commit dd045b3

Please sign in to comment.