Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor UX for some of the login related pages #337

Merged
merged 1 commit into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions frontend/occupi-web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
Rooms,
AboutPage,
SecurityPage,
ForgotPassword,
ResetPassword,
} from "@pages/index";
import {
Appearance,
Expand Down Expand Up @@ -87,6 +89,26 @@ function App() {
)
}
/>
<Route
path="/forgot-password"
element={
isAuthenticated ? (
<Navigate to="/dashboard/overview" />
) : (
<ForgotPassword />
)
}
/>
<Route
path="/reset-password"
element={
isAuthenticated ? (
<Navigate to="/dashboard/overview" />
) : (
<ResetPassword />
)
}
/>
<Route
path="/*"
element={
Expand Down
48 changes: 42 additions & 6 deletions frontend/occupi-web/src/AuthService.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import axios from "axios";
import { wrapper } from "axios-cookiejar-support";
import { CookieJar } from "tough-cookie";

const jar = new CookieJar();
const client = wrapper(axios.create({ jar }));

const API_URL = "/auth"; // This will be proxied to https://dev.occupi.tech
const API_USER_URL = "/api"; // Adjust this if needed
Expand Down Expand Up @@ -188,7 +183,7 @@ const AuthService = {
logout: async () => {
try {
// Perform the logout request
const response = await client.post(`${API_URL}/logout`, {
const response = await axios.post(`${API_URL}/logout`, {
withCredentials: true,
});
console.log(response.data);
Expand Down Expand Up @@ -284,6 +279,47 @@ const AuthService = {
throw new Error("An unexpected error occurred during OTP verification");
}
},

sendResetEmail: async (email: string) => {
try {
const response = await axios.post(`${API_URL}/forgot-password`, {
"email": email
});
if (response.data.status === 200) {
return response.data;
} else {
throw new Error(response.data.message || "Failed to send reset email");
}
} catch (error) {
console.error("Error in sendResetEmail:", error);
if (axios.isAxiosError(error) && error.response) {
throw error.response.data;
}
throw new Error("An unexpected error occurred while sending reset email");
}
},

resetPassword: async (email: string, otp: string, newPassword: string, newPasswordConfirm: string) => {
try {
const response = await axios.post(`${API_URL}/reset-password-admin-login`, {
"email": email,
"otp": otp,
"newPassword": newPassword,
"newPasswordConfirm": newPasswordConfirm
});
if (response.data.status === 200) {
return response.data;
} else {
throw new Error(response.data.message || "Failed to send reset email");
}
} catch (error) {
console.error("Error in sendResetEmail:", error);
if (axios.isAxiosError(error) && error.response) {
throw error.response.data;
}
throw new Error("An unexpected error occurred while sending reset email");
}
},
};

function bufferEncode(value: ArrayBuffer): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ const OtpComponent = (props: OtpComponentProps) => {
};

return (
<div className="flex flex-col items-center">
<div className="w-full flex flex-col items-center">
{err !== "" && <h5 className="text-text_col_red_salmon font-normal text-base mt-3 mb-1">{err}</h5>}
<div className="flex space-x-2 md:space-x-4">
<div className="w-full flex justify-between">
{otp.map((data, index) => (
<input
key={index}
Expand Down
52 changes: 37 additions & 15 deletions frontend/occupi-web/src/pages/Login/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { loginpng, OccupiLogo } from "@assets/index";
import { Checkbox, GradientButton, InputBox, OccupiLoader } from "@components/index";
import { GradientButton, InputBox, OccupiLoader } from "@components/index";
import { useNavigate } from "react-router-dom";
import AuthService from "AuthService";
import { useUser } from "userStore";
Expand All @@ -18,6 +18,7 @@ const LoginForm = (): JSX.Element => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string>("");
const [requiresOtp, setRequiresOtp] = useState<boolean>(false);
const [webauthn, setWebauthn] = useState<boolean>(false);

const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
Expand All @@ -30,7 +31,7 @@ const LoginForm = (): JSX.Element => {
return;
}

if(!window.PublicKeyCredential || form.password !== "" ) {
if(!webauthn) {
if (form.password === "") {
setError("Please fill in password field");
setIsLoading(false);
Expand Down Expand Up @@ -101,8 +102,24 @@ const LoginForm = (): JSX.Element => {
}
};

const canUseWebauthn = () => {
return window.PublicKeyCredential !== undefined;
}

const attemptWebauthn = async () => {
if (!canUseWebauthn()) {
setWebauthn(false);
} else {
setWebauthn(true);
}
}

const clickSubmit = () => { document.getElementById("LoginFormSubmitButton")?.click(); }

useEffect(() => {
attemptWebauthn();
}, []);

return (
<div className="flex flex-col lg:flex-row justify-center items-center min-h-screen p-4">
{isLoading && <OccupiLoader message={requiresOtp ? "Redirecting to OTP page..." : "Logging you in..."} />}
Expand All @@ -126,21 +143,25 @@ const LoginForm = (): JSX.Element => {
setForm({ ...form, email: val, valid_email: validity })
}}
/>
<InputBox
type="password"
placeholder="Enter your password"
label="Password"
submitValue={(val, validity) => {
setForm({ ...form, password: val, valid_password: validity })
}}
/>
{ !webauthn &&
<InputBox
type="password"
placeholder="Enter your password"
label="Password"
submitValue={(val, validity) => {
setForm({ ...form, password: val, valid_password: validity })
}}
/>
}

<div className="w-full flex flex-col sm:flex-row justify-between items-center mt-5 space-y-2 sm:space-y-0">
<div className="flex items-center">
<Checkbox id="rememberMeCheckbox" />
<p className="text-text_col_green_leaf cursor-pointer ml-2">Remember me</p>
{ !canUseWebauthn() ? <p className="text-text_col_green_leaf opacity-70">Use passwordless login</p> :
webauthn ? <p className="text-text_col_green_leaf cursor-pointer" onClick={() => setWebauthn(false)}>Use password instead</p>
: <p className="text-text_col_green_leaf cursor-pointer" onClick={attemptWebauthn}>Use password-less login</p>
}
</div>
<p className="text-text_col_green_leaf cursor-pointer">Forgot Password?</p>
<p className="text-text_col_green_leaf cursor-pointer" onClick={() => navigate("/forgot-password")}>Forgot Password?</p>
</div>

{error && <p className="text-red-500 mt-2">{error}</p>}
Expand All @@ -157,7 +178,8 @@ const LoginForm = (): JSX.Element => {

<div className="flex items-center justify-center mt-5 mb-5">
<p className="text-text_col">New to occupi?</p>
<p className="ml-2 text-text_col_green_leaf cursor-pointer">Learn more</p>
<a className="ml-2 text-text_col_green_leaf cursor-pointer" href="https://occupi.tech" target="ref">
Learn more</a>
</div>
</form>
</div>
Expand Down
79 changes: 79 additions & 0 deletions frontend/occupi-web/src/pages/forgot-password/ForgotPassword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { OccupiLogo, login_image } from "@assets/index";
import { GradientButton, OccupiLoader, InputBox } from "@components/index";
import AuthService from "AuthService";

const ForgotPassword = () => {
const navigate = useNavigate();

const [form, setForm] = useState<{
email: string,
valid_email: boolean
}>({ email: "", valid_email: false });
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string>("");

async function sendResetEmail() {
setIsLoading(true);
setError("");

try{
const response = await AuthService.sendResetEmail(form.email);

if (response.message.includes('check your email for an otp')) {
navigate("/reset-password", { state: { email: form.email } });
setIsLoading(false);
return;
}
setIsLoading(false);
setError("An unexpected error occurred");
} catch (error) {
console.error("Login error:", error);
if (typeof error === 'object' && error !== null && 'message' in error) {
setError(error.message as string);
} else {
setError("An unexpected error occurred");
}
setIsLoading(false);
return;
}
}

return (
<div className="flex flex-col md:flex-row justify-center w-screen h-screen items-center p-4">
{isLoading && <OccupiLoader message="Please wait..." />}
<div className="w-full md:w-[60vw] h-auto md:h-[40vw] flex justify-center items-center mb-8 md:mb-0">
<div className="w-full md:w-[70vw] h-auto md:h-[35vw]">
<img className="w-full h-full object-cover" src={login_image} alt="welcomes" />
</div>
</div>
<div className="w-full md:w-[30vw] md:ml-10 md:mr-3 flex flex-col items-center px-4">
<div className="w-[30vw] max-w-[150px] h-auto aspect-square mb-6">
<OccupiLogo />
</div>
<h2 className="text-text_col font-semibold text-3xl md:text-4xl lg:text-5xl text-center mb-4">Forgot your password?</h2>
<h3 className="text-text_col font-extralight text-xl md:text-2xl text-center mb-6">
Enter your email address below
</h3>

<InputBox
type="email"
placeholder="Enter your email address"
label="Email Address"
submitValue={(val, validity) => {
setForm({ ...form, email: val, valid_email: validity })
}}
/>

<div className="mt-5 w-full max-w-md">
<GradientButton isLoading={isLoading} Text="Send OTP" isClickable={form.valid_email} clickEvent={sendResetEmail} />
</div>

{error && <p className="text-red-500 mt-2 text-center">{error}</p>}
</div>
</div>
);
};

export default ForgotPassword;
4 changes: 4 additions & 0 deletions frontend/occupi-web/src/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import Rooms from "./rooms/Rooms";
import { NotificationsSettings } from "./notificationsSettings/NotificationsSettings";
import AboutPage from "./about/AboutPage";
import SecurityPage from "./securityPage/SecurityPage";
import ForgotPassword from './forgot-password/ForgotPassword';
import ResetPassword from './reset-password/ResetPassword';
export {
LoginForm,
OtpPage,
Expand All @@ -31,4 +33,6 @@ export {
NotificationsSettings,
AboutPage,
SecurityPage,
ForgotPassword,
ResetPassword
};
4 changes: 1 addition & 3 deletions frontend/occupi-web/src/pages/otp-page/OtpPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ const OtpPage = () => {
if (state && state.email) {
setEmail(state.email);
} else {
setError("Email not provided. Please start the login process again.");
// Optionally, redirect to login page after a short delay
// setTimeout(() => navigate('/login'), 3000);
navigate("/")
}
}, [location.state, navigate]);

Expand Down
Loading