diff --git a/src/App.tsx b/src/App.tsx index 8edab65..75aebb1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,9 @@ -import React from "react"; +import React, {useEffect} from "react"; import {AppProvider} from "./providers/app"; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import storage from "./utils/storage"; diff --git a/src/components/Layout/MainLayout.tsx b/src/components/Layout/MainLayout.tsx index 09726f1..a90830f 100644 --- a/src/components/Layout/MainLayout.tsx +++ b/src/components/Layout/MainLayout.tsx @@ -11,6 +11,8 @@ import {NavLink, Link} from 'react-router-dom'; import logo from '../../assets/Logo.svg'; import {useLogout, useUser} from "../../lib/auth"; import Heading from "../Elements/Headings/Heading"; +import storage from '../../utils/storage'; + type SideNavigationItem = { @@ -36,6 +38,8 @@ const SideNavigation = () => { const handleClick = (index:any) => { setActive(index); }; + + return ( <> @@ -82,6 +86,8 @@ const UserNavigation = () => { const user = useUser(); const userName = user.data?.email; + + return ( @@ -141,6 +147,9 @@ type MobileSidebarProps = { sidebarOpen: boolean; setSidebarOpen: React.Dispatch>; }; + //Get the clock from the storage : + + const clockData : {name : string, clockId : string} = storage.getClock() ? storage.getClock() : {name : "No clock selected", clockId : "SELECTACLOCK"} const MobileSidebar = ({sidebarOpen, setSidebarOpen}: MobileSidebarProps) => { return ( @@ -199,10 +208,16 @@ const MobileSidebar = ({sidebarOpen, setSidebarOpen}: MobileSidebarProps) => {
+
- - + +
+
@@ -226,10 +241,16 @@ const Sidebar = () => {
+
- - + +
+
diff --git a/src/features/alarm/api/alarmApi.ts b/src/features/alarm/api/alarmApi.ts index d4c3dae..900ef51 100644 --- a/src/features/alarm/api/alarmApi.ts +++ b/src/features/alarm/api/alarmApi.ts @@ -44,11 +44,4 @@ export const deleteAlarm = ( alarmId: string): Promise => { throw error.response.data; }); }; -/* -export const getDummyAlarms = (): Promise => { - return new Promise((resolve) => { - setTimeout(() => { - resolve(dummyAlarms); - }, 1000); // Simulate a 1 second delay for the mock API call - }); -};*/ + diff --git a/src/features/alarm/components/AddAlarm.tsx b/src/features/alarm/components/AddAlarm.tsx index 64e5e06..54342eb 100644 --- a/src/features/alarm/components/AddAlarm.tsx +++ b/src/features/alarm/components/AddAlarm.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import {useEffect, useState} from "react"; import Heading from "../../../components/Elements/Headings/Heading"; import InputField from "../../../components/Form/InputField"; import { LocalizationProvider, TimePicker } from "@mui/x-date-pickers"; @@ -9,16 +9,17 @@ import Button from "../../../components/Elements/Button"; import {CreateAlarmProps} from "../types"; import { createAlarm} from "../api/alarmApi"; import SelectForm from "../../../components/Form/selectForm"; -import {SimpleClockProps} from "../../clockSettings/types"; +import {ClockPropsResponse, SimpleClockProps} from "../../clockSettings/types"; +import {getAllClocks} from "../../clockSettings/api/clockApi"; +import storage from "../../../utils/storage"; interface AddAlarmProps { - change: boolean; setChange: React.Dispatch>; } -const AddAlarm: React.FC = ({change, setChange}) => { +const AddAlarm: React.FC = ({ setChange}) => { const [alarmName, setAlarmName] = useState(""); const [nameError, setNameError] = useState(""); const [alarmTime, setAlarmTime] = React.useState(null); @@ -28,11 +29,31 @@ const AddAlarm: React.FC = ({change, setChange}) => { id: 0, name: "Select", }); + const [clocks, setClocks] = useState([]); + useEffect(() => { + const fetchClocks = async () => { + try { + console.log("inside try of fetching clocks"); + const response = await getAllClocks(storage.getUser().userId); // Adjust the endpoint to your API + const clocks: SimpleClockProps[] = response.map(clock => ({ + id: clock.id, + name: clock.name + })); + setClocks(clocks); + console.log("clocks"+clocks); + } catch (error) { + console.error('Error fetching clocks:', error); + } + }; + + fetchClocks(); + }, []); - const clocks: SimpleClockProps[] = [ - { id: "f656d97d-63b7-451a-91ee-0e620e652c9e", name: "Alexa" }, - { id: "f656d97d-63b7-451a-91ee-0e620e652c99", name: "Ricardo clock" } - ]; + const toggleChangeAfterTwoSeconds = () => { + setTimeout(() => { + setChange(prevChange => !prevChange); + }, 500); // 2000 milliseconds = 2 seconds + }; const handleAddAlarm = () => { if (!alarmTime) { @@ -54,11 +75,10 @@ const AddAlarm: React.FC = ({change, setChange}) => { minutes: Number(alarmTime.format("mm")), name: alarmName, } - setChange(!change); console.log(createAlarmData); createAlarm(createAlarmData).then((response) => { console.log(response); - setChange(!change) + toggleChangeAfterTwoSeconds() }).catch( (error) => { console.log(error); diff --git a/src/features/alarm/components/AlarmsList.tsx b/src/features/alarm/components/AlarmsList.tsx index 4bdd42f..6342fb6 100644 --- a/src/features/alarm/components/AlarmsList.tsx +++ b/src/features/alarm/components/AlarmsList.tsx @@ -20,17 +20,6 @@ const AlarmsList: React.FC = (change) => { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); -/* - const toggleEnabled = (name: string, time: string) => { - const updatedAlarms = alarms.map(alarm => { - if (alarm.name === name && alarm.setOffTime === time) { - return { ...alarm, isEnabled: !alarm.isActive }; - } - return alarm; - }); - setAlarms(updatedAlarms); - }; - */ const setAllAlarms = (response: AlarmsPropsResponse) => { setAlarms(response.alarms); @@ -44,11 +33,7 @@ const AlarmsList: React.FC = (change) => { const response = await getAllAlarmsByClockId(clockId); //const response = await getDummyAlarms(); await setAllAlarms(response); - console.log('Response', response); - console.log('alarmsVar', response); - console.log('changestat', change); } catch (error) { - console.error('Failed to fetch alarms:', error); setError('Failed to fetch alarms. Please try again later.'); } finally { setLoading(false); diff --git a/src/features/auth/api/getUser.ts b/src/features/auth/api/getUser.ts index a9b9645..336ce12 100644 --- a/src/features/auth/api/getUser.ts +++ b/src/features/auth/api/getUser.ts @@ -1,19 +1 @@ - -import { AuthUser } from '../types'; - -import hardCodedUser from "./hardcodedUser.json"; -// getting user data from the backend by sending JWT token -//todo: getting user data from the backend by sending JWT token, example https://github.com/alan2207/react-query-auth/blob/master/examples/vite/src/lib/api.ts#L8 - export const getUser = (): Promise<{ user: AuthUser | undefined}> => { - - const hardcodedUser = hardCodedUser as AuthUser; - if(!Object.keys(hardcodedUser).length) { - return Promise.reject(new Error('No user data available')); - } - else { - return Promise.resolve({user: hardcodedUser}); - } - //return axios.get('/auth/me'); - //return Promise.reject(new Error('No user data available')) - //return Promise.resolve({user: hardcodedUserData}); -}; +export {} diff --git a/src/features/auth/api/login.ts b/src/features/auth/api/login.ts index 50f467b..ac9101b 100644 --- a/src/features/auth/api/login.ts +++ b/src/features/auth/api/login.ts @@ -1,26 +1,18 @@ -import { UserResponse } from '../types'; +import {CreateUserPropsRequest, CreateUserPropsResponse, LoginPropsRequest, UserPropsResponse} from '../types'; +import axios from "axios"; +import {axiosConfigAuth, baseURL} from "../../../lib/axios"; + -//what I send -export type LoginCredentialsDTO = { - email: string; - password: string; -}; -export const loginWithEmailAndPassword = ( - data: LoginCredentialsDTO -): Promise => { - //this will be received from backend and stored locally in the client - const hardcodedUserData: UserResponse = { - jwt: 'mock_jwt_token', // Hardcoded JWT token - user: { - id: '1', - email: data.email, - name: 'John Doe' - } - }; - // Return a Promise that immediately resolves with the hardcoded user data - return Promise.resolve(hardcodedUserData); +export const loginWithEmailAndPassword = (data: LoginPropsRequest): Promise => { + return axios.post(`${baseURL}/UserService/users/login`, data, axiosConfigAuth) + .then(response => response.data) + .catch(error => { + console.log("error "+error.response.data) + throw error.response.data; + }); }; + /*export const loginWithEmailAndPassword = (data: LoginCredentialsDTO): Promise => { return axios.post('/auth/login', data); };*/ diff --git a/src/features/auth/api/register.ts b/src/features/auth/api/register.ts index 7d7d868..553034e 100644 --- a/src/features/auth/api/register.ts +++ b/src/features/auth/api/register.ts @@ -1,31 +1,18 @@ //import { axios } from '@/lib/axios'; -import { UserResponse } from '../types'; +import {CreateUserPropsRequest, UserPropsResponse} from '../types'; -export type RegisterCredentialsDTO = { - email: string; - password: string; - name: string; - avatarId?: number; -}; +import axios from "axios"; +import {axiosConfigAuth, baseURL} from "../../../lib/axios"; -const hardcodedUserData: UserResponse = { - jwt: 'mock_jwt_token', // Hardcoded JWT token - user: { - id: '1', - email: 'example@example.com', - name: 'John Doe' - } -}; -export const registerWithEmailAndPassword = ( - data: RegisterCredentialsDTO -): Promise => { - // Return a Promise that immediately resolves with the hardcoded user data - return Promise.resolve(hardcodedUserData); + +export const registerWithEmailAndPassword = (data: CreateUserPropsRequest): Promise => { + + return axios.post(`${baseURL}/UserService/users`, data, axiosConfigAuth) + .then(response => response.data) + .catch(error => { + throw error.response.data; + }); }; -/*export const registerWithEmailAndPassword = ( - data: RegisterCredentialsDTO -): Promise => { - return axios.post('/auth/register', data); -};*/ + diff --git a/src/features/auth/routes/Login.tsx b/src/features/auth/routes/Login.tsx index 842b37d..587139a 100644 --- a/src/features/auth/routes/Login.tsx +++ b/src/features/auth/routes/Login.tsx @@ -7,22 +7,23 @@ import Button from "../../../components/Elements/Button"; import * as z from 'zod'; import React, {useState} from "react"; import {useLogin} from "../../../lib/auth"; -import {hashPassword} from "./index"; +import {LoginPropsRequest} from "../types"; +import SpinnerComponent from "../../spinner/SpinnerComponent"; +import {useNavigate} from "react-router"; const schema = z.object({ email: z.string().min(1, 'Email is required').email('Invalid email format. Please insert valid email.'), password: z.string().min(1, 'Password is required'), }); -type LoginValues = { - email: string; - password: string; -}; + export const Login = () => { const login = useLogin(); - const [values, setValues] = useState({ email: '', password: '' }); + const [values, setValues] = useState({ email: '', password: '' }); const [errors, setErrors] = useState<{ [key: string]: string }>({}); + const navigate = useNavigate(); + const [isSubmitting, setIsSubmitting] = useState(false); const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target; @@ -32,14 +33,20 @@ export const Login = () => { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - + setIsSubmitting(true); try { schema.parse(values); - values.password = await hashPassword(values.password); - // If validation passes, proceed with form submission - //console.log('Form login submitted:', values); - login.mutate(values); + const request:LoginPropsRequest = { + email: values.email, + password: values.password + } + login.mutate(request, { + onSuccess: () => { + navigate('/'); + } + } ); } catch (error) { + setIsSubmitting(false); if (error instanceof z.ZodError) { const fieldErrors: { [key: string]: string } = {}; error.errors.forEach(err => { @@ -48,6 +55,7 @@ export const Login = () => { }); setErrors(fieldErrors); } + console.log("Error: "+error) } }; @@ -71,10 +79,12 @@ export const Login = () => {
-
- -

Not a member?{' '} Register a new account here! diff --git a/src/features/auth/routes/Register.tsx b/src/features/auth/routes/Register.tsx index 7f04bcd..4545068 100644 --- a/src/features/auth/routes/Register.tsx +++ b/src/features/auth/routes/Register.tsx @@ -1,50 +1,52 @@ -import {Form, Link} from 'react-router-dom'; -import {Layout} from '../components/Layout'; +import { Form, Link } from 'react-router-dom'; +import { Layout } from '../components/Layout'; import * as z from "zod"; -import React, {useEffect, useState} from "react"; +import React, { useEffect, useState } from "react"; import Heading from "../../../components/Elements/Headings/Heading"; import InputField from "../../../components/Form/InputField"; import Button from "../../../components/Elements/Button"; -import {EmblaOptionsType} from "embla-carousel"; +import { EmblaOptionsType } from "embla-carousel"; import EmblaCarousel from "../../../components/Elements/Carousel/Carousel"; -import {fetchPokemon, Pokemon} from "../../avatarPic/api"; -import {hashPassword} from "./index"; -import {RegisterCredentialsDTO} from "../api/register"; -import {useRegister} from "../../../lib/auth"; +import { fetchPokemon, Pokemon } from "../../avatarPic/api"; +import { useRegister } from "../../../lib/auth"; import storage from "../../../utils/storage"; -import {useQuery} from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; import SpinnerComponent from "../../spinner/SpinnerComponent"; - - +import { CreateUserPropsRequest } from "../types"; +import {useNavigate} from "react-router"; const schema = z.object({ email: z.string().min(1, 'Email is required').email('Invalid email format. Please insert valid email.'), - password: z.string().min(1, 'Password needs to be at least 5 characters long.'), + password: z.string().min(1, 'Password needs to be at least 1 character long.'), name: z.string().min(1, 'Name is required.') }); + type RegisterValues = { email: string; password: string; name: string; avatarId: string; }; -const OPTIONS: EmblaOptionsType = {align: 'start'} + +const OPTIONS: EmblaOptionsType = { align: 'start' } const SLIDE_COUNT = 6 const SLIDES = Array.from(Array(SLIDE_COUNT).keys()) export const Register = () => { - //for register form - const [values, setValues] = useState({email: '', password: '', name: '', avatarId: '1'}); + // for register form + const [values, setValues] = useState({ email: '', password: '', name: '', avatarId: '1' }); const [errors, setErrors] = useState<{ [key: string]: string }>({}); + const [isSubmitting, setIsSubmitting] = useState(false); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [pokemonError, setPokemonError] = useState(''); + const [emailsList, setEmailsList] = useState([]); - - //for pokemon image slider + // for pokemon image slider const [pokemonList, setPokemonList] = useState([]); const handlePokemonSelect = (pokemonId: string) => { - handleChange({target: {name: 'avatarId', value: pokemonId}} as React.ChangeEvent) + handleChange({ target: { name: 'avatarId', value: pokemonId } } as React.ChangeEvent) }; + const navigate = useNavigate(); const { isLoading, error, data } = useQuery({ queryKey: ['pokemonList'], @@ -55,31 +57,60 @@ export const Register = () => { if (data) { setPokemonList(data); } + const emails: string[] = [ + "asd@gmail.com", + "dsa@gmail.com" + ]; + + setEmailsList(emails); + }, [data]); const handleChange = (e: React.ChangeEvent) => { - const {name, value} = e.target; - setValues({...values, [name]: value}); - setErrors({...errors, [name]: ''}); + const { name, value } = e.target; + setValues({ ...values, [name]: value }); + setErrors({ ...errors, [name]: '' }); }; + const register = useRegister(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + setIsSubmitting(true); try { schema.parse(values); - values.password = await hashPassword(values.password); + + if (emailsList.includes(values.email)) { + setErrors({ ...errors, email: 'Email is already taken' }); + setIsSubmitting(false); + return; + } + const avatarIdNumber = parseInt(values.avatarId); - const credentials: RegisterCredentialsDTO = { + const credentials: CreateUserPropsRequest = { email: values.email, password: values.password, name: values.name, avatarId: avatarIdNumber }; + register.mutate(credentials, { + onSuccess: () => { + navigate('/'); + } + } ); - console.log('Form register submitted:', credentials); - register.mutate(credentials); + setEmailsList([...emailsList, values.email]); + + register.mutate(credentials, { + onSuccess: () => { + setIsSubmitting(false); + }, + onError: () => { + setIsSubmitting(false); + } + }); await console.log(storage.getToken()) } catch (error) { + setIsSubmitting(false); if (error instanceof z.ZodError) { const fieldErrors: { [key: string]: string } = {}; error.errors.forEach(err => { @@ -91,43 +122,44 @@ export const Register = () => { } }; - - return (

- +
+ error={errors.name} />
+ error={errors.email} />
+ error={errors.password} />
- {isLoading && } + {isLoading && } {error &&

{"Error loading pokemons"}

} - {pokemonError &&

{pokemonError}

} {data && } + onSelect={handlePokemonSelect} />}
-

Already a member?{' '} diff --git a/src/features/auth/routes/index.tsx b/src/features/auth/routes/index.tsx index f698973..7b54149 100644 --- a/src/features/auth/routes/index.tsx +++ b/src/features/auth/routes/index.tsx @@ -12,11 +12,4 @@ export const AuthRoutes = () => { ); }; -export const hashPassword = async (password: string): Promise => { - // Define the number of salt rounds (higher number means more security but slower hashing) - const saltRounds = 10; - // Generate a salt - const salt = await bcrypt.genSalt(saltRounds); - // Hash the password with the salt - return await bcrypt.hash(password, salt); -}; + diff --git a/src/features/auth/types/index.ts b/src/features/auth/types/index.ts index 5aa14a8..e25f77a 100644 --- a/src/features/auth/types/index.ts +++ b/src/features/auth/types/index.ts @@ -1,11 +1,25 @@ -export type AuthUser = { - id: string; + +export interface CreateUserPropsRequest{ + email: string, + name: string, + password: string, + avatarId: number; + +} +export interface LoginPropsRequest { email: string; - name: string; + password: string; } +export interface CreateUserPropsResponse{ + email: string, + name: string, + userId: string, + avatarId: number; +} -export type UserResponse = { - jwt: string; - user: AuthUser; +export type UserPropsResponse = { + token: string; + user: CreateUserPropsResponse; }; + diff --git a/src/features/calendar/components/event/EventForm.tsx b/src/features/calendar/components/event/EventForm.tsx index f7b0e57..ff3409f 100644 --- a/src/features/calendar/components/event/EventForm.tsx +++ b/src/features/calendar/components/event/EventForm.tsx @@ -34,8 +34,10 @@ export const EventForm: React.FC = ({ selectedEvent , mode}) => const [dateError, setDateError] = useState(""); const [timeError, setTimeError] = useState(""); const [description, setDescription] = useState(""); - const [deadlineDate, setDeadlineDate] = React.useState(null); - const [deadlineTime, setDeadlineTime] = React.useState(null); + const [startingDate, setStartingDate] = React.useState(null); + const [startingTime, setStartingTime] = React.useState(null); + const [endingDate, setEndingDate] = React.useState(null); + const [endingTime, setEndingTime] = React.useState(null); const [name, setName] = useState(""); const [status, setStatus] = useState<{ id: number; name: string }>({ id: 1, @@ -66,10 +68,16 @@ export const EventForm: React.FC = ({ selectedEvent , mode}) => if (!name.trim() && selectedEvent===null) { setNameError("Please enter a name"); valid = false; - } if (!deadlineDate && selectedEvent===null) { + } if (!startingDate && selectedEvent===null) { setDateError("Select date"); valid = false; - } if (!deadlineTime && selectedEvent===null) { + } if (!startingTime && selectedEvent===null) { + setTimeError("Select time"); + valid = false; + } if (!endingDate && selectedEvent===null) { + setDateError("Select date"); + valid = false; + } if (!endingTime && selectedEvent===null) { setTimeError("Select time"); valid = false; } @@ -82,8 +90,10 @@ export const EventForm: React.FC = ({ selectedEvent , mode}) => const eventSubmitted:EventProps = { name: name || selectedEvent?.name || "", description: description || selectedEvent?.description, // Use selectedEvent data if description is not entered - deadlineDate: deadlineDate || selectedEvent?.deadlineDate || dayjs(), // Use selectedEvent data or current date if deadlineDate is not entered - deadlineTime: deadlineTime || selectedEvent?.deadlineTime || dayjs(), // Use selectedEvent data or current time if deadlineTime is not entered + startingDate: startingDate || selectedEvent?.startingDate || dayjs(), // Use selectedEvent data or current date if startingDate is not entered + startingTime: startingTime || selectedEvent?.startingTime || dayjs(), // Use selectedEvent data or current time if startingTime is not entered + endingDate: endingDate || selectedEvent?.endingDate || dayjs(), // Use selectedEvent data or current date if endingDate is not entered + endingTime: endingTime || selectedEvent?.endingTime || dayjs(), // Use selectedEvent data or current time if endingTime is not entered status: status || selectedEvent?.status, categories: categories || selectedEvent?.categories || [], }; @@ -91,16 +101,20 @@ export const EventForm: React.FC = ({ selectedEvent , mode}) => console.log(eventSubmitted); dummyDataForEvents.push(eventSubmitted); setName(""); - setDeadlineTime(null); - setDeadlineDate(null); + setStartingTime(null); + setStartingDate(null); + setEndingTime(null); + setEndingDate(null); setDescription(""); setStatus({ id: 1, name: "Not started" }) } }; useEffect(() => {setNameError(""); setTimeError(""); setDateError(""); setName(selectedEvent?.name||name) - setDeadlineDate(dayjs(selectedEvent?.deadlineDate)||deadlineDate) - setDeadlineTime(dayjs(selectedEvent?.deadlineTime)||deadlineTime) + setStartingDate(dayjs(selectedEvent?.startingDate)||startingDate) + setStartingTime(dayjs(selectedEvent?.startingTime)||startingTime) + setEndingDate(dayjs(selectedEvent?.endingDate)||endingDate) + setEndingTime(dayjs(selectedEvent?.endingTime)||endingTime) setDescription(selectedEvent?.description||description) setCategories(selectedEvent?.categories||categories); let nameValue = selectedEvent?.name || ""; @@ -166,15 +180,29 @@ export const EventForm: React.FC = ({ selectedEvent , mode}) =>

- setDeadlineDate(newValue)} views={["year", "month", "day"]}/> + setStartingDate(newValue)} + views={["year", "month", "day"]}/> + {dateError && {dateError}} +
+
+ setStartingTime(newValue)}/> + {timeError && {timeError}} +
+
+ setEndingDate(newValue)} + views={["year", "month", "day"]}/> {dateError && {dateError}}
- setDeadlineTime(newValue)} /> + setEndingTime(newValue)}/> {timeError && {timeError}}
@@ -193,8 +221,9 @@ export const EventForm: React.FC = ({ selectedEvent , mode}) => /> ))}
- {mode === "create" && ( +
+ )} + + ); +}; + +export default Contact; diff --git a/src/features/contacts/components/ContactsList.tsx b/src/features/contacts/components/ContactsList.tsx index 67e7683..cf8dbe2 100644 --- a/src/features/contacts/components/ContactsList.tsx +++ b/src/features/contacts/components/ContactsList.tsx @@ -1,6 +1,104 @@ -const ContactsList = () => { - return ( - <> - ) +import Heading from "../../../components/Elements/Headings/Heading"; +import { ContactPropsResponse, ContactsPropsResponse } from "../types"; +import PaginationRounded from "../../../components/Elements/Pagination/pagination"; +import { useEffect, useState } from "react"; +import Contact from "./Contact"; +import { deleteContact, getAllContactsByUserEmail } from "../api/contactApi"; +import SpinnerComponent from "../../spinner/SpinnerComponent"; + +interface ContactsListProps { + change: boolean; } -export default ContactsList \ No newline at end of file + +const ContactsList: React.FC = ({ change }) => { + const [currentPage, setCurrentPage] = useState(1); + const handleChangeOfPage = ( + event: React.ChangeEvent, + value: number + ) => { + setCurrentPage(value); + }; + const contactsPerPage = 5; + + const [contacts, setContacts] = useState([]); + const userEmail = "f656d97d-63b7-451a-91ee-0e620e652c9e"; + + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const setAllContacts = (response: ContactsPropsResponse) => { + setContacts(response.contacts); + console.log("Contacts", contacts); + }; + + useEffect(() => { + const fetchContacts = async () => { + setLoading(true); + setError(null); + try { + const response = await getAllContactsByUserEmail(userEmail); + await setAllContacts(response); + console.log("Response", response); + } catch (error) { + console.error("Failed to fetch contacts:", error); + setError("Failed to fetch contacts. Please try again later."); + } finally { + setLoading(false); + } + }; + + fetchContacts().then((r) => console.log("Contacts fetched")); + }, [change]); + + const handleDeleteContact = (contactToDelete: ContactPropsResponse) => { + deleteContact(contactToDelete.id) + .then(async () => { + const response = getAllContactsByUserEmail(userEmail); + await setAllContacts(await response); + console.log("Response", response); + }) + .catch((error) => { + console.error("Failed to delete alarm:", error); + }); + }; + + return ( + <> + + {loading ? ( + + ) : error ? ( + + ) : ( + <> + {contacts.length === 0 ? ( + + ) : ( + <> + {contacts.map((contact, index) => ( + handleDeleteContact(contact)} + /> + ))} + + {/* Conditionally render pagination only when there are 5 or more contacts*/} + {contacts.length > contactsPerPage && ( // Display pagination only if there are more than 5 alarms + + )} + + )} + + )} + + ); +}; +export default ContactsList; diff --git a/src/features/contacts/types/index.ts b/src/features/contacts/types/index.ts index f0f85a9..9f9113e 100644 --- a/src/features/contacts/types/index.ts +++ b/src/features/contacts/types/index.ts @@ -1,4 +1,37 @@ export interface ContactProps { email: string; - imageSrc: string + imageSrc: string; + onDelete?: () => void; + index?: number; +} + + +export interface ContactPropsResponse { + id: string; + email: string; + imageSrc: string; +} + + +export interface ContactsPropsResponse { + contacts: ContactPropsResponse[] +} + + +export const dummyContacts: ContactPropsResponse[] = [ + { + id: "1", + email: "john@example.com", + imageSrc: "https://example.com/john.jpg", + }, + { + id: "2", + email: "jane@example.com", + imageSrc: "https://example.com/jane.jpg", + } +] + + +export function getDummyContacts(): ContactsPropsResponse { + return { contacts: dummyContacts }; } \ No newline at end of file diff --git a/src/features/messages/api/createMessage.ts b/src/features/messages/api/createMessage.ts deleted file mode 100644 index 052d411..0000000 --- a/src/features/messages/api/createMessage.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {SendMessageProps} from "../types"; -import axios, {HttpStatusCode} from "axios"; - -export const sendMessage = ( - data: SendMessageProps -): Promise => { - return axios.post('http://192.168.43.151:8080/Message', data); -}; diff --git a/src/features/messages/api/messageApi.ts b/src/features/messages/api/messageApi.ts new file mode 100644 index 0000000..6a9e974 --- /dev/null +++ b/src/features/messages/api/messageApi.ts @@ -0,0 +1,31 @@ +import {MessageResponseProps, MessagesResponseProps, SendMessageProps} from "../types"; +import axios from "axios"; +import {axiosConfig, baseURL} from "../../../lib/axios"; + + +export const sendMessage = (data: MessageResponseProps): Promise => { + return axios.post(`${baseURL}/UserService/messages`, data, axiosConfig) + .then(response => response.data) + .catch(error => { + throw error.response.data; + }); +}; + + +export const getAllSentMessages = (userId: string): Promise => { + return axios.get(`${baseURL}/UserService/users/${userId}/messages?activity=sent`, axiosConfig) + .then(response => response.data) + .catch(error => { + throw error.response.data; + }); +}; + + +export const getAllReceivedMessages = (userId: string): Promise => { + return axios.get(`${baseURL}/UserService/users/${userId}/messages?activity=received`, axiosConfig) + .then(response => response.data) + .catch(error => { + throw error.response.data; + }); +}; + diff --git a/src/features/messages/components/MessagesList.tsx b/src/features/messages/components/MessagesList.tsx index 4e5da8c..1fbed0f 100644 --- a/src/features/messages/components/MessagesList.tsx +++ b/src/features/messages/components/MessagesList.tsx @@ -1,17 +1,24 @@ import React, {useEffect, useState} from "react"; import { ContentInnerContainer } from "../../../components/Layout/ContentInnerContainer"; import ToggleMessages from "./ToggleMessages"; -import {dummyDataReceivedMessages, dummyDataSentMessages, ShowMessageProps} from "../types"; +import {MessageResponseProps, ShowMessageProps} from "../types"; import PaginationRounded from "../../../components/Elements/Pagination/pagination"; import Heading from "../../../components/Elements/Headings/Heading"; -import { getPokemonPicById} from "../../avatarPic/api"; +import {getAllReceivedMessages, getAllSentMessages} from "../api/messageApi"; +import storage from "../../../utils/storage"; +import SpinnerComponent from "../../spinner/SpinnerComponent"; +interface MessagesListProps { + change: boolean; +} -const MessagesList = () => { +const MessagesList = ({change}:MessagesListProps) => { const [activeTab, setActiveTab] = useState<'sent' | 'received'>('sent'); const [currentPage, setCurrentPage] = useState(1); const messagesPerPage = 5; + const [receivedMessages, setReceivedMessages] = useState([]); + const [sentMessages, setSentMessages] = useState([]); const handleTabChange = (tab: 'sent' | 'received') => { setActiveTab(tab); @@ -24,56 +31,81 @@ const MessagesList = () => { setCurrentPage(value); }; - const receivedMessages = dummyDataReceivedMessages; - const reversedSentMessages = dummyDataSentMessages.slice().reverse(); - const messagesToDisplay = activeTab === 'received' ? receivedMessages : reversedSentMessages; + const [loading, setLoading] = useState(true); + useEffect(() => { + const fetchMessages = async () => { + try { + const responseReceivedMessages = await getAllReceivedMessages(storage.getUser().userId); + const responseSentMessages = await getAllSentMessages(storage.getUser().userId); + + setReceivedMessages(responseReceivedMessages.messages); + setSentMessages(responseSentMessages.messages); + + } catch (error) { + console.error('Failed to get messages. Please try again later.'); + } + }; + + fetchMessages().then(() => setLoading(false)); + }, [change]); + + const messagesToDisplay = activeTab === 'received' ? receivedMessages : sentMessages; return (
- {messagesToDisplay.length > 0 ? ( - <> - {messagesToDisplay - .slice((currentPage - 1) * messagesPerPage, currentPage * messagesPerPage) - .map((message:ShowMessageProps, index) => ( - + {loading ? ( + + ) : ( + <>{messagesToDisplay.length > 0 ? ( + <> + {messagesToDisplay + .slice((currentPage - 1) * messagesPerPage, currentPage * messagesPerPage) + .map((message:MessageResponseProps, index) => ( + + ))} + {messagesToDisplay.length > messagesPerPage && ( + + )} + + ) : ( + - ))} - {messagesToDisplay.length > messagesPerPage && ( - - )} - - ) : ( - - )} + )} + + )} + +
); } export default MessagesList; +//TODO add avatar pic when backend is ready const Message: React.FC = ({ email, text, - avatarId, + //avatarId, type }) => { - const [avatar, setAvatar] =useState(""); + /*const [avatar, setAvatar] =useState(""); useEffect(() => { getPokemonPicById(avatarId) // Fetch picture for Pikachu (ID 25) .then(pictureUrl => { @@ -84,12 +116,12 @@ const Message: React.FC = ({ }) .catch(error => { }); - }, [avatarId]); + }, [avatarId]);*/ return (
Avatar diff --git a/src/features/messages/components/SendMessage.tsx b/src/features/messages/components/SendMessage.tsx index 1aaa8bf..cb779e9 100644 --- a/src/features/messages/components/SendMessage.tsx +++ b/src/features/messages/components/SendMessage.tsx @@ -1,23 +1,43 @@ import Heading from "../../../components/Elements/Headings/Heading"; import TextArea from "../../../components/Form/TextArea"; import SelectForm from "../../../components/Form/selectForm"; -import { useState } from "react"; +import React, { useState } from "react"; import { MessageProps, SendMessageProps } from "../types"; import PopUp from "../../../components/Elements/PopUp/PopUp"; import Button from "../../../components/Elements/Button"; import { ContentInnerContainer } from "../../../components/Layout/ContentInnerContainer"; -import { sendMessage } from "../api/createMessage"; - -const SendMessage = ({ - receiverOptions, - clockOptions, - updateSentMessages, -}: any) => { +import { sendMessage } from "../api/messageApi"; +import storage from "../../../utils/storage"; +import SpinnerComponent from "../../spinner/SpinnerComponent"; + +interface MessageParams { + setChange: React.Dispatch>; +} +const SendMessage = ({setChange}: MessageParams) => { const [message, setMessage] = useState({ text: "", receiver: { id: 0, name: "Select" }, clock: { id: 0, name: "Select" }, }); + const [isSubmitting, setIsSubmitting] = useState(false); +//TODO replace with api call later + const receiverOptions = [ + { id: 1, name: "Receiver 1" }, + { id: 2, name: "Receiver 2" }, + { id: 3, name: "Receiver 3" }, + ]; +//TODO replace with api call later + const clockOptions = [ + { id: 1, name: "Clock 1" }, + { id: 2, name: "Clock 2" }, + { id: 3, name: "Clock 3" }, + ]; + + const updateMessages = () => { + setTimeout(() => { + setChange(prevChange => !prevChange); + }, 500); // 2000 milliseconds = 2 seconds + }; const [messageError, setMessageError] = useState(""); const [receiverError, setReceiverError] = useState(""); @@ -76,40 +96,31 @@ const SendMessage = ({ setSuccessMessage(""); if (validateFields()) { + + setIsSubmitting(true); + const messageToSend: SendMessageProps = { message: message.text, - receiverId: "5f3bb5af-e982-4a8b-8590-b620597a7360", - clockId: "f656d97d-63b7-451a-91ee-0e620e652c9e", - userId: "5f3bb5af-e982-4a8b-8590-b620597a7360", + receiverId: "f8a383e2-38ee-4755-ac1f-c6aa881a5798", + clockId: "bce5c68c-d26b-4fa5-826b-2d74912a7b80", + userId: storage.getUser().id? storage.getUser().id : "f8a383e2-38ee-4755-ac1f-c6aa881a5798", }; sendMessage(messageToSend) - .then((response) => { - console.log("Message sent successfully:", response); + .then(() => { + updateMessages(); setShowPopup(true); setMessage({ text: "", receiver: { id: 0, name: "Select" }, clock: { id: 0, name: "Select" }, }); - - updateSentMessages((prevSentMessages: any) => [ - ...prevSentMessages, - { - userEmail: message.receiver.name, - text: message.text, - }, - ]); - }) - .catch((error) => { + .catch((error:any) => { console.error("Error sending message:", error); // Handle error, such as displaying an error message to the user }); } - - console.log("Message:", message); - console.log("Receiver:", message.receiver); - console.log("Clock:", message.clock); + setIsSubmitting(false); }; const handlePopupClose = () => { @@ -165,11 +176,18 @@ const SendMessage = ({ } error={clockError} /> -
+ {successMessage &&

{successMessage}

} diff --git a/src/features/messages/types/index.ts b/src/features/messages/types/index.ts index f009bed..b249158 100644 --- a/src/features/messages/types/index.ts +++ b/src/features/messages/types/index.ts @@ -11,70 +11,19 @@ export interface SendMessageProps { } export interface ShowMessageProps { - avatarId: number; + //avatarId: number; email: string; text: string; type?:string } - - -export let dummyDataSentMessages: ShowMessageProps[] = [ - { - avatarId: 1, - email: "alice@example.com", - text: "Hey, how are you?", - }, - { - avatarId: 2, - email: "bob@example.com", - text: "Just checking in. Let me know if you need anything.", - }, - { - avatarId: 3, - email: "charlie@example.com", - text: "Reminder: Our meeting is at 3 PM today.", - }, - { - avatarId: 13, - email: "charlie@example.com", - text: "Reminder: Our meeting is at 3 PM today.", - }, - { - avatarId: 23, - email: "charlie@example.com", - text: "Reminder: Our meeting is at 3 PM today.", - }, - { - avatarId: 3, - email: "charlie@example.com", - text: "Reminder: Our meeting is at 3 PM today.", - }, -] - - export let dummyDataReceivedMessages: ShowMessageProps[] = [ - { - avatarId: 1, - email:"alice@.gmail.com", - text:"Hey, I hope you have a great day!", - - }, - { - avatarId: 4, - email:"max@.gmail.com", - text:"Don't forget to get groceries!", - - }, - { - avatarId: 3, - email: "bob@example.com", - text: "Just checking in. Let me know if you need anything.", - }, - { - avatarId: 2, - email: "david@example.com", - text: "Good morning! I sent you a message.", - }, - - -] +export interface MessageResponseProps { + message: string; + receiverId: string; + clockId: string; + userId: string; +} +export interface MessagesResponseProps { + userID: string; + messages: MessageResponseProps[]; +} diff --git a/src/index.css b/src/index.css index 1dc3e22..7da124d 100644 --- a/src/index.css +++ b/src/index.css @@ -8,6 +8,7 @@ --color-green: 19 194 150; /* ... */ } + } diff --git a/src/lib/auth.tsx b/src/lib/auth.tsx index bd32271..c1be397 100644 --- a/src/lib/auth.tsx +++ b/src/lib/auth.tsx @@ -1,49 +1,52 @@ import {configureAuth} from 'react-query-auth'; -import {UserResponse} from "../features/auth/types"; + import storage from "../utils/storage"; -import {getUser} from "../features/auth/api/getUser"; + import {loginWithEmailAndPassword} from "../features/auth/api/login"; import {registerWithEmailAndPassword} from "../features/auth/api/register"; +import { + CreateUserPropsRequest, + LoginPropsRequest, + UserPropsResponse +} from "../features/auth/types"; -export type LoginCredentials = { - email: string; - password: string; -}; -export type RegisterCredentials = { - email: string; - name: string; - password: string; - avatarId?: number; -}; -async function handleUserResponse(data: UserResponse) { - const { jwt, user } = data; - storage.setToken(jwt); +async function handleUserResponse(data: UserPropsResponse) { + const { token, user } = data; + storage.setToken(token); + storage.setUser(user); return user; } async function userFn() { if (storage.getToken()) { - const {user} = await getUser(); + const user = storage.getUser(); + console.log("Token Exists " + user + storage.getUser()); return user; } + else if(storage.getUser()){ + return storage.getUser(); + } + console.log("Token Does Not Exist"); return null; } -async function loginFn(data: LoginCredentials) { +async function loginFn(data: LoginPropsRequest) { + console.log("before api " + data); const response = await loginWithEmailAndPassword(data); return await handleUserResponse(response); } -async function registerFn(data: RegisterCredentials) { +async function registerFn(data: CreateUserPropsRequest) { const response = await registerWithEmailAndPassword(data); return await handleUserResponse(response); } async function logoutFn() { storage.clearToken(); + storage.clearUser(); window.location.assign(window.location.origin as unknown as string); } diff --git a/src/lib/axios.ts b/src/lib/axios.ts index 498304d..e1ee1fe 100644 --- a/src/lib/axios.ts +++ b/src/lib/axios.ts @@ -1,10 +1,19 @@ import {AxiosRequestConfig} from "axios"; +import storage from "../utils/storage"; export const baseURL = "https://sep4coupledclock.azure-api.net"; const functionKey = '95029b2c630d4b50bccbc3a777e952c6'; +//const token = storage.getToken(); + export const axiosConfig: AxiosRequestConfig = { + headers: { + 'Ocp-Apim-Subscription-Key': functionKey, + //Authorization: `Bearer ${token}` + } +}; +export const axiosConfigAuth: AxiosRequestConfig = { headers: { 'Ocp-Apim-Subscription-Key': functionKey, } diff --git a/src/pages/Alarm.tsx b/src/pages/Alarm.tsx index 8bc1c0f..25693f0 100644 --- a/src/pages/Alarm.tsx +++ b/src/pages/Alarm.tsx @@ -17,7 +17,7 @@ export const Alarm = () => { - + diff --git a/src/pages/Calendar.css b/src/pages/Calendar.css index d875254..8042e9d 100644 --- a/src/pages/Calendar.css +++ b/src/pages/Calendar.css @@ -52,3 +52,9 @@ height: 426px !important; } } + +.fc-event { + border: white !important; + border-radius: 10px !important; + outline: none !important; +} \ No newline at end of file diff --git a/src/pages/Calendar.tsx b/src/pages/Calendar.tsx index 41b7546..3006136 100644 --- a/src/pages/Calendar.tsx +++ b/src/pages/Calendar.tsx @@ -1,21 +1,67 @@ import React from "react"; -import {ContentInnerContainer} from "../components/Layout/ContentInnerContainer"; -import {ContentLayout} from "../components/Layout/ContentLayout"; +import { ContentInnerContainer } from "../components/Layout/ContentInnerContainer"; +import { ContentLayout } from "../components/Layout/ContentLayout"; import FullCalendar from "@fullcalendar/react"; import timeGridPlugin from "@fullcalendar/timegrid"; import interactionPlugin from "@fullcalendar/interaction"; import dayGridPlugin from "@fullcalendar/daygrid"; import "./Calendar.css"; - +import {dummyDataForEvents, dummyDataForTasks, EventProps, TaskProps} from "../features/calendar/types"; +import { getTextColor } from "../features/calendar/types/categoryColorLogic"; export const Calendar = () => { + const transformDataToTasks = (data: TaskProps[], type: string) => { + return data.map((item => ({ + title: `${type}: ${item.name}`, + start: item.deadlineDate, + extendedProps: { + status: item.status.name, + categories: item.categories, + type: type, + } + }))); + }; + + const transformDataToEvents = (data: EventProps[], type: string) => { + return data.map((item => ({ + title: `${type}: ${item.name}`, + start: item.startingTime, + end: item.endingTime, + extendedProps: { + status: item.status.name, + categories: item.categories, + type: type, + } + }))); + }; + + const calendarTasks = transformDataToTasks(dummyDataForTasks, "Task"); + const calendarEvents = transformDataToEvents(dummyDataForEvents, "Event"); + const combinedEvents = [...calendarTasks, ...calendarEvents]; + const renderEventContent = (eventInfo: { event: any; }) => { + const { event } = eventInfo; + const firstCategoryColor = event.extendedProps.categories && event.extendedProps.categories[0] ? event.extendedProps.categories[0].color : "#FFFFFF"; + return ( +
+ {event.title} + {event.extendedProps.description &&

{event.extendedProps.description}

} +
+ ); + }; return ( - - -
+ + +
{ center: "title", end: "dayGridMonth,timeGridWeek,timeGridDay", }} + events={combinedEvents} // Pass the combined array to the FullCalendar component + eventContent={renderEventContent} // Custom rendering function />
-
); }; + +// Other types and dummy data as in the original code... diff --git a/src/pages/Contacts.tsx b/src/pages/Contacts.tsx index fba0532..222938c 100644 --- a/src/pages/Contacts.tsx +++ b/src/pages/Contacts.tsx @@ -1,186 +1,72 @@ -import Heading from "../components/Elements/Headings/Heading"; import { ContentInnerContainer } from "../components/Layout/ContentInnerContainer"; import { ContentLayout } from "../components/Layout/ContentLayout"; -import PaginationRounded from "../components/Elements/Pagination/pagination"; -import InputField from "../components/Form/InputField"; -import Button from "../components/Elements/Button"; import { useState } from "react"; -import * as z from "zod"; -import { XMarkIcon } from "@heroicons/react/24/outline"; import PopUp from "../components/Elements/PopUp/PopUp"; import { ContactProps } from "../features/contacts/types"; -import * as React from "react"; +import ContactsList from "../features/contacts/components/ContactsList"; +import AddContact from "../features/contacts/components/AddContact"; -const schema = z.object({ - email: z - .string() - .min(1, "Email is required") - .email("Invalid email format. Please insert valid email."), -}); export const Contacts = () => { + const [change, setChange] = useState(false); + const [message, setMessage] = useState(""); const [errors, setErrors] = useState<{ [key: string]: string }>({}); const [email, setEmail] = useState(""); - - const [currentPage, setCurrentPage] = useState(1); - const handleChangeOfPage = (event: React.ChangeEvent, value: number) => { - setCurrentPage(value); - }; - const [contacts, setContacts] = useState< - Array - >([]); - const [selectedContact, setSelectedContact] = useState({ - email: "", - imageSrc: "", - }) + const [contacts, setContacts] = useState>([]); + const [selectedContact, setSelectedContact] = useState( + null + ); const [isHovered, setIsHovered] = useState(false); const [showPopUp, setShowPopUp] = useState(false); const [popupType, setPopupType] = useState<"success" | "delete" | null>(null); - const handleAddContact = () => { - setMessage(""); - setErrors({}); - - try { - schema.parse({ email }); - console.log({ email }); - - //logic to add the contacts to the list - setContacts([ - ...contacts, - { - email, - imageSrc: - "https://yt3.googleusercontent.com/wzEypbVsmY9BI-IbLwVius4UvC2rejtJB_PTXAdPpYXQ07EIjl5Ms55NCFq_dILwONpxrzE2xA=s900-c-k-c0x00ffffff-no-rj", - }, - ]); - setEmail(""); - handleSuccessPopup(); - } catch (error) { - if (error instanceof z.ZodError) { - const fieldErrors: { [key: string]: string } = {}; - error.errors.forEach((err) => { - const path = err.path.join("."); - fieldErrors[path] = err.message; - }); - setErrors(fieldErrors); - } - } - }; - - const handleChange = (e: React.ChangeEvent) => { - setEmail(e.target.value); - setErrors({ ...errors, email: "" }); - setMessage(""); - }; - - const handleDeleteContact = (index: number) => { - // Set the selected contact - setSelectedContact(contacts[index]); - // Show the delete popup - handleDeletePopup(); - - }; - // Function to handle the deletion process - const handleConfirmDelete = () => { - - const updatedContacts = [...contacts]; - updatedContacts.splice( - updatedContacts.findIndex((contact) => contact.email === selectedContact.email), - 1 - ); - setContacts(updatedContacts); - setShowPopUp(false); - - }; + // const handleDeleteContact = (index: number) => { + // setSelectedContact(contacts[index]); + // handleDeletePopup(); + // }; + // // Function to handle the deletion process + // const handleConfirmDelete = () => { + // const updatedContacts = [...contacts]; + // updatedContacts.splice( + // updatedContacts.findIndex( + // (contact) => contact.email === selectedContact.email + // ), + // 1 + // ); + // setContacts(updatedContacts); + // setShowPopUp(false); + // }; const handleSuccessPopup = () => { setPopupType("success"); setShowPopUp(true); }; - + const handleDeletePopup = () => { setPopupType("delete"); setShowPopUp(true); }; - return ( <> - - {contacts.length === 0 ? ( - - ) : ( - <> - {contacts.map((contact, index) => ( -
setIsHovered(index)} - onMouseLeave={() => setIsHovered(false)} - > -
- {contact.email} -
-
- -
- - {/* ensures that the XMark icon appears only on the contact you're hovering over. */} - {isHovered === index && ( -
- -
- )} -
- ))} - - {/* Conditionally render pagination only when there are 5 or more contacts*/} - {contacts.length >= 8 && ( - - )} - - - )} +
- - - - -