diff --git a/.env b/.env new file mode 100644 index 0000000..6c40f26 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VITE_API_BASE_URL=https://hsytrailerapi.azurewebsites.net/api/ diff --git a/package-lock.json b/package-lock.json index 3bc2a61..af956f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "axios": "^1.6.2", "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.1", + "dotenv": "^16.3.1", "geolib": "^3.3.4", "i18next": "^23.7.6", "i18next-browser-languagedetector": "^7.2.0", @@ -29,7 +30,8 @@ "react-i18next": "^13.5.0", "react-router-dom": "^6.16.0", "react-simple-star-rating": "^5.1.7", - "react-toastify": "^9.1.3" + "react-toastify": "^9.1.3", + "uuid": "^9.0.1" }, "devDependencies": { "@types/react": "^18.2.15", @@ -1324,6 +1326,17 @@ "csstype": "^3.0.2" } }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.567", "dev": true, @@ -4341,6 +4354,18 @@ "punycode": "^2.1.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vite": { "version": "4.5.0", "dev": true, diff --git a/package.json b/package.json index ccb8c41..6b3705a 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "axios": "^1.6.2", "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.1", + "dotenv": "^16.3.1", "geolib": "^3.3.4", "i18next": "^23.7.6", "i18next-browser-languagedetector": "^7.2.0", @@ -35,7 +36,8 @@ "react-i18next": "^13.5.0", "react-router-dom": "^6.16.0", "react-simple-star-rating": "^5.1.7", - "react-toastify": "^9.1.3" + "react-toastify": "^9.1.3", + "uuid": "^9.0.1" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/src/App.jsx b/src/App.jsx index d2c3462..992f13b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -13,6 +13,7 @@ import { StepperProvider } from './context/StepperContext'; import { useTranslation } from 'react-i18next'; import { useState } from 'react'; + function App() { const [itemReturned, setItemReturned] = useState(false); diff --git a/src/components/BankButton.jsx b/src/components/BankButton.jsx index a112a9b..62bfdd0 100644 --- a/src/components/BankButton.jsx +++ b/src/components/BankButton.jsx @@ -4,18 +4,41 @@ import { useState } from 'react'; import PopUpWarningModal from '../components/PopUpWarningModal'; import { useNavigate } from 'react-router-dom'; import { Trans, useTranslation } from 'react-i18next'; - -const BankButton = ({ logo, bankName, rentId }) => { +import useApi from '../hooks/useApi'; +import { useStepper } from '../hooks/useStepper'; +const BankButton = ({ logo, bankName, randomUUID }) => { const [showWarningModal, setShowWarningModal] = useState(false); + const navigate = useNavigate(); const { t } = useTranslation(); + const { userData } = useStepper(); + const { postRequest } = useApi(); - const handleClick = (bankName) => { + const handleClick = async (bankName) => { if (bankName === 'HSY') { handleOpenWarningModal(); } else { - navigate(`/rent-successful/${rentId}`); + try { + const bodyData = { + customerInfo: { + name: userData.firstName, + lastName: userData.lastName, + phoneNumber: userData.phoneNumber, + email: userData.emailAddress, + address: userData.streetName, + zipCode: userData.postalCode, + city: userData.cityName, + }, + uuid: randomUUID, + }; + + const responce = await postRequest('add-reservation', bodyData); + console.log('BankButton.jsx 36', responce.updatedReservation._id); + navigate(`/rent-successful/${responce.updatedReservation._id}`); + } catch (error) { + console.log(error); + } } }; @@ -68,7 +91,7 @@ const BankButton = ({ logo, bankName, rentId }) => { BankButton.propTypes = { logo: PropTypes.any.isRequired, bankName: PropTypes.string, - rentId: PropTypes.string.isRequired, + randomUUID: PropTypes.string.isRequired, }; export default BankButton; diff --git a/src/components/BankType.jsx b/src/components/BankType.jsx index 68b56bd..32d082d 100644 --- a/src/components/BankType.jsx +++ b/src/components/BankType.jsx @@ -2,7 +2,14 @@ import PropTypes from 'prop-types'; import BankButton from './BankButton'; import styles from '../css/BankButton.module.css'; -const BankType = ({ gridName, title, arrayName, paymentName, rentId }) => { +const BankType = ({ + gridName, + title, + arrayName, + paymentName, + rentId, + randomUUID, +}) => { return (

{title}

