Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/ia tickets 27 72 user preferences #92

Merged
merged 27 commits into from
Sep 7, 2022
Merged
Changes from 23 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
35bc92b
Added layout and dash control panel
ialej001 Sep 3, 2022
d325953
Merge branch 'development' into feature/ia-tickets-27-72-user-prefere…
ialej001 Sep 4, 2022
32121e2
added preferences router and procedures
ialej001 Sep 5, 2022
8183c7b
merged preferences router into trpc
ialej001 Sep 5, 2022
cce611d
added preferences query and fixed pref mutation
ialej001 Sep 5, 2022
40c89ad
location prefs load on render and now update db
ialej001 Sep 5, 2022
ca26e76
Merge branch 'development' into feature/ia-tickets-27-72-user-prefere…
ialej001 Sep 6, 2022
83b3614
fixed typo
ialej001 Sep 6, 2022
f710f9c
unit pref save/load on render
ialej001 Sep 6, 2022
c5f6e04
form close/cancel resets, save persists to db
ialej001 Sep 6, 2022
4527cbd
removed head element
ialej001 Sep 6, 2022
fb83aef
feat: set primary vehicle
ialej001 Sep 6, 2022
b25f439
fixed trpc endpoint
ialej001 Sep 6, 2022
5debada
primary button now rerenders on click
ialej001 Sep 6, 2022
34fd602
vehicles refetch on new vehicle submit
ialej001 Sep 6, 2022
f752e52
cleaned up modal appearance
ialej001 Sep 6, 2022
f95f67b
feat: delete car and remove primaryId if required
ialej001 Sep 6, 2022
f601671
updated vehicle tile styles
ialej001 Sep 6, 2022
9ac8a50
styling: added responsiveness
ialej001 Sep 6, 2022
1a9a9a3
moved preferences back into /account
ialej001 Sep 6, 2022
82045f0
added missing unauth error throw
ialej001 Sep 6, 2022
28069d8
updated styling
ialej001 Sep 6, 2022
b9e052d
render empty message on no saved cars
ialej001 Sep 6, 2022
b0ffc8e
added location save feedback
ialej001 Sep 7, 2022
b07f8bd
added unit preference saved feedback
ialej001 Sep 7, 2022
e3d86b2
updated logout button style and function
ialej001 Sep 7, 2022
6826a3f
redirects back to login with no session
ialej001 Sep 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/components/Account/ControlPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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;

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={() => signOut()} size={buttonSize} />
</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>
);
ialej001 marked this conversation as resolved.
Show resolved Hide resolved

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