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
4 changes: 3 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export default [
React: 'readonly',
exports: 'readonly',
HTMLInputElement: "readonly",
HTMLFormElement: "readonly"
HTMLFormElement: "readonly",
setInterval: "readonly",
clearInterval: "readonly",
},
},
plugins: {
Expand Down
3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"test": "vitest"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@fullcalendar/core": "^6.1.17",
"@fullcalendar/daygrid": "^6.1.17",
"@fullcalendar/interaction": "^6.1.17",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/app/dashboard/calendar/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import axios from 'axios';
import { backendBaseUrl } from '@/lib/utils';

interface Event {
start_time?: Date | string;
id: number | string; // your backend sometimes uses uuid string, sometimes number
title: string;
end_time: Date | string | null;
Expand Down Expand Up @@ -102,7 +103,7 @@ export default function Home() {
const extractedEvents = response.data.map((event: Event) => ({
id: event.id,
title: event.title,
start: event.start ? new Date(event.start) : undefined,
start: event.start_time ? new Date(event.start_time) : undefined,
end: event.end_time ? new Date(event.end_time) : undefined,
allDay: event.allDay ?? false, // default to false if undefined
// Optional: You could add more fields here if FullCalendar needs
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/Help.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const Help = () => {
return (
<div className="relative">
<Button
className="cursor-pointer"
variant="ghost"
size="icon"
aria-label="Help"
Expand Down
43 changes: 4 additions & 39 deletions frontend/src/components/LandingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
'use client';
import React, { useEffect } from 'react';
// import { useRouter } from 'next/navigation';
import React from 'react';
import { motion } from 'framer-motion';
import { Calendar } from 'lucide-react';
import { Button } from '@/components/ui/button';
// import { Input } from '@/components/ui/input';
import SignInSignUp from '@/components/ui/auth/SignInSignUp';
import { useAuth } from './context/auth/AuthContext';

const Home: React.FC = () => {
// const router = useRouter();
const { user } = useAuth();
useEffect(() => {
if (!user) {
console.log('User not signed in.');
}
}, [user]);
const { verified } = useAuth();

const scrollToSignUp = () =>
document
.getElementById('signin-signup')
Expand Down Expand Up @@ -83,10 +76,9 @@ const Home: React.FC = () => {
</div>
</section>

{/* Sign-in */}
{/* Sign-in or Dashboard Redirect */}
<section id="signin-signup" className="max-w-md mx-auto px-6 py-20">
{user ? (
{verified ? (
<div className="text-center space-y-4">
<p className="text-xl font-medium">
You&apos;re already signed in.
Expand All @@ -102,33 +94,6 @@ const Home: React.FC = () => {
<SignInSignUp />
)}
</section>

{/* <section id="signin-signup" className="max-w-md mx-auto px-6 py-20">
<SignInSignUp />
</section> */}
{/*<section id="signin" className="max-w-md mx-auto px-6 py-20">*/}
{/* <div className="bg-white/60 dark:bg-gray-900/50 backdrop-blur-lg p-8 rounded-3xl shadow-xl">*/}
{/* <h3 className="text-2xl font-semibold mb-6 text-center">Sign In</h3>*/}
{/* <div className="space-y-4">*/}
{/* <Input*/}
{/* type="text"*/}
{/* placeholder="Username"*/}
{/* className="w-full p-3 rounded-full border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800"*/}
{/* />*/}
{/* <Input*/}
{/* type="password"*/}
{/* placeholder="Password"*/}
{/* className="w-full p-3 rounded-full border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800"*/}
{/* />*/}
{/* <Button*/}
{/* onClick={() => router.push('/dashboard')}*/}
{/* className="w-full rounded-full py-3 font-semibold bg-gradient-to-r from-purple-500 to-indigo-500 text-white hover:from-purple-400 hover:to-indigo-400 transition"*/}
{/* >*/}
{/* Sign In*/}
{/* </Button>*/}
{/* </div>*/}
{/* </div>*/}
{/*</section>*/}
</div>
);
};
Expand Down
23 changes: 20 additions & 3 deletions frontend/src/components/UserPreferencesForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
Expand All @@ -21,6 +21,7 @@ import { Card, CardHeader, CardContent } from './ui/card';
import axios from 'axios';
import { useAuth } from '@/components/context/auth/AuthContext';
import { backendBaseUrl, minutesToTime } from '@/lib/utils';
import { Loader2 } from 'lucide-react';
//Define form shema
export const formSchema = z
.object({
Expand Down Expand Up @@ -75,6 +76,7 @@ export function UserPreferencesForm({
defaultValues,
}: UserPreferencesFormProps) {
const router = useRouter();
const [loading, setLoading] = useState<boolean>(false);
const { user } = useAuth();
// 1. Define your form.
const form = useForm<z.infer<typeof formSchema>>({
Expand All @@ -97,6 +99,7 @@ export function UserPreferencesForm({

// 2. Define a submit handler.
function onSubmit(values: z.infer<typeof formSchema>) {
setLoading(true);
// Do something with the form values.
// This will be type-safe and validated.
console.log(values);
Expand All @@ -118,6 +121,7 @@ export function UserPreferencesForm({
axios
.post(backendBaseUrl + `/api/user/surveyresults/${user.uid}`, outputs)
.then((response) => {
setLoading(false);
console.log('Successfully posted answers for user: ', user!.uid);
console.log(response);
router.push('/dashboard');
Expand Down Expand Up @@ -242,8 +246,21 @@ export function UserPreferencesForm({
)}
/>

<Button type="submit" className="w-full">
Save Preferences
<Button
type="submit"
className="w-full flex items-center justify-center"
disabled={loading}
>
{loading ? (
<>
<Loader2 className="h-4 w-4 animate-spin text-white/50" />
<span className="text-white/90">
Updating your preferences...
</span>
</>
) : (
'Save Preferences'
)}
</Button>
</form>
</Form>
Expand Down
32 changes: 22 additions & 10 deletions frontend/src/components/UserProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,31 +68,47 @@ export default function ProfilePage() {

// Handle the save preference logic
function handleSavePreferences(values: z.infer<typeof formSchema>): void {
console.log('Updated preferences:', values);
console.log(
'handleSavePreferences',
values as ReturnType<typeof getPreferenceExtractData>
);
console.log('current: ', userPreferences);
console.log(values === userPreferences);
console.log(JSON.stringify(values) === JSON.stringify(userPreferences));

if (JSON.stringify(values) === JSON.stringify(userPreferences)) {
window.alert("You didn't make any changes!");
setOpen(false);
return;
}

const outputs = [];
for (const question in values) {
outputs.push({
question_text: question,
answer: String(values[question as keyof z.infer<typeof formSchema>]),
});
}
console.log(outputs);

// Send the data to our backend
if (!user?.uid) return;

axios
.put(backendBaseUrl + `/api/user/surveyresults/${user.uid}`, outputs)
.then((response) => {
// Update state of user preference
setUserPreferences(values);

console.log('Successfully updated answers for user: ', user!.uid);
console.log(response);
})
.catch((error) => {
console.log(error);
})
.finally(() => {
// Close the dialog after save
setOpen(false);
});
// Update state of user preference
setUserPreferences(values);
// Close the dialog after save
setOpen(false);
}

if (loading || !userPreferences) {
Expand Down Expand Up @@ -153,10 +169,6 @@ export default function ProfilePage() {
)}
</div>

{/*<h1 className="text-3xl font-bold text-gray-900 dark:text-white">*/}
{/* {displayName}*/}
{/*</h1>*/}

{/* User Preferences */}
<div className="w-full space-y-4 text-gray-700 dark:text-gray-300 text-lg">
<div>
Expand Down
68 changes: 54 additions & 14 deletions frontend/src/components/context/auth/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,28 @@ import {
User,
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
updateProfile,
sendEmailVerification,
sendPasswordResetEmail,
} from 'firebase/auth';

type AuthContextType = {
user: User | null;
displayName: string | null;
verified: boolean;
loading: boolean;
signIn: (email: string, password: string) => Promise<void>;
signOut: () => Promise<void>;
signUp: (email: string, password: string) => Promise<void>;
signUp: (
email: string,
password: string,
displayName: string
) => Promise<void>;
forgotPassword: (email: string) => Promise<void>;
};

const AuthContext = createContext<AuthContextType | undefined>(undefined);

// Custom hook to use authentication context
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
Expand All @@ -28,42 +37,73 @@ export const useAuth = () => {
return context;
};

// Define the type for children prop
interface AuthProviderProps {
children: React.ReactNode; // This ensures the AuthProvider can accept any valid React child
children: React.ReactNode;
}

export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [verified, setVerified] = useState<boolean>(false);

// Listen for authentication state changes
useEffect(() => {
const unsubscribe = onAuthStateChanged(firebaseAuth, (user) => {
setUser(user);
const unsubscribe = onAuthStateChanged(firebaseAuth, (currentUser) => {
setUser(currentUser);
setVerified(currentUser?.emailVerified ?? false);
setLoading(false);
});

return () => unsubscribe();
}, []);

// SignIn function
const signIn = async (email: string, password: string) => {
await signInWithEmailAndPassword(firebaseAuth, email, password);
setLoading(true);
await signInWithEmailAndPassword(firebaseAuth, email, password).finally(
() => setLoading(false)
);
};

// SignUp function
const signUp = async (email: string, password: string) => {
await createUserWithEmailAndPassword(firebaseAuth, email, password);
const signUp = async (
email: string,
password: string,
displayName: string
) => {
setLoading(true);
try {
const { user } = await createUserWithEmailAndPassword(
firebaseAuth,
email,
password
);

await updateProfile(user, { displayName });
await sendEmailVerification(user);
} finally {
setLoading(false);
}
};

// SignOut function
const signOut = async () => {
await firebaseAuth.signOut();
};

const forgotPassword = async (email: string) => {
await sendPasswordResetEmail(firebaseAuth, email);
};

return (
<AuthContext.Provider value={{ user, loading, signIn, signOut, signUp }}>
<AuthContext.Provider
value={{
user,
displayName: user?.displayName ?? null,
verified,
loading,
signIn,
signOut,
signUp,
forgotPassword,
}}
>
{children}
</AuthContext.Provider>
);
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/ui/ProtectedRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import { useEffect } from 'react';
import { useAuth } from '@/components/context/auth/AuthContext';

const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
const { user, loading } = useAuth();
const { verified, loading } = useAuth();
const router = useRouter();
const pathname = usePathname();

useEffect(() => {
// Only redirect if not loading and no user
if (!loading && !user && pathname !== '/') {
if (!loading && !verified && pathname !== '/') {
router.push('/');
}
}, [user, loading, pathname, router]);
}, [verified, loading, pathname, router]);

if (loading || (!user && pathname !== '/')) {
if (loading || (!verified && pathname !== '/')) {
return <div className="text-center p-8">Loading...</div>;
}

Expand Down
Loading