generated from chingu-voyages/voyage-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/ia tickets 27 72 user preferences (#92)
Feat: Added user preferences page
- Loading branch information
Showing
13 changed files
with
971 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React from 'react'; | ||
import { GoSettings } from 'react-icons/go'; | ||
import { MdDashboard } from 'react-icons/md'; | ||
import { CgLogOut } from 'react-icons/cg'; | ||
import { Tooltip } from 'flowbite-react'; | ||
import { useRouter } from 'next/router'; | ||
import { signOut } from 'next-auth/react'; | ||
|
||
const ControlPanel = () => { | ||
const router = useRouter(); | ||
const buttonSize = 50; | ||
|
||
const handleSignOut = async () => { | ||
await signOut(); | ||
router.push('/'); | ||
}; | ||
|
||
return ( | ||
<div className="flex flex-col items-center justify-start p-4"> | ||
<Tooltip content="Dashboard"> | ||
<MdDashboard | ||
onClick={() => { | ||
router.push('/account'); | ||
}} | ||
size={buttonSize} | ||
className="cursor-pointer mb-5" | ||
/> | ||
</Tooltip> | ||
|
||
<Tooltip content="Preferences"> | ||
<GoSettings | ||
onClick={() => { | ||
router.push('/account/preferences'); | ||
}} | ||
size={buttonSize} | ||
className="cursor-pointer hover:bg-gray-400 mb-5" | ||
/> | ||
</Tooltip> | ||
|
||
<Tooltip content="Logout"> | ||
<CgLogOut | ||
onClick={() => handleSignOut()} | ||
size={buttonSize} | ||
className="cursor-pointer hover:bg-gray-400 mb-5" | ||
/> | ||
</Tooltip> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ControlPanel; |
45 changes: 45 additions & 0 deletions
45
src/components/Account/Preferences/ConfirmVehicleDeleteModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { Button, Modal } from 'flowbite-react'; | ||
import React from 'react'; | ||
import { HiOutlineExclamationCircle } from 'react-icons/hi'; | ||
import { Vehicle } from '../../../schema/preferences.schema'; | ||
|
||
interface ConfirmVehicleDeleteModalProps { | ||
show: boolean; | ||
closeModal: () => void; | ||
vehicle: Vehicle | undefined; | ||
handleConfirmDelete: () => void; | ||
} | ||
const ConfirmVehicleDeleteModal = ({ | ||
show, | ||
closeModal, | ||
vehicle, | ||
handleConfirmDelete, | ||
}: ConfirmVehicleDeleteModalProps) => { | ||
return ( | ||
<div> | ||
<Modal show={show} size="md" popup={true}> | ||
<Modal.Header /> | ||
<Modal.Body> | ||
<div className="text-center"> | ||
<HiOutlineExclamationCircle className="mx-auto mb-4 h-14 w-14 text-red-400 dark:text-red-200" /> | ||
<h3 className="mb-5 text-lg font-normal"> | ||
Are you sure you want to delete your {vehicle?.vehicle_year}{' '} | ||
{vehicle?.vehicle_make} {vehicle?.vehicle_model}? | ||
</h3> | ||
<div className="flex justify-center gap-4"> | ||
<Button | ||
color="failure" | ||
onClick={() => handleConfirmDelete()} | ||
> | ||
Yes, I'm sure | ||
</Button> | ||
<Button onClick={closeModal}>No, cancel</Button> | ||
</div> | ||
</div> | ||
</Modal.Body> | ||
</Modal> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ConfirmVehicleDeleteModal; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import { Button, Label, Modal } from 'flowbite-react'; | ||
import React, { useEffect, useState } from 'react'; | ||
import SelectSearch, { | ||
fuzzySearch, | ||
SelectedOptionValue, | ||
} from 'react-select-search'; | ||
import { | ||
VehicleMakeSearch, | ||
VehicleModelSearch, | ||
} from '../../../schema/vehicle.schema'; | ||
import { trpc } from '../../../utils/trpc'; | ||
import 'react-select-search/style.css'; | ||
|
||
interface NewVehicleModalProps { | ||
show: boolean; | ||
toggleModal: () => void; | ||
} | ||
const NewVehicleModal = ({ show, toggleModal }: NewVehicleModalProps) => { | ||
const [vehicleMakes, setVehicleMakes] = useState<VehicleMakeSearch[]>([]); | ||
const [vehicleModels, setVehicleModels] = useState<VehicleModelSearch[]>([]); | ||
const [selectedVehicleMake, setSelectedVehicleMake] = useState< | ||
SelectedOptionValue | SelectedOptionValue[] | string | ||
>(''); | ||
const [selectedVehicleModel, setSelectedVehicleModel] = useState< | ||
SelectedOptionValue | SelectedOptionValue[] | string | ||
>(''); | ||
|
||
const utils = trpc.useContext(); | ||
|
||
const makes = trpc.useQuery(['vehicle.get-vehicle-makes']); | ||
const models = trpc.useQuery([ | ||
'vehicle.get-vehicle-models', | ||
selectedVehicleMake as string, | ||
]); | ||
|
||
const { mutate: mutateVehicle } = trpc.useMutation( | ||
['preferences.add-user-vehicle'], | ||
{ | ||
onSuccess() { | ||
utils.refetchQueries(['preferences.get-user-vehicles'], {active: true, exact: true, inactive: true}) | ||
}, | ||
} | ||
); | ||
|
||
useEffect(() => { | ||
if (!makes.isLoading && makes.data) { | ||
setVehicleMakes(makes.data); | ||
} | ||
}, [makes.isLoading, makes.data]); | ||
|
||
useEffect(() => { | ||
if (selectedVehicleMake) { | ||
if (!models.isLoading && models.data) { | ||
setVehicleModels(models.data); | ||
} | ||
} | ||
}, [selectedVehicleMake, models.isLoading, models.data]); | ||
|
||
const isVehicleMakesDisabled = vehicleMakes?.length == 0; | ||
const isVehicleModelsDisabled = vehicleModels?.length == 0; | ||
const isSubmitDisabled = !selectedVehicleMake || !selectedVehicleModel; | ||
|
||
const handleVehicleMakeChange = ( | ||
value: SelectedOptionValue | SelectedOptionValue[] | string | ||
) => { | ||
if (selectedVehicleModel) { | ||
setSelectedVehicleModel(''); | ||
} | ||
setSelectedVehicleMake(value); | ||
}; | ||
|
||
const handleVehicleModelChange = ( | ||
value: SelectedOptionValue | SelectedOptionValue[] | string | ||
) => { | ||
setSelectedVehicleModel(value); | ||
}; | ||
|
||
const handleFormSubmit = (e: React.FormEvent) => { | ||
e.preventDefault(); | ||
|
||
const make = makes.data?.filter( | ||
(make) => make.value === selectedVehicleMake | ||
); | ||
|
||
const yearAndModel = models.data | ||
?.filter((model) => model.value === selectedVehicleModel)![0]! | ||
.name.split(' '); | ||
|
||
mutateVehicle({ | ||
vehicle_model_id: selectedVehicleModel as string, | ||
vehicle_make: make![0]!.name, | ||
vehicle_model: yearAndModel![1] as string, | ||
vehicle_year: parseInt(yearAndModel![0] as string), | ||
}); | ||
resetForm(); | ||
toggleModal(); | ||
}; | ||
|
||
const handleFormCancel = () => { | ||
resetForm(); | ||
toggleModal(); | ||
}; | ||
|
||
const resetForm = () => { | ||
setSelectedVehicleMake(() => ''); | ||
setSelectedVehicleModel(() => ''); | ||
}; | ||
|
||
return ( | ||
<div> | ||
<Button onClick={() => toggleModal()}>Add Vehicle</Button> | ||
<Modal show={show} onClose={() => handleFormCancel()} size='sm'> | ||
{/* <Modal.Header>Add new Vehicle</Modal.Header> */} | ||
<Modal.Body> | ||
<div className="flex flex-col items-center gap-4"> | ||
<form className="flex flex-col gap-4"> | ||
<div> | ||
<Label | ||
htmlFor="vehicleMake" | ||
value="Please enter your vehicle make" | ||
id="vehicleMakeLabel" | ||
/> | ||
<SelectSearch | ||
options={vehicleMakes} | ||
value={selectedVehicleMake as string} | ||
search | ||
disabled={isVehicleMakesDisabled} | ||
onChange={handleVehicleMakeChange} | ||
filterOptions={fuzzySearch} | ||
emptyMessage={'No Makes found that match'} | ||
placeholder="Vehicle Make" | ||
/> | ||
</div> | ||
<div> | ||
<Label | ||
htmlFor="vehicleModel" | ||
value="Please enter your vehicle model" | ||
id="vehicleModelLabel" | ||
/> | ||
<SelectSearch | ||
options={vehicleModels} | ||
search | ||
value={selectedVehicleModel as string} | ||
onChange={handleVehicleModelChange} | ||
filterOptions={fuzzySearch} | ||
disabled={isVehicleModelsDisabled} | ||
emptyMessage={'No Models found that match'} | ||
placeholder="Vehicle Model" | ||
/> | ||
</div> | ||
</form> | ||
<div className="flex items-end gap-3 mt-3"> | ||
<Button onClick={() => handleFormCancel()}>Cancel</Button> | ||
<Button | ||
onClick={(e) => handleFormSubmit(e)} | ||
color="success" | ||
disabled={isSubmitDisabled} | ||
> | ||
Save | ||
</Button> | ||
</div> | ||
</div> | ||
</Modal.Body> | ||
</Modal> | ||
</div> | ||
); | ||
}; | ||
|
||
export default NewVehicleModal; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { Spinner } from 'flowbite-react'; | ||
import React, { useState } from 'react'; | ||
import { Vehicle } from '../../../schema/preferences.schema'; | ||
import { trpc } from '../../../utils/trpc'; | ||
import ConfirmVehicleDeleteModal from './ConfirmVehicleDeleteModal'; | ||
import VehicleTile from './VehicleTile'; | ||
|
||
const UserVehicles = () => { | ||
const utils = trpc.useContext(); | ||
const [showDeleteModal, setShowDeleteModal] = useState(false); | ||
const [vehicleToBeDeleted, setVehicleToBeDeleted] = useState< | ||
Vehicle | undefined | ||
>(); | ||
|
||
const { data: userVehicleData } = trpc.useQuery([ | ||
'preferences.get-user-vehicles', | ||
]); | ||
|
||
const { mutate: mutateUserPrimaryVehicle } = trpc.useMutation( | ||
'preferences.update-user-primary-vehicle', | ||
{ | ||
onSuccess(data) { | ||
utils.setQueryData(['preferences.get-user-vehicles'], (oldData) => { | ||
return { | ||
vehicles: oldData?.vehicles, | ||
userPrimaryVehicle: data.userPrimaryVehicleId, | ||
}; | ||
}); | ||
}, | ||
} | ||
); | ||
|
||
const { mutate: deleteVehicle } = trpc.useMutation( | ||
['preferences.remove-user-vehicle'], | ||
{ | ||
onSuccess() { | ||
utils.refetchQueries(['preferences.get-user-vehicles'], { | ||
active: true, | ||
exact: true, | ||
inactive: true, | ||
}); | ||
}, | ||
} | ||
); | ||
|
||
const handleUpdateUserPrimaryVehicle = (vehicleId: string) => { | ||
mutateUserPrimaryVehicle({ primaryVehicleId: vehicleId }); | ||
}; | ||
|
||
const handleDeleteVehicle = () => { | ||
deleteVehicle(vehicleToBeDeleted!.id); | ||
|
||
if (userVehicleData!.userPrimaryVehicle === vehicleToBeDeleted!.id) { | ||
mutateUserPrimaryVehicle({ primaryVehicleId: '' }); | ||
} | ||
|
||
handleDeleteModalClose(); | ||
}; | ||
|
||
const handleDeleteModalClose = () => { | ||
setShowDeleteModal((old) => !old); | ||
setVehicleToBeDeleted(undefined); | ||
}; | ||
|
||
const handleDeleteModalOpen = (vehicleId: string) => { | ||
if (userVehicleData) { | ||
const vehicles = userVehicleData.vehicles; | ||
const vehicle = vehicles!.filter((vehicle) => vehicle.id === vehicleId); | ||
|
||
setVehicleToBeDeleted(() => vehicle[0] as Vehicle); | ||
setShowDeleteModal((old) => !old); | ||
} | ||
return; | ||
}; | ||
|
||
if (!userVehicleData) | ||
return ( | ||
<div className="flex justify-center items-center"> | ||
<Spinner /> | ||
</div> | ||
); | ||
|
||
if (userVehicleData.vehicles?.length === 0) { | ||
return <div className='px-10 py-3 text-xl text-center'> | ||
You have no saved vehicles. | ||
</div>; | ||
} | ||
|
||
return ( | ||
<div className="grow grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-10 md:px-10"> | ||
{userVehicleData.vehicles!.map((vehicle) => { | ||
return ( | ||
<VehicleTile | ||
key={vehicle.id} | ||
id={vehicle.id} | ||
make={vehicle.vehicle_make} | ||
model={vehicle.vehicle_model} | ||
year={vehicle.vehicle_year} | ||
isPrimary={userVehicleData.userPrimaryVehicle ?? ''} | ||
setPrimary={handleUpdateUserPrimaryVehicle} | ||
toggleModal={handleDeleteModalOpen} | ||
/> | ||
); | ||
})} | ||
<ConfirmVehicleDeleteModal | ||
show={showDeleteModal} | ||
closeModal={handleDeleteModalClose} | ||
vehicle={vehicleToBeDeleted} | ||
handleConfirmDelete={handleDeleteVehicle} | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
export default UserVehicles; |
Oops, something went wrong.