Skip to content
Open
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
23 changes: 23 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,26 @@ body .header.dark-mode{
text-align: left;
color: #333;
}

/* loading spinner */

/* Loading Spinner */
.spinner {
width: 40px;
height: 40px;
border: 4px solid transparent; /* Transparent border for the spinner */
border-top-color: #3498db; /* Set a color for the top part of the spinner (primary blue) */
border-radius: 50%; /* Make it a circle */
animation: spin 1s linear infinite; /* Spin animation */
}

/* Spinner Animation */
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

40 changes: 32 additions & 8 deletions src/componenets/Header/LogoutBtn.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
// src/components/LogoutBtn.js

import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import authService from "../../appwrite/auth";
import { logout } from "../../store/authSlice";
import { setLoadingAuth, unSetLoadingAuth } from "../../store/loadingSlice";
import LoadingSpinner from "../LoadingSpinner";

function LogoutBtn() {
const [showModal, setShowModal] = useState(false);
const dispatch = useDispatch();
const isLoading = useSelector((state) => state.loading.loading);

const logoutHandler = async () => {
setShowModal(false); // Close modal after logout confirmation
dispatch(setLoadingAuth()); // Start loading

const logoutHandler = () => {
authService.logout().then(() => {
try {
await authService.logout();
dispatch(logout());
});
setShowModal(false); // Close modal after logout
} catch (error) {
console.error("Logout failed:", error.message);
} finally {
dispatch(unSetLoadingAuth()); // End loading
}
};

return (
Expand Down Expand Up @@ -39,17 +51,29 @@ function LogoutBtn() {
Cancel
</button>

{/* Confirm Logout Button */}
{/* Confirm Logout Button with Loading Indicator */}
<button
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 flex items-center"
onClick={logoutHandler}
disabled={isLoading} // Disable while loading
>
Confirm Logout
{isLoading ? (
<LoadingSpinner /> // Show loading spinner while logging out
) : (
"Confirm Logout"
)}
</button>
</div>
</div>
</div>
)}

{/* Full-page Loading Spinner */}
{isLoading && (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
<LoadingSpinner />
</div>
)}
</>
);
}
Expand Down
17 changes: 17 additions & 0 deletions src/componenets/LoadingSpinner.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'
import { useSelector } from 'react-redux'


export default function LoadingSpinner() {
const isLoading = useSelector((state) => state.loading.loading);

if (!isLoading) return null;

return (
<div className="fixed inset-0 flex items-center justify-center bg-gray-800 bg-opacity-50 z-50">
<div className="spinner"></div>
</div>
);
}


189 changes: 94 additions & 95 deletions src/componenets/Login.jsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,45 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
import { login as authLogin } from "../store/authSlice";
import { Button, Input, Logo } from "./index";
import authService from "../appwrite/auth";
import { useForm } from "react-hook-form";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import ForgotPasswordModal from "./ForgotPasswordModal.jsx";
import { setLoadingAuth, unSetLoadingAuth } from "../store/loadingSlice.js";
import LoadingSpinner from "./LoadingSpinner";

