Skip to content
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
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="StellarAid - Empowering communities through technology" />
<title>StellarAid</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div id="root"></div>
Expand Down
22 changes: 3 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 34 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,40 @@
import AppRouter from './routes/AppRouter';
import { Toaster } from 'react-hot-toast';

function App() {
return <AppRouter />;
return (
<>
<Toaster
position="top-right"
toastOptions={{
duration: 3500,
style: {
border: '1px solid #e5e7eb',
background: '#ffffff',
color: '#0F172A',
borderRadius: '8px',
fontFamily: "'Poppins', sans-serif",
fontSize: '.875rem',
boxShadow:
'0 10px 15px -3px rgba(15,23,42,.1), 0 4px 6px -4px rgba(15,23,42,.1)',
},
success: {
iconTheme: { primary: '#10B981', secondary: '#ffffff' },
style: { borderColor: '#10B981' },
},
error: {
iconTheme: { primary: '#EF4444', secondary: '#ffffff' },
style: { borderColor: '#EF4444' },
},
loading: {
iconTheme: { primary: '#6366F1', secondary: '#ffffff' },
style: { borderColor: '#6366F1' },
},
}}
/>
<AppRouter />
</>
);
}

export default App;
6 changes: 1 addition & 5 deletions src/components/common/GlobalLoadingWrapper.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useNavigation } from 'react-router-dom';
import { useSelector } from 'react-redux';
import Spinner from './Spinner';

Expand All @@ -9,15 +8,12 @@ const selectIsAnyLoading = (state) =>
state.dashboard.loading;

const GlobalLoadingWrapper = ({ children }) => {
const navigation = useNavigation();
const isReduxLoading = useSelector(selectIsAnyLoading);

const isLoading = navigation.state !== 'idle' || isReduxLoading;

return (
<>
{children}
{isLoading && (
{isReduxLoading && (
<div
aria-busy="true"
className="fixed inset-0 z-50 flex items-center justify-center bg-white/60 backdrop-blur-sm"
Expand Down
30 changes: 0 additions & 30 deletions src/components/layout/MainLayout.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Outlet } from 'react-router-dom';
import { Toaster } from 'react-hot-toast';
import Navbar from './Navbar';
import Footer from './Footer';

Expand Down Expand Up @@ -28,35 +27,6 @@ export default function MainLayout() {
}
`}</style>

<Toaster
position="top-right"
toastOptions={{
duration: 3500,
style: {
border: '1px solid #e5e7eb',
background: '#ffffff',
color: '#0F172A',
borderRadius: '8px',
fontFamily: "'Poppins', sans-serif",
fontSize: '.875rem',
boxShadow:
'0 10px 15px -3px rgba(15,23,42,.1), 0 4px 6px -4px rgba(15,23,42,.1)',
},
success: {
iconTheme: { primary: '#10B981', secondary: '#ffffff' },
style: { borderColor: '#10B981' },
},
error: {
iconTheme: { primary: '#EF4444', secondary: '#ffffff' },
style: { borderColor: '#EF4444' },
},
loading: {
iconTheme: { primary: '#6366F1', secondary: '#ffffff' },
style: { borderColor: '#6366F1' },
},
}}
/>

<div className="ml-root">
<Navbar />
<main className="ml-main">
Expand Down
53 changes: 15 additions & 38 deletions src/features/auth/authThunks.ts → src/features/auth/authThunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,126 +2,103 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
import api from '../../services/api';
import { toastSuccess, toastError } from '../../utils/toast';

// payload/type definitions
interface RegisterData {
// specify fields if known, otherwise use any
[key: string]: any;
}

interface LoginCredentials {
email: string;
password: string;
}

interface ResetPasswordPayload {
token: string;
password: string;
}

// generic reject value type used across thunks
interface RejectValue {
message?: string;
}

export const registerUser = createAsyncThunk<any, RegisterData, { rejectValue: RejectValue }>(
export const registerUser = createAsyncThunk(
'auth/register',
async (userData, { rejectWithValue }) => {
try {
// const response = await api.post('/auth/register', userData);
toastSuccess('Registration successful');
// return response.data;
return { status: 'success', message: 'Registration successful' };
} catch (err: any) {
} catch (err) {
toastError(err);
return rejectWithValue(err.response?.data || err.message);
}
}
);

export const loginUser = createAsyncThunk<any, LoginCredentials, { rejectValue: RejectValue }>(
export const loginUser = createAsyncThunk(
'auth/login',
async (credentials, { rejectWithValue }) => {
try {
const response = await api.post('/auth/login', credentials);
toastSuccess('Logged in successfully');
return response.data;
} catch (err: any) {
} catch (err) {
toastError(err);
return rejectWithValue(err.response?.data || err.message);
}
}
);

export const logoutUser = createAsyncThunk<boolean, void, { rejectValue: RejectValue }>(
export const logoutUser = createAsyncThunk(
'auth/logout',
async (_, { rejectWithValue }) => {
try {
await api.post('/auth/logout');
toastSuccess('Logged out successfully');
return true;
} catch (err: any) {
} catch (err) {
toastError(err);
return rejectWithValue(err.response?.data || err.message);
}
}
);

export const verifyEmail = createAsyncThunk<any, string, { rejectValue: RejectValue }>(
export const verifyEmail = createAsyncThunk(
'auth/verifyEmail',
async (token, { rejectWithValue }) => {
try {
const response = await api.post('/auth/verify-email', { token });
toastSuccess('Email verified');
return response.data;
} catch (err: any) {
} catch (err) {
toastError(err);
return rejectWithValue(err.response?.data || err.message);
}
}
);

export const resendEmailVerification = createAsyncThunk<any, string, { rejectValue: RejectValue }>(
export const resendEmailVerification = createAsyncThunk(
'auth/resendEmailVerification',
async (email, { rejectWithValue }) => {
try {
const response = await api.post('/auth/resend-verification', { email });
toastSuccess('Verification email sent');
return response.data;
} catch (err: any) {
} catch (err) {
toastError(err);
return rejectWithValue(err.response?.data || err.message);
}
}
);

export const forgotPassword = createAsyncThunk<any, string, { rejectValue: RejectValue }>(
export const forgotPassword = createAsyncThunk(
'auth/forgotPassword',
async (email) => {
try {
const response = await api.post('/auth/forgot-password', { email });
toastSuccess('If this email is registered, a reset link has been sent');
return response.data;
} catch (err: any) {
} catch (err) {
// Log for debugging but don't show error toast to user
console.error('Forgot password background error:', err);

// For security (avoiding user enumeration), always return success state to the UI
// unless it's a critical application error we want the user to see.
// In this specific task, "regardless of whether the email exists" implies a uniform success UI.

toastSuccess('If this email is registered, a reset link has been sent');
return { status: 'success', message: 'Email processed' };
}
}
);

export const resetPassword = createAsyncThunk<any, ResetPasswordPayload, { rejectValue: RejectValue }>(
export const resetPassword = createAsyncThunk(
'auth/resetPassword',
async ({ token, password }, { rejectWithValue }) => {
try {
const response = await api.post('/auth/reset-password', { token, password });
toastSuccess('Password reset successfully');
return response.data;
} catch (err: any) {
} catch (err) {
toastError(err);
return rejectWithValue(err.response?.data || err.message);
}
Expand Down
Loading