@@ -14,6 +21,7 @@ const BankType = ({ gridName, title, arrayName, paymentName, rentId }) => { bankName={item.bankName} key={item.bankName} rentId={rentId} + randomUUID={randomUUID} >
))} @@ -28,5 +36,6 @@ BankType.propTypes = { arrayName: PropTypes.array, paymentName: PropTypes.string, rentId: PropTypes.string.isRequired, + randomUUID: PropTypes.string.isRequired, }; export default BankType; diff --git a/src/components/PopUpWarningModal.jsx b/src/components/PopUpWarningModal.jsx index 134d025..9736a51 100644 --- a/src/components/PopUpWarningModal.jsx +++ b/src/components/PopUpWarningModal.jsx @@ -40,7 +40,7 @@ PopUpWarningModal.propTypes = { show: PropTypes.bool, onHide: PropTypes.func, title: PropTypes.string, - body: PropTypes.obj, + body: PropTypes.string, backButton: PropTypes.string, acceptButton: PropTypes.string, acceptButtonVariant: PropTypes.string, diff --git a/src/components/SelectTime.jsx b/src/components/SelectTime.jsx index b3b8148..8313a46 100644 --- a/src/components/SelectTime.jsx +++ b/src/components/SelectTime.jsx @@ -7,6 +7,7 @@ function SelectTime({ setSelectedStationAndTime, stationName, timeSlots, + randomUUID, }) { const handleClick = (stationName, timeSlot) => { setSelectedStationAndTime({ @@ -16,6 +17,7 @@ function SelectTime({ const itemElements = timeSlots.map((item, index) => ( { + const { deleteRequest } = useApi(); const [showWarningModal, setShowWarningModal] = useState(false); const { t } = useTranslation(); @@ -31,20 +36,8 @@ const ProductAndTime = ({ selectedProduct, } = useStepper(); - // values will be used in future - const currentDate = new Date(); - const currentMonth = currentDate.getMonth(); - const currentYear = currentDate.getFullYear(); - const currentDay = currentDate.getDate(); - const futureDates = []; - const navigate = useNavigate(); - for (let i = 1; i < 4; i++) { - const randomDate = new Date(currentYear, currentMonth, currentDay + i); - futureDates.push(randomDate); - } - // handling the button click for selecting a time and date const handleSubmit = () => { if ( @@ -66,6 +59,29 @@ const ProductAndTime = ({ setShowWarningModal(true); }; + // TODO add before unload delete temp reservation with uuid + useEffect(() => { + const sendDeleteRequestOnUnload = async () => { + window.addEventListener('beforeunload', async () => { + try { + const responce = await deleteRequest( + 'delete-temp-reservation/', + randomUUID, + ); + console.log('TimeForm.jsx 72 ', responce); + } catch (error) { + console.error('Error sending DELETE request:', error); + } + }); + }; + + sendDeleteRequestOnUnload(); + + // Cleanup the event listener when the component unmounts + return () => { + window.removeEventListener('beforeunload', sendDeleteRequestOnUnload); + }; + }, []); return ( <> { + const { postRequest } = useApi(); + const { selectedDate, selectedProduct, selectAdaptor } = useStepper(); + + const handleClick = async () => { setSelectedTime(stationName, buttonText); + try { + const bodyData = { + uuid: randomUUID, + station: stationName, + timeSlot: buttonText, + product: selectedProduct, + date: selectedDate, + isAdapter: selectAdaptor, + }; + const isEmptyField = Object.values(bodyData).some((value) => !value); + if (isEmptyField) { + const response = await postRequest('add-temp-reservation', bodyData); + console.log('TimePeriodButton.jsx 30 ', response); + } + } catch (error) { + console.error('api error: ', error); + } }; return ( @@ -35,6 +58,7 @@ TimePeriodButton.propTypes = { stationName: PropTypes.string.isRequired, setSelectedTime: PropTypes.func.isRequired, setSelectedStation: PropTypes.func, + randomUUID: PropTypes.string, }; export default TimePeriodButton; diff --git a/src/components/UserForm.jsx b/src/components/UserForm.jsx index c73aba1..170185d 100644 --- a/src/components/UserForm.jsx +++ b/src/components/UserForm.jsx @@ -13,7 +13,6 @@ import PopUpWarningModal from '../components/PopUpWarningModal'; import hsyLogo from '../assets/hsy_logo_dark.png'; import { useStepper } from '../hooks/useStepper'; import { useTranslation } from 'react-i18next'; - const UserForm = ({ onSubmit, onPrevStep }) => { const [validated, setValidated] = useState(false); const [showInfoModal, setShowInfoModal] = useState(false); diff --git a/src/hooks/useApi.js b/src/hooks/useApi.js index 2e292f7..585d124 100644 --- a/src/hooks/useApi.js +++ b/src/hooks/useApi.js @@ -1,7 +1,8 @@ import API from '../utils/axios'; import { useState } from 'react'; import { errorHandling } from '../utils/errorHandling'; - +import { postRequest, deleteRequest } from '../services/ApiServices'; +import { useNavigate } from 'react-router-dom'; const useApi = () => { const [error, setError] = useState(null); @@ -29,7 +30,44 @@ const useApi = () => { return await errorHandling(deleteRent, (err) => setError(err)); }; - return { getRentById, updateRent, deleteRent, error }; + const navigate = useNavigate(); + + const handleApiError = (error) => { + console.error('API Error:', error); + navigate('/error'); + }; + + const handleApiSuccess = (response) => { + console.log('API Response:', response); + }; + + const postApiRequest = async (endpoint, data) => { + try { + const response = await postRequest(endpoint, data); + handleApiSuccess(response); + return response; + } catch (error) { + handleApiError(error); + } + }; + const deleteApiRequest = async (endpoint, uuid) => { + try { + const response = await deleteRequest(endpoint, uuid); + handleApiSuccess(response); + return response; + } catch (error) { + handleApiError(error); + } + }; + + return { + postRequest: postApiRequest, + deleteRequest: deleteApiRequest, + getRentById, + updateRent, + deleteRent, + error, + }; }; -export default useApi; +export default useApi; \ No newline at end of file diff --git a/src/pages/RentProcess.jsx b/src/pages/RentProcess.jsx index 78357af..8cd0a9f 100644 --- a/src/pages/RentProcess.jsx +++ b/src/pages/RentProcess.jsx @@ -25,23 +25,25 @@ import styles from '../css/BankButton.module.css'; import BankType from '../components/BankType'; import PopUpWarningModal from '../components/PopUpWarningModal'; import { useTranslation } from 'react-i18next'; - +import { v4 as uuidv4 } from 'uuid'; const RentProcessPage = () => { const countdownDuration = 20 * 60 * 1000; const [activeStep, setActiveStep] = useState(0); const [isMobile, setIsMobile] = useState(window.innerWidth < 820); const [showWarningModal, setShowWarningModal] = useState(false); + const [randomUUID, setRandomUUID] = useState(''); + useEffect(() => { + setRandomUUID(uuidv4()); + }, []); + const [reservationDeadline, setReservationDeadline] = useState( + calculateReservationDeadline(), + ); - // TODO: get the id from the response when the user has made a reservation const mockRentData = { id: '656e0884162df1917d30e826', }; - const [reservationDeadline, setReservationDeadline] = useState( - calculateReservationDeadline(), - ); - const { t } = useTranslation(); function calculateReservationDeadline() { @@ -143,6 +145,7 @@ const RentProcessPage = () => { title={t('Mobiilimaksutavat')} arrayName={mobileBanks} paymentName={styles.mobilePayment} + randomUUID={randomUUID} /> { title={t('Korttimaksutavat')} arrayName={cardPayments} paymentName={styles.cardPayment} + randomUUID={randomUUID} /> { title={t('Pankkimaksutavat')} arrayName={bankPayments} paymentName={styles.bankPayment} + randomUUID={randomUUID} /> { title={t('Maksu paikan päällä')} arrayName={irlPayments} paymentName={styles.irlPayment} + randomUUID={randomUUID} /> ); @@ -185,6 +191,7 @@ const RentProcessPage = () => { handleWarningModal={handleWarningModal} onProductAndTimeSelected={handleProductAndTimeSelected} onPrevStep={handlePrevStep} + randomUUID={randomUUID} /> ); case 2: diff --git a/src/services/ApiServices.js b/src/services/ApiServices.js new file mode 100644 index 0000000..927c865 --- /dev/null +++ b/src/services/ApiServices.js @@ -0,0 +1,31 @@ +const API_BASE_URL = 'https://hsytrailerapi.azurewebsites.net/api/'; + +export const postRequest = async (endpoint, data) => { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + return response.json(); +}; +export const deleteRequest = async (endpoint, uuid) => { + const response = await fetch(`${API_BASE_URL}${endpoint}${uuid}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + return response.json(); +}; diff --git a/src/utils/axios.js b/src/utils/axios.js index eb25be9..d4dd889 100644 --- a/src/utils/axios.js +++ b/src/utils/axios.js @@ -1,10 +1,6 @@ import axios from 'axios'; -import { NODE_ENV, PUBLIC_DOMAIN, PUBLIC_PORT } from './constants'; -const baseURL = - NODE_ENV === 'production' - ? 'https://hsytrailerapi.azurewebsites.net/api/' - : `http://${PUBLIC_DOMAIN ?? 'localhost'}:${PUBLIC_PORT ?? '3000'}/api/`; +const baseURL = 'https://hsytrailerapi.azurewebsites.net/api/'; const axiosInstance = axios.create({ baseURL: baseURL, diff --git a/vite.config.js b/vite.config.js index 89e04ca..3e0094e 100644 --- a/vite.config.js +++ b/vite.config.js @@ -5,5 +5,14 @@ import react from '@vitejs/plugin-react' export default defineConfig({ base: '/', // gh pages base config plugins: [react()], + server: { + proxy: { + '/api': { + target: 'http://localhost:5173', // Point to your Vite development server + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + }, + }, + }, });