function Login() {
const navigate = useNavigate();
const dispatch = useDispatch();
const { register, handleSubmit, watch } = useForm();
const { register, handleSubmit } = useForm();
const [error, setError] = useState("");
const [loading, setLoading] = useState(false); // Added loading state
const [isForgotPasswordOpen, setIsForgotPasswordOpen] = useState(false); // Added state for modal
const [isForgotPasswordOpen, setIsForgotPasswordOpen] = useState(false);

// Retrieve loading state from Redux store
const loadingAuth = useSelector((state) => state.loading.loading);

// Log loading state whenever it changes
useEffect(() => {
console.log(`loading state is ${loadingAuth}`);
}, [loadingAuth]);

const login = async (data) => {
setError("");
setLoading(true); // Set loading to true when the request starts
dispatch(setLoadingAuth()); // Start loading
try {
const session = await authService.login(data);
if (session) {
const userData = await authService.getCurrentUser();
if (userData) dispatch(authLogin(userData));
navigate("/");
if (userData) {
dispatch(authLogin(userData));
navigate("/");
}
}
} catch (error) {
setError(error.message);
} finally {
setLoading(false); // Set loading to false after the request completes
dispatch(unSetLoadingAuth()); // End loading
}
};

Expand All @@ -37,95 +48,83 @@ function Login() {
};

return (
<div className='flex items-center justify-center w-full'>
<div
className={`mx-auto w-full max-w-lg bg-gray-100 rounded-xl p-10 border border-black/10`}
>
<div className='mb-2 flex justify-center'>
<span className='inline-block w-full max-w-[100px]'>
<Logo width='100%' />
</span>
<div className="relative w-full h-full">
{/* Full-page loading overlay */}
{loadingAuth && (
<div className="fixed inset-0 bg-gray-800 bg-opacity-50 z-50 flex items-center justify-center">
<LoadingSpinner />
</div>
<h2 className='text-center text-2xl font-bold leading-tight'>
Sign in to your account
</h2>
<p className='mt-2 text-center text-base text-black/60'>
Don&apos;t have any account?&nbsp;
<Link
to='/signup'
className='font-medium text-primary transition-all duration-200 hover:underline'
>
Sign Up
</Link>
</p>
{error && <p className='text-red-600 mt-8 text-center'>{error}</p>}
<form onSubmit={handleSubmit(login)} className='mt-8'>
<div className='space-y-5'>
<Input
label='Email: '
placeholder='Enter your email'
type='email'
{...register("email", {
required: true,
validate: {
matchPatern: (value) =>
/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(value) ||
"Email address must be a valid address",
},
})}
/>
<Input
label='Password: '
type='password'
placeholder='Enter your password'
{...register("password", {
required: true,
})}
/>
<Button
type='submit'
className='w-full'
disabled={loading} // Disable the button during loading
)}

<div className="flex items-center justify-center w-full">
<div
className={`mx-auto w-full max-w-lg bg-gray-100 rounded-xl p-10 border border-black/10`}
>
<div className="mb-2 flex justify-center">
<span className="inline-block w-full max-w-[100px]">
<Logo width="100%" />
</span>
</div>
<h2 className="text-center text-2xl font-bold leading-tight">
Sign in to your account
</h2>
<p className="mt-2 text-center text-base text-black/60">
Don&apos;t have an account?&nbsp;
<Link
to="/signup"
className="font-medium text-primary transition-all duration-200 hover:underline"
>
{loading ? (
<div className='flex items-center justify-center'>
<svg
className='animate-spin h-5 w-5 mr-2 text-white'
xmlns='http://www.w3.org/2000/svg'
fill='none'
viewBox='0 0 24 24'
>
<circle
className='opacity-25'
cx='12'
cy='12'
r='10'
stroke='currentColor'
strokeWidth='4'
></circle>
<path
className='opacity-75'
fill='currentColor'
d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z'
></path>
</svg>
Signing in...
</div>
) : (
"Sign in"
)}
</Button>
<p className='mt-2 text-center text-base text-black/60'>
<button
type='button'
className='font-medium text-primary transition-all duration-200 hover:underline'
onClick={handleForgotPassword}
Sign Up
</Link>
</p>
{error && <p className="text-red-600 mt-8 text-center">{error}</p>}
<form onSubmit={handleSubmit(login)} className="mt-8">
<div className="space-y-5">
<Input
label="Email: "
placeholder="Enter your email"
type="email"
{...register("email", {
required: true,
validate: {
matchPattern: (value) =>
/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(value) ||
"Email address must be a valid address",
},
})}
/>
<Input
label="Password: "
type="password"
placeholder="Enter your password"
{...register("password", { required: true })}
/>
<Button
type="submit"
className="w-full flex items-center justify-center"
disabled={loadingAuth} // Disable the button during loadingAuth
>
Forgot Password?
</button>
</p>
</div>
</form>
{loadingAuth ? (
<div className="flex items-center justify-center">
<LoadingSpinner className="h-5 w-5 mr-2" />
Signing in...
</div>
) : (
"Sign in"
)}
</Button>
<p className="mt-2 text-center text-base text-black/60">
<button
type="button"
className="font-medium text-primary transition-all duration-200 hover:underline"
onClick={handleForgotPassword}
>
Forgot Password?
</button>
</p>
</div>
</form>
</div>
</div>
<ForgotPasswordModal
isOpen={isForgotPasswordOpen}
Expand Down
Loading