From 0e4da163fedbdc90574aacc728d168e46060f99e Mon Sep 17 00:00:00 2001 From: Adi Zafri Date: Wed, 26 Jun 2024 07:16:20 +0800 Subject: [PATCH] declaring soft end of farmer UI --- web/src/App.jsx | 2 + web/src/components/CreateStoreForm.jsx | 190 +++++++++ web/src/components/ProfileMainCard.jsx | 546 +++++++++++++----------- web/src/components/ProfileMainCard2.jsx | 331 ++++++++++++++ web/src/contexts/UserContext.jsx | 22 +- web/src/pages/CreateStorePage.jsx | 55 +++ web/src/pages/ProfilePage.jsx | 2 + web/src/pages/StorePage.jsx | 17 +- web/src/services/api.jsx | 37 +- web/src/services/store.jsx | 16 +- 10 files changed, 938 insertions(+), 280 deletions(-) create mode 100644 web/src/components/CreateStoreForm.jsx create mode 100644 web/src/components/ProfileMainCard2.jsx create mode 100644 web/src/pages/CreateStorePage.jsx diff --git a/web/src/App.jsx b/web/src/App.jsx index 2f52759..7ad90ab 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -12,6 +12,7 @@ import StorePage from "./pages/StorePage.jsx"; import CreateProducePage from "./pages/CreateProducePage.jsx"; import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; +import CreateStorePage from "./pages/CreateStorePage.jsx"; const App = () => { @@ -69,6 +70,7 @@ const App = () => { } /> } /> } /> + } /> } /> }/> diff --git a/web/src/components/CreateStoreForm.jsx b/web/src/components/CreateStoreForm.jsx new file mode 100644 index 0000000..d33e8ec --- /dev/null +++ b/web/src/components/CreateStoreForm.jsx @@ -0,0 +1,190 @@ +import React, {useState, useContext, useEffect} from "react"; +import { useNavigate } from 'react-router-dom'; +import UserContext from '../contexts/UserContext.jsx'; +import { TextField, Button, Box, Typography, Grid } from '@mui/material'; +import CircularProgress from "@mui/material/CircularProgress"; +import storeService from "../services/store.jsx"; +import { toast } from 'react-toastify'; +import { useTheme } from '@mui/material/styles'; + +const CreateStoreForm = ({ userDetails }) => { + const { user, loading, updateUserDetails } = useContext(UserContext); + const [name, setName] = useState(''); + const [longitude, setLongitude] = useState(); + const [latitude, setLatitude] = useState(); + const [bankName, setBankName] = useState(userDetails.bankName || ''); // Set initial value + const [bankNumber, setBankNumber] = useState(userDetails.bankNumber || ''); // Set initial value + const [isLoading, setIsLoading] = useState(false); + const theme = useTheme(); // Add this line + + const navigate = useNavigate() + + useEffect(() => { + console.log('rendering CreateStoreForm, user details: ', userDetails) + }, []); + + const handleSubmit = async (event) => { + event.preventDefault(); + + const data = { + name, + longitude, + latitude, + bankName, + bankNumber, + farmer: user.id + }; + + console.log('about to create store with data: ', data) + + try { + setIsLoading(true) // Set isLoading to true when the form is being submitted + const response = await storeService.create(user.accessToken, data); + console.log('response from creating new store: ', response) + await updateUserDetails(); + toast.success('Store successfully created!'); + navigate('/store'); // Redirect to the store page + } catch (error) { + toast.error('Error creating store.'); + console.error('Error creating store:', error); + } finally { + setIsLoading(false); // Set isLoading back to false when the request is done + } + }; + + const handleCancel = () => { + navigate('/store'); // Navigate back to the store page + }; + + if (loading) { + return ; + } + + return ( + + + Create New Store + + setName(target.value)} + fullWidth + /> + setLongitude(target.value)} + fullWidth + /> + setLatitude(target.value)} + fullWidth + /> + setBankName(target.value)} + fullWidth + /> + setBankNumber(target.value)} + fullWidth + /> + {/**/} + {/* Create*/} + {/**/} + {/**/} + {/* Cancel*/} + {/**/} + + + {isLoading ? ( + // Render a loading indicator when isLoading is true + ) : ( + + )} + + + + + + + ); +}; + +export default CreateStoreForm; \ No newline at end of file diff --git a/web/src/components/ProfileMainCard.jsx b/web/src/components/ProfileMainCard.jsx index 02d013a..cd8b698 100644 --- a/web/src/components/ProfileMainCard.jsx +++ b/web/src/components/ProfileMainCard.jsx @@ -1,10 +1,9 @@ -import React, { useState, useEffect } from 'react'; -import { Card, CardContent, Typography, Grid, Button, TextField } from '@mui/material'; +import React, {useState, useEffect, useCallback, memo} from 'react'; +import { Card, CardContent, Typography, Grid, Button, TextField, CircularProgress } from '@mui/material'; import { styled } from '@mui/system'; -import accountService from '../services/account.jsx'; +import accountService from '../services/account'; import GridItem from '../layouts/GridItem'; -import CircularProgress from "@mui/material/CircularProgress"; -import {toast} from "react-toastify"; +import { toast } from 'react-toastify'; import { useNavigate } from 'react-router-dom'; const ImageContainer = styled('div')({ @@ -19,6 +18,188 @@ const ImageContainer = styled('div')({ marginRight: '20px', }); +const FirstRow = ({ isEditing, imagePreview, name, setName, currentUser }) => ( + + + + {imagePreview ? ( + User + ) : ( + No Image + )} + + + + {isEditing ? ( + setName(target.value)} + /> + ) : ( + + {currentUser.name || 'Incomplete'} + + )} + + +); + +const SecondRow = ({ isEditing, email, phone, bankNumber, bankName, type, isActive, handleInputChange, handleFileChange, imageFile, fileError, currentUser }) => ( + + {isEditing ? ( + <> + + handleInputChange(e, 'email')} + required={true} + /> + + + handleInputChange(e, 'phone')} + required={true} + /> + + + handleInputChange(e, 'bankNumber')} + required={true} + /> + + + handleInputChange(e, 'bankName')} + required={true} + /> + + + handleInputChange(e, 'type')} + required={true} + /> + + + handleInputChange(e, 'isActive')} + required={true} + /> + + + + + + + + + {imageFile &&

Selected file: {imageFile.name}

} + {fileError &&

{fileError}

} +
+
+
+ + ) : ( + <> + + + Email: {currentUser.email || 'Incomplete'} + + + + + Phone: {currentUser.phone || 'Incomplete'} + + + + + Bank Number: {currentUser.bankNumber || 'Incomplete'} + + + + + Bank Name: {currentUser.bankName || 'Incomplete'} + + + + + Type: {currentUser.type || 'Incomplete'} + + + + + Is Active: {currentUser.isActive !== null ? currentUser.isActive.toString() : 'Incomplete'} + + + + )} +
+); + +const ThirdRow = (({ isEditing, formValid, handleCancelSubmit, setIsEditing, handleSubmit, fileError }) => ( + + + {isEditing ? ( + <> + + + + ) : ( + + )} + + +)); + const ProfileMainCard = ({ user, userFromContext }) => { const [currentUser, setCurrentUser] = useState(user); const [isEditing, setIsEditing] = useState(false); @@ -29,63 +210,79 @@ const ProfileMainCard = ({ user, userFromContext }) => { const [bankName, setBankName] = useState(currentUser.bankName); const [type, setType] = useState(currentUser.type); const [isActive, setIsActive] = useState(currentUser.isActive); - const [imageFile, setImageFile] = useState(currentUser.image); + const [imageFile, setImageFile] = useState(null); + const [imagePreview, setImagePreview] = useState(currentUser.image); const [formErrors, setFormErrors] = useState({}); const [fileError, setFileError] = useState(''); const [formValid, setFormValid] = useState(false); const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); - const handleFileChange = (event) => { + useEffect(() => { + console.log('ProfileMainCard initial useEffect') + console.log('currentUser: ', currentUser) + console.log('formValid: ', formValid) + }, []); + + const handleFileChange = useCallback((event) => { const file = event.target.files[0]; - if (file && file.type.startsWith('image/')) { - if (file.size > 10 * 1024 * 1024) { + if (file) { + // Check file type + if (!file.type.startsWith('image/')) { + setFileError('Please select an image file'); + setImageFile(null); + setImagePreview(null); + } else if (file.size > 10 * 1024 * 1024) { // Check file size (10MB limit) + setFileError('File size exceeds 10MB limit'); setImageFile(null); - setFormErrors((errors) => ({ ...errors, imageFile: 'File size should not exceed 10MB.' })); - setFileError('File size should not exceed 10MB.'); + setImagePreview(null); } else { - setImageFile(file); - setFormErrors((errors) => ({ ...errors, imageFile: null })); setFileError(''); + setImageFile(file); + const reader = new FileReader(); + reader.onloadend = () => { + setImagePreview(reader.result); + }; + reader.readAsDataURL(file); } - } else { - setImageFile(null); - setFormErrors((errors) => ({ ...errors, imageFile: 'Invalid file type. Please select an image file.' })); - setFileError('Invalid file type. Please select an image file.'); } - }; + }, []); - const handleCancelSubmit = () => { - setName(currentUser.name); - setEmail(currentUser.email); - setPhone(currentUser.phone); - setBankNumber(currentUser.bankNumber); - setBankName(currentUser.bankName); - setType(currentUser.type); - setIsActive(currentUser.isActive); - setImageFile(currentUser.image); - setIsEditing(false); // Add this line to exit edit mode on cancel - }; - useEffect(() => { - if (name && email && phone && bankNumber && bankName && type && isActive !== null && (imageFile || currentUser.image) && !fileError) { - setFormValid(true); - } else { - setFormValid(false); - } - }, [name, email, phone, bankNumber, bankName, type, isActive, imageFile, currentUser.image, fileError]); + const handleInputChange = useCallback((event, field) => { + const value = event.target.value; + if (field === 'email') setEmail(value); + if (field === 'phone') setPhone(value); + if (field === 'bankNumber') setBankNumber(value); + if (field === 'bankName') setBankName(value); + if (field === 'type') setType(value); + if (field === 'isActive') setIsActive(value); + }, []); useEffect(() => { - if (currentUser.image) { - setFileError(''); - } - }, []); + console.log('ProfileMainCard, useEffect for form validation in run') + const validateForm = () => { + const errors = {}; + if (!name) errors.name = 'Name is required'; + if (!email) errors.email = 'Email is required'; + if (!phone) errors.phone = 'Phone is required'; + if (!bankNumber) errors.bankNumber = 'Bank Number is required'; + if (!bankName) errors.bankName = 'Bank Name is required'; + if (!type) errors.type = 'Type is required'; + if (!isActive) errors.isActive = 'Is Active is required'; + setFormErrors(errors); + setFormValid(Object.keys(errors).length === 0); + }; + validateForm(); + }, [name, email, phone, bankNumber, bankName, type, isActive]); const handleSubmit = async (event) => { - // event.preventDefault(); - setIsLoading(true); + event.preventDefault(); + console.log('updating user with event: ', event) + if (!formValid) return; const data = { + ...currentUser, name, email, phone, @@ -93,238 +290,70 @@ const ProfileMainCard = ({ user, userFromContext }) => { bankName, type, isActive, - image: imageFile ? '' : currentUser.image, + image: imageFile ? '' : currentUser.image }; try { - console.log('calling API to update account') - console.log('data: ', data) - console.log('image: ', imageFile) - const response = await accountService.update(currentUser.id, data, userFromContext.accessToken, imageFile); - const updatedUser = response.data - setCurrentUser(updatedUser) - /* - AccountResponseDTO{ - id [...] - email [...] - name [...] - phone [...] - bankNumber [...] - bankName [...] - image [...] - type [...] - isActive [...] + setIsLoading(true); + const token = userFromContext.accessToken; // Assuming you get the token from context + const response = await accountService.update(currentUser.id, data, token, imageFile); + if (response.status === 200) { + toast.success('User updated successfully'); + setCurrentUser(data); + setIsEditing(false); + setImageFile(null); + } else { + toast.error('Failed to update user'); } - */ - setEmail(updatedUser.email) - setName(updatedUser.name) - setBankName(updatedUser.bankName) - setPhone(updatedUser.phone) - setBankNumber(updatedUser.bankNumber) - setImageFile(updatedUser.image) - console.log('user updated to: ', updatedUser) - setIsEditing(false); - toast.success('Account successfully updated') - navigate('/profile') } catch (error) { - console.error('Failed to update user details:', error); - toast.error('Account update failed') + console.error('Error updating user:', error); + toast.error('Failed to update user'); } finally { setIsLoading(false); } }; - const FirstRow = () => ( - - - - {currentUser.image ? ( - User - ) : ( - No Image - )} - - - - {isEditing ? ( - setName(target.value)} - /> - ) : ( - - {user.name || 'Incomplete'} - - )} - - - ); - - const SecondRow = () => ( - - {isEditing ? ( - <> - - setEmail(e.target.value)} - required={true} - /> - - - setPhone(e.target.value)} - required={true} - /> - - - setBankNumber(e.target.value)} - required={true} - /> - - - setBankName(e.target.value)} - required={true} - /> - - - setType(e.target.value)} - required={true} - /> - - - setIsActive(e.target.value)} - required={true} - /> - - - - - - - - - - {imageFile &&

Selected file: {imageFile.name}

} - {fileError &&

{fileError}

} -
-
-
-
- - ) : ( - <> - - - Email: {user.email || 'Incomplete'} - - - - - Phone: {user.phone || 'Incomplete'} - - - - - Bank Number: {user.bankNumber || 'Incomplete'} - - - - - Bank Name: {user.bankName || 'Incomplete'} - - - - - Type: {user.type || 'Incomplete'} - - - - - Is Active: {user.isActive !== null ? user.isActive.toString() : 'Incomplete'} - - - - )} -
- ); - - const ThirdRow = () => ( - - - {isEditing ? ( - <> - - - - ) : ( - - )} - - - ); + const handleCancelSubmit = useCallback(() => { + setName(currentUser.name); + setEmail(currentUser.email); + setPhone(currentUser.phone); + setBankNumber(currentUser.bankNumber); + setBankName(currentUser.bankName); + setType(currentUser.type); + setIsActive(currentUser.isActive); + setImageFile(null); + setImagePreview(currentUser.image); + setIsEditing(false); + }, [currentUser]); - if (isLoading) - return ; + if (isLoading) return ; return ( - - - + + + @@ -332,3 +361,4 @@ const ProfileMainCard = ({ user, userFromContext }) => { }; export default ProfileMainCard; + diff --git a/web/src/components/ProfileMainCard2.jsx b/web/src/components/ProfileMainCard2.jsx new file mode 100644 index 0000000..751c46f --- /dev/null +++ b/web/src/components/ProfileMainCard2.jsx @@ -0,0 +1,331 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, Typography, Grid, Button, TextField } from '@mui/material'; +import { styled } from '@mui/system'; +import accountService from '../services/account.jsx'; +import GridItem from '../layouts/GridItem'; +import CircularProgress from "@mui/material/CircularProgress"; +import {toast} from "react-toastify"; +import { useNavigate } from 'react-router-dom'; + +const ImageContainer = styled('div')({ + width: 150, + height: 150, + border: '1px solid gray', + borderRadius: '50%', + overflow: 'hidden', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + marginRight: '20px', +}); + +const ProfileMainCard = ({ user, userFromContext }) => { + const [currentUser, setCurrentUser] = useState(user); + const [isEditing, setIsEditing] = useState(false); + const [name, setName] = useState(currentUser.name); + const [email, setEmail] = useState(currentUser.email); + const [phone, setPhone] = useState(currentUser.phone); + const [bankNumber, setBankNumber] = useState(currentUser.bankNumber); + const [bankName, setBankName] = useState(currentUser.bankName); + const [type, setType] = useState(currentUser.type); + const [isActive, setIsActive] = useState(currentUser.isActive); + const [imageFile, setImageFile] = useState(currentUser.image); + const [formErrors, setFormErrors] = useState({}); + const [fileError, setFileError] = useState(''); + const [formValid, setFormValid] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const navigate = useNavigate(); + + const handleFileChange = (event) => { + const file = event.target.files[0]; + if (file && file.type.startsWith('image/')) { + if (file.size > 10 * 1024 * 1024) { + setImageFile(null); + setFormErrors((errors) => ({ ...errors, imageFile: 'File size should not exceed 10MB.' })); + setFileError('File size should not exceed 10MB.'); + } else { + setImageFile(file); + setFormErrors((errors) => ({ ...errors, imageFile: null })); + setFileError(''); + } + } else { + setImageFile(null); + setFormErrors((errors) => ({ ...errors, imageFile: 'Invalid file type. Please select an image file.' })); + setFileError('Invalid file type. Please select an image file.'); + } + }; + + const handleCancelSubmit = () => { + setName(currentUser.name); + setEmail(currentUser.email); + setPhone(currentUser.phone); + setBankNumber(currentUser.bankNumber); + setBankName(currentUser.bankName); + setType(currentUser.type); + setIsActive(currentUser.isActive); + setImageFile(currentUser.image); + setIsEditing(false); // Add this line to exit edit mode on cancel + }; + + useEffect(() => { + console.log('running useEffect to check form validity') + if (name && email && phone && bankNumber && bankName && type && isActive !== null && (imageFile || currentUser.image) && !fileError) { + setFormValid(true); + } else { + setFormValid(false); + } + }, [name, email, phone, bankNumber, bankName, type, isActive, imageFile, currentUser.image, fileError]); + + useEffect(() => { + console.log('running useEffect for initial image check') + if (currentUser.image) { + setFileError(''); + } + }, []); + + const handleSubmit = async (event) => { + event.preventDefault(); + setIsLoading(true); + + const data = { + name, + email, + phone, + bankNumber, + bankName, + type, + isActive, + image: imageFile ? '' : currentUser.image, + }; + + try { + console.log('calling API to update account') + console.log('data: ', data) + console.log('image: ', imageFile) + const response = await accountService.update(currentUser.id, data, userFromContext.accessToken, imageFile); + const updatedUser = response.data + setCurrentUser(updatedUser) + setEmail(updatedUser.email) + setName(updatedUser.name) + setBankName(updatedUser.bankName) + setPhone(updatedUser.phone) + setBankNumber(updatedUser.bankNumber) + setImageFile(updatedUser.image) + console.log('user updated to: ', updatedUser) + setIsEditing(false); + toast.success('Account successfully updated') + navigate('/profile') + } catch (error) { + console.error('Failed to update user details:', error); + toast.error('Account update failed') + } finally { + setIsLoading(false); + } + }; + + const FirstRow = () => ( + + + + {currentUser.image ? ( + User + ) : ( + No Image + )} + + + + {isEditing ? ( + setName(target.value)} + /> + ) : ( + + {currentUser.name || 'Incomplete'} + + )} + + + ); + + const SecondRow = () => ( + + {isEditing ? ( + <> + + setEmail(e.target.value)} + required={true} + /> + + + setPhone(e.target.value)} + required={true} + /> + + + setBankNumber(e.target.value)} + required={true} + /> + + + setBankName(e.target.value)} + required={true} + /> + + + setType(e.target.value)} + required={true} + /> + + + setIsActive(e.target.value)} + required={true} + /> + console.log('test: ', e.target.value)} + // required={true} + /> + + + + + + + + + + {imageFile &&

Selected file: {imageFile.name}

} + {fileError &&

{fileError}

} +
+
+
+
+ + ) : ( + <> + + + Email: {currentUser.email || 'Incomplete'} + + + + + Phone: {currentUser.phone || 'Incomplete'} + + + + + Bank Number: {currentUser.bankNumber || 'Incomplete'} + + + + + Bank Name: {currentUser.bankName || 'Incomplete'} + + + + + Type: {currentUser.type || 'Incomplete'} + + + + + Is Active: {currentUser.isActive !== null ? currentUser.isActive.toString() : 'Incomplete'} + + + + )} +
+ ); + + const ThirdRow = () => ( + + + {isEditing ? ( + <> + + + + ) : ( + + )} + + + ); + + if (isLoading) + return ; + + console.log('rendering return'); + return ( + + + + + + + + + + ); +}; + +export default ProfileMainCard; diff --git a/web/src/contexts/UserContext.jsx b/web/src/contexts/UserContext.jsx index 71d4581..7f0cb0e 100644 --- a/web/src/contexts/UserContext.jsx +++ b/web/src/contexts/UserContext.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import axios from "axios"; import storeService from '../services/store.jsx'; import cartService from '../services/cart.jsx'; -import { setRefreshToken as setRefreshTokenInApi } from '../services/api.jsx'; +// import { setRefreshToken as setRefreshTokenInApi } from '../services/api.jsx'; import {jwtDecode} from 'jwt-decode'; const host = import.meta.env.VITE_API_URL @@ -25,14 +25,16 @@ export const UserProvider = ({ children }) => { result.cart = response.data.id ? response.data.id : null; } } catch (error) { - if (error.status === 404) { + console.log('getExtraUserDetails encountered error: ', error) + if (error.status === '404') { + console.log('User does not have a store or cart') if (user.type === 'Farmer') { result.store = null; } else { result.cart = null; } } - } + }console.log('returning result: ', result) return result; }; @@ -52,6 +54,7 @@ export const UserProvider = ({ children }) => { if (storedUser) { const parsedUser = JSON.parse(storedUser); setUser(parsedUser); + console.log('parsed user from local storage: ', parsedUser) if (isTokenExpired(parsedUser.accessToken)) { console.log('Token is expired'); @@ -81,9 +84,16 @@ export const UserProvider = ({ children }) => { setUser(fullDetails); console.log('setting user to context after login: ', fullDetails); localStorage.setItem('user', JSON.stringify(fullDetails)); - setRefreshTokenInApi(refreshAccessToken); // Set the refreshAccessToken function in the api module + // setRefreshTokenInApi(refreshAccessToken); // Set the refreshAccessToken function in the api module }; + const updateUserDetails = async () => { + const extraDetails = await getExtraUserDetails(user); + const fullDetails = { ...user, ...extraDetails }; + console.log('updating user in global context to: ', fullDetails); + setUser(fullDetails) + } + const logout = () => { setUser(null); localStorage.removeItem('user'); @@ -107,13 +117,15 @@ export const UserProvider = ({ children }) => { setUser(updatedUser); localStorage.setItem('user', JSON.stringify(updatedUser)); console.log('Token refreshed successfully'); + return response.data; } catch (error) { console.error('Failed to refresh token', error); + throw error; } }; return ( - + {children} ); diff --git a/web/src/pages/CreateStorePage.jsx b/web/src/pages/CreateStorePage.jsx new file mode 100644 index 0000000..0124c08 --- /dev/null +++ b/web/src/pages/CreateStorePage.jsx @@ -0,0 +1,55 @@ +import React, {useContext, useEffect, useState} from "react"; +import UserContext from "../contexts/UserContext.jsx"; +import {useUserCheck} from "../hooks/useUserCheck.jsx"; +import CircularProgress from "@mui/material/CircularProgress"; +import CreateStoreForm from "../components/CreateStoreForm.jsx"; +import accountService from "../services/account.jsx"; +import {toast} from "react-toastify"; +import {useNavigate} from "react-router-dom"; + +const CreateStorePage = () => { + const { user, loading } = useContext(UserContext); + const [isLoading, setIsLoading] = useState(false); + const [fullUser, setFullUser] = useState({}) + const navigate = useNavigate(); + + useUserCheck(); + + useEffect(() => { + console.log('rendering CreateStorePage, validating user details') + const validateCompleteUserDetails = async () => { + const response = await accountService.getById(user.id); + const userDetails = response.data; + console.log('retrieved user details: ', userDetails) + if (userDetails.id && userDetails.email && userDetails.name && userDetails.phone && userDetails.bankNumber && userDetails.bankName && userDetails.image){ + setFullUser(userDetails) + return true + } + toast.info('Complete account details first to create store') + navigate('/profile') + return false; + } + + const fetchData = async () => { + setIsLoading(true); + console.log('valid for store creation: ', await validateCompleteUserDetails()); + setIsLoading(false) + } + + fetchData(); + }, []); + + if (loading || isLoading) { + return ; + } + + if (!user) { + return null; + } + + return ( + + ); +} + +export default CreateStorePage; \ No newline at end of file diff --git a/web/src/pages/ProfilePage.jsx b/web/src/pages/ProfilePage.jsx index 2158661..380f723 100644 --- a/web/src/pages/ProfilePage.jsx +++ b/web/src/pages/ProfilePage.jsx @@ -16,6 +16,7 @@ const ProfilePage = () => { useUserCheck(); useEffect(() => { + console.log('profile page, useEffect in run') const fetchUserDetails = async () => { setIsLoading(true); try { @@ -45,6 +46,7 @@ const ProfilePage = () => { console.log('profile page, currentUser supposedly fetched from context: ', currentUser) } + console.log('ProfilePage, rendering return') return (

