diff --git a/django_project/geohosting/src/components/DashboardSidePanel/DashboardSidePanel.tsx b/django_project/geohosting/src/components/DashboardSidePanel/DashboardSidePanel.tsx index 52d35ca..f3045ad 100644 --- a/django_project/geohosting/src/components/DashboardSidePanel/DashboardSidePanel.tsx +++ b/django_project/geohosting/src/components/DashboardSidePanel/DashboardSidePanel.tsx @@ -75,6 +75,11 @@ const DashboardSidePanel = ({ selected, onClose, ...rest }) => { isSelected={selected === 'support'} onClick={() => navigate('/dashboard/support')} /> + navigate('/dashboard/profile')} + /> { + const [passwords, setPasswords] = useState({ + oldPassword: '', + newPassword: '', + repeatPassword: '', + }); + const [showPassword, setShowPassword] = useState({ + oldPassword: false, + newPassword: false, + repeatPassword: false, + }); + const [passwordError, setPasswordError] = useState(''); + + const handlePasswordUpdate = () => { + // Implement password update logic + }; + + const handlePasswordChange = (e: React.ChangeEvent, field: keyof typeof passwords) => { + const { value } = e.target; + setPasswords({ ...passwords, [field]: value }); + + if (field === 'repeatPassword') { + if (passwords.newPassword !== value) { + setPasswordError('Passwords do not match.'); + } else { + setPasswordError(''); + } + } + }; + + const renderInputField = (field: keyof typeof passwords, label: string) => ( + + {label} + + handlePasswordChange(e, field)} + borderWidth="0px" + bg="white" + width="400px" + /> + + : } + onClick={() => setShowPassword({ ...showPassword, [field]: !showPassword[field] })} + variant="link" + color="gray.500" + size="sm" + /> + + + + ); + + return ( + + + + {renderInputField('oldPassword', 'Old Password')} + {renderInputField('newPassword', 'New Password')} + {renderInputField('repeatPassword', 'Repeat New Password')} + + + {passwordError && {passwordError}} + + + + + ); +}; + +export default PasswordResetTab; diff --git a/django_project/geohosting/src/pages/DashboardPage/DashboardPage.tsx b/django_project/geohosting/src/pages/DashboardPage/DashboardPage.tsx index 91fba73..063197a 100644 --- a/django_project/geohosting/src/pages/DashboardPage/DashboardPage.tsx +++ b/django_project/geohosting/src/pages/DashboardPage/DashboardPage.tsx @@ -15,6 +15,7 @@ import DashboardMainPage from "./DashboardMainPage"; import SupportPage from "./Support/SupportPage"; import OrdersList from './Orders/OrderList'; import OrderDetail from "./Orders/OrderDetail"; +import ProfilePage from './Profile/ProfilePage'; import AgreementsTab from '../../components/Profile/Agreements'; const DashboardPage = ({ title="Dashboard" }) => { @@ -59,6 +60,7 @@ const DashboardPage = ({ title="Dashboard" }) => { } /> } /> } /> + } /> diff --git a/django_project/geohosting/src/pages/DashboardPage/Profile/ProfilePage.tsx b/django_project/geohosting/src/pages/DashboardPage/Profile/ProfilePage.tsx new file mode 100644 index 0000000..99a8186 --- /dev/null +++ b/django_project/geohosting/src/pages/DashboardPage/Profile/ProfilePage.tsx @@ -0,0 +1,229 @@ +import React, { useEffect, useState } from 'react'; +import { + Box, + Flex, + Button, + Input, + FormControl, + FormLabel, + Avatar, + VStack, + Text, + SimpleGrid, +} from '@chakra-ui/react'; +import { useDispatch, useSelector } from 'react-redux'; +import { AppDispatch, RootState } from '../../../redux/store'; +import { fetchUserProfile, updateUserProfile } from '../../../redux/reducers/profileSlice'; +import PasswordResetTab from '../../../components/Profile/Password'; + +const ProfilePage: React.FC = () => { + const dispatch: AppDispatch = useDispatch(); + const { user } = useSelector((state: RootState) => state.profile); + + const [profileImage, setProfileImage] = useState(null); + const [personalInfo, setPersonalInfo] = useState({ + name: '', + surname: '', + email: '', + }); + const [showPasswordUpdate, setShowPasswordUpdate] = useState(false); + const [billingInfo, setBillingInfo] = useState({ + billingName: '', + address: '', + postalCode: '', + country: '', + city: '', + region: '', + }); + + useEffect(() => { + dispatch(fetchUserProfile()); + }, [dispatch]); + + useEffect(() => { + if (user) { + setPersonalInfo({ + name: user.name, + surname: user.surname, + email: user.email, + }); + setBillingInfo({ + billingName: user.billingName, + address: user.address, + postalCode: user.postalCode, + country: user.country, + city: user.city, + region: user.region, + }); + } + }, [user]); + + const handleProfileImageChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + setProfileImage(e.target.files[0]); + } + }; + + const handleProfileUpdate = () => { + dispatch(updateUserProfile({ + ...personalInfo, + profileImage, + billingInfo, + })); + }; + + return ( + + Profile + + + + + + + + + + User Information + + Name + setPersonalInfo({ ...personalInfo, name: e.target.value })} + borderWidth="0px" + borderColor="gray.400" + bg="white" + width={{ base: '100%', md: '400px' }} + /> + + + Surname + setPersonalInfo({ ...personalInfo, surname: e.target.value })} + borderWidth="0px" + borderColor="gray.400" + bg="white" + width={{ base: '100%', md: '400px' }} + /> + + + Email + setPersonalInfo({ ...personalInfo, email: e.target.value })} + borderWidth="0px" + borderColor="gray.400" + bg="white" + width={{ base: '100%', md: '400px' }} + /> + + + + + + {showPasswordUpdate && ( + + + + )} + + + Billing Information + + + Institution Name + setBillingInfo({ ...billingInfo, billingName: e.target.value })} + borderWidth="0px" + borderColor="gray.400" + bg="white" + width={{ base: '100%', md: '400px' }} + /> + + + Billing Address + setBillingInfo({ ...billingInfo, address: e.target.value })} + borderWidth="0px" + borderColor="gray.400" + bg="white" + width={{ base: '100%', md: '400px' }} + /> + + + Postal Code + setBillingInfo({ ...billingInfo, postalCode: e.target.value })} + borderWidth="0px" + borderColor="gray.400" + bg="white" + width={{ base: '100%', md: '400px' }} + /> + + + Country + setBillingInfo({ ...billingInfo, country: e.target.value })} + borderWidth="0px" + borderColor="gray.400" + bg="white" + width={{ base: '100%', md: '400px' }} + /> + + + City + setBillingInfo({ ...billingInfo, city: e.target.value })} + borderWidth="0px" + borderColor="gray.400" + bg="white" + width={{ base: '100%', md: '400px' }} + /> + + + Region + setBillingInfo({ ...billingInfo, region: e.target.value })} + borderWidth="0px" + borderColor="gray.400" + bg="white" + width={{ base: '100%', md: '400px' }} + /> + + + VAT/Tax number + setBillingInfo({ ...billingInfo, region: e.target.value })} + borderWidth="0px" + borderColor="gray.400" + bg="white" + width={{ base: '100%', md: '400px' }} + /> + + + + + + + ); +}; + +export default ProfilePage; diff --git a/django_project/geohosting/src/redux/reducers/profileSlice.ts b/django_project/geohosting/src/redux/reducers/profileSlice.ts new file mode 100644 index 0000000..cbda6f7 --- /dev/null +++ b/django_project/geohosting/src/redux/reducers/profileSlice.ts @@ -0,0 +1,62 @@ +import { createAsyncThunk, createSlice, SerializedError } from '@reduxjs/toolkit'; + +interface ProfileState { + user: any; + loading: boolean; + error: SerializedError | null; +} + +const initialState: ProfileState = { + user: null, + loading: false, + error: null, +}; + +export const fetchUserProfile = createAsyncThunk( + 'profile/fetchUserProfile', + async () => { + const response = await fetch('/api/user/profile'); + return response.json(); + } +); + +export const updateUserProfile = createAsyncThunk( + 'profile/updateUserProfile', + async (profileData: any) => { + const response = await fetch('/api/user/profile', { + method: 'PUT', + body: JSON.stringify(profileData), + }); + return response.json(); + } +); + +const profileSlice = createSlice({ + name: 'profile', + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchUserProfile.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(fetchUserProfile.fulfilled, (state, action) => { + state.loading = false; + state.user = action.payload; + }) + .addCase(fetchUserProfile.rejected, (state, action) => { + state.loading = false; + state.error = action.error as SerializedError; + }) + .addCase(updateUserProfile.fulfilled, (state, action) => { + state.user = action.payload; + }) + .addCase(updateUserProfile.rejected, (state, action) => { + state.loading = false; + state.error = action.error as SerializedError; + }); + }, +}); + +export default profileSlice.reducer; diff --git a/django_project/geohosting/src/redux/store.ts b/django_project/geohosting/src/redux/store.ts index e4feabd..b3e2e67 100644 --- a/django_project/geohosting/src/redux/store.ts +++ b/django_project/geohosting/src/redux/store.ts @@ -5,6 +5,7 @@ import supportSlice from './reducers/supportSlice'; import ordersSlice from './reducers/ordersSlice'; import salesOrdersReducer from './reducers/salesOrdersSlice'; import instanceSlice from './reducers/instanceSlice'; +import profileSlice from './reducers/profileSlice'; export const store = configureStore({ reducer: { @@ -13,7 +14,8 @@ export const store = configureStore({ support: supportSlice, salesOrders: salesOrdersReducer, instance: instanceSlice, - orders:ordersSlice + orders:ordersSlice, + profile: profileSlice }, });