Skip to content

Commit

Permalink
Feature/ia tickets 27 72 user preferences (#92)
Browse files Browse the repository at this point in the history
Feat: Added user preferences page
  • Loading branch information
ialej001 authored Sep 7, 2022
1 parent 6fd618d commit 1c90936
Show file tree
Hide file tree
Showing 13 changed files with 971 additions and 15 deletions.
51 changes: 51 additions & 0 deletions src/components/Account/ControlPanel.tsx
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 src/components/Account/Preferences/ConfirmVehicleDeleteModal.tsx
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;
169 changes: 169 additions & 0 deletions src/components/Account/Preferences/NewVehicleModal.tsx
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;
115 changes: 115 additions & 0 deletions src/components/Account/Preferences/UserVehicles.tsx
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;
Loading

0 comments on commit 1c90936

Please sign in to comment.