Profile Page

diff --git a/web/src/pages/StorePage.jsx b/web/src/pages/StorePage.jsx index 3c31bd7..2ec82fe 100644 --- a/web/src/pages/StorePage.jsx +++ b/web/src/pages/StorePage.jsx @@ -1,8 +1,8 @@ import React, { useContext, useEffect, useState } from 'react'; import UserContext from "../contexts/UserContext.jsx"; import { useNavigate } from 'react-router-dom'; -import storeService from '../services/store'; // Service for store-related API calls -import produceService from '../services/produce'; // Service for produce-related API calls +import storeService from '../services/store'; +import produceService from '../services/produce'; import CircularProgress from '@mui/material/CircularProgress'; import ProduceCard from "../components/ProduceCard.jsx"; import { Grid, Container, Button, Box, Typography } from '@mui/material'; @@ -31,7 +31,6 @@ const StorePage = () => { setProduceList(produceListData); } else { console.log('User does not have a store') - // Handle the case when user.store is null } } catch (error) { console.error("Error fetching store or produce:", error); @@ -58,10 +57,11 @@ const StorePage = () => { {store ? ( <> - - {user.name}'s Store - + + + + {user.name}'s Store

{store?.name}

{produceList.map(produce => ( @@ -72,7 +72,10 @@ const StorePage = () => { ) : ( - No store exists for user + <> + No store exists for user + + )}
); diff --git a/web/src/services/api.jsx b/web/src/services/api.jsx index 3d0c9a1..cabba4b 100644 --- a/web/src/services/api.jsx +++ b/web/src/services/api.jsx @@ -1,10 +1,9 @@ import axios from 'axios'; const host = import.meta.env.VITE_API_URL -let refreshToken = null; const api = axios.create({ - baseURL: host, + baseURL: import.meta.env.VITE_API_URL, }); api.interceptors.response.use( @@ -16,15 +15,38 @@ api.interceptors.response.use( error.response.status === 401 && !originalRequest._retry && originalRequest.method !== 'get' && - // !expiredTokenMessages.some(message => error.response.data.message.includes(message)) error.response.data.message === 'Expired token' ) { console.log('api error message: ', error.response.data.message) console.log('refreshing token'); originalRequest._retry = true; - const newToken = await refreshToken(); - console.log('new token: ', newToken); - originalRequest.headers.Authorization = `Bearer ${newToken}`; + + const storedUser = localStorage.getItem('user'); + let parsedUser = null; + let newTokens = null; + if(!storedUser) { + window.redirect(`${host}/login`) + return + } + else { + parsedUser = JSON.parse(storedUser); + // setUser(parsedUser); + console.log('parsed user from local storage at api interceptor: ', parsedUser) + const tokens = { + accessToken: parsedUser.accessToken, + refreshToken: parsedUser.refreshToken + } + + const response = await axios.post(`${host}/auth/refresh`, tokens); + console.log('response from refresh token mechanism: ', response); + newTokens = response.data; + } + + console.log('new tokens: ', newTokens); + originalRequest.headers.Authorization = `Bearer ${newTokens.accessToken}`; + const updatedUser = {...parsedUser, accessToken: newTokens.accessToken, refreshToken: newTokens.refreshToken}; + console.log('updating user tokens from api interceptor: ', updatedUser) + localStorage.setItem('user', JSON.stringify(updatedUser)); return api(originalRequest); } else{ @@ -34,8 +56,5 @@ api.interceptors.response.use( } ); -export const setRefreshToken = (tokenRefreshFunction) => { - refreshToken = tokenRefreshFunction; -}; export default api; \ No newline at end of file diff --git a/web/src/services/store.jsx b/web/src/services/store.jsx index 83c337c..8372a2b 100644 --- a/web/src/services/store.jsx +++ b/web/src/services/store.jsx @@ -26,4 +26,18 @@ const getByFarmer = async (farmerId) => { } }; -export default { getAll, getById, getByFarmer }; + +const create = async (token, data) => { + try { + return await api.post(`${baseUrl}`, data, { + headers: { + Authorization: `Bearer ${token}` + } + }); + } catch (error) { + throw error.response.data; + } + +} + +export default { getAll, getById, getByFarmer, create };