Skip to content

Commit

Permalink
Merge pull request #164 from WT000/WT000-feature/#157-add-remove-dele…
Browse files Browse the repository at this point in the history
…gates

Add remove delegates from edit form
  • Loading branch information
WT000 committed Apr 18, 2023
2 parents d4f1bde + 528596d commit 70fe3e2
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 45 deletions.
6 changes: 3 additions & 3 deletions src/components/DelegatesListItem/DelegatesListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ export default function DelegatesListItem(props: DelegatesListItemProps) {
<div
className="relative h-11 w-11 md:h-12 md:w-12 rounded-full cursor-pointer flex justify-center items-center
bg-[#1D1A22] text-[rgb(255,255,255,0.0)] hover:text-[rgb(255,255,255,1)]
transition duration-500 ease-in-out hover:bg-orange hover:scale-110"
transition duration-500 ease-in-out hover:scale-110"
onClick={onClick}
>
<IoRemove className="" size="20px" />
{/* <IoRemove className="" size="20px" /> */}
<Image
className="rounded-full opacity-75 hover:opacity-25"
className="rounded-full opacity-75 hover:opacity-100"
src={image}
alt={username}
fill
Expand Down
16 changes: 9 additions & 7 deletions src/components/Notification/Notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import toast from "react-hot-toast";
interface NotificationProp {
icon: React.ReactElement;
text: string;
interactive?: { onClick: () => void; text: string };
interactive?: { onClick: () => void; text?: string };
duration?: number;
}

Expand Down Expand Up @@ -57,13 +57,15 @@ export default function Notification(props: NotificationProp) {
<IconButton icon={icon} parentToastId={t.id} />
)}
{interactive && (
<div className="grid grid-cols-2 gap-2 ml-auto">
<button
onClick={function(event){ interactive.onClick(); toast.dismiss(t.id);}}
className="inline-flex justify-center w-full text-base font-bold text-center text-white hover:scale-110 transition ease-in-out"
>
<div className={`grid grid-cols-${interactive.text ? "2" : "1"} gap-2 ml-auto`}>
{interactive.text && (
<button
onClick={function(event){ interactive.onClick(); toast.dismiss(t.id);}}
className="inline-flex justify-center w-full text-base font-bold text-center text-white hover:scale-110 transition ease-in-out"
>
{interactive.text}
</button>
</button>
)}
<div className="inline-flex justify-center">
<IconButton icon={icon} parentToastId={t.id} />
</div>
Expand Down
44 changes: 41 additions & 3 deletions src/components/forms/HomeForm/HomeForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IoBed, IoFlash, IoFootsteps, IoImages, IoPerson, IoSave, IoText, IoWallet, IoTrashBin } from "react-icons/io5";
import { IoBed, IoFlash, IoPerson, IoSave, IoText, IoWallet, IoTrashBin, IoMail } from "react-icons/io5";
import { Controller, SubmitHandler, useForm } from "react-hook-form";
import Button from "../../Button/Button";
import InputLayout from "../../layouts/InputLayout/InputLayout";
Expand All @@ -7,13 +7,13 @@ import Tile, { TileType } from "../../Tile/Tile";
import { useEffect, useState } from "react";
import Role from "../../../lib/utils/roles";
import PhotoInputLayout from "../../layouts/PhotoInputLayout/PhotoInputLayout";
import ImageLayout from "../../layouts/ImageLayout/ImageLayout";

export interface HomeFormData {
name: string;
owner: string;
description?: string;
image?: string;
delegates?: string;
numBeds: number;
energyInstructions: string;
energyTariff: number;
Expand Down Expand Up @@ -64,6 +64,24 @@ async function findUser(email: string, userFinder: (email: string) => Promise<bo
return false;
}

async function checkEmails(emails: string, userFinder: (email: string) => Promise<boolean>) {
if (emails !== "" && typeof emails == "string") {
// Detect if inputted as email1,email2 or email1, email2
let delegateEmails;
if (emails.match(", ")) {
delegateEmails = Array.from(new Set(emails.split(", ")));
} else {
delegateEmails = Array.from(new Set(emails.split(",")));
}

// Map, forEach, etc seem to be very buggy with react-hook-form
for (let i=0; i<delegateEmails.length; i++) {
if (!await findUser(delegateEmails[i], userFinder)) return false;
}
}
return true;
}

export default function HomeForm(props: HomeFormProps) {
const { onSubmit, onCancel, userFinder, isLoading, triggerReset, edit } = props;

Expand Down Expand Up @@ -169,7 +187,7 @@ export default function HomeForm(props: HomeFormProps) {

{/* Instructions */}
{/* May need a custom size set on md: breakpoint */}
<Tile tileType={TileType.fill} customClass="row-span-3" clickable={false}>
<Tile tileType={TileType.fill} customClass="row-span-2" clickable={false}>
<InstructionsLayout
text=""
editable={true}
Expand Down Expand Up @@ -227,6 +245,26 @@ export default function HomeForm(props: HomeFormProps) {
errorMessage={"*Must be at least £0.01."}
/>
</Tile>

{/* Delegates */}
<Tile tileType={TileType.input} clickable={false}>
<InputLayout
icon={<IoMail size="32px" />}
text={"Delegate Emails"}
type={"text"}
name={"delegates"}
placeholder={"abc@gmail.com, def@g..."}
register={register}
registerSettings={{
validate: {
real: (emails) => checkEmails(emails, userFinder),
},
}}
errors={errors.delegates}
errorMessage={"*Must be real and comma seperated."}
fontSize="md"
/>
</Tile>

<div className="flex flex-row justify-around w-100 md:w-[368px]">
<Button
Expand Down
13 changes: 9 additions & 4 deletions src/components/layouts/InputLayout/InputLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ interface InputLayoutProps {
placeholder: string;
currency?: boolean;
// Add HomeFormData | xFormData | yFormData in the future
register?: UseFormRegister<HomeFormData> | UseFormRegister<ReadingFormData> | UseFormRegister<BookingFormData> | UseFormRegister<GuestLoginFormData>;
register?:
| UseFormRegister<HomeFormData>
| UseFormRegister<ReadingFormData>
| UseFormRegister<BookingFormData>
| UseFormRegister<GuestLoginFormData>;
registerSettings?: object;
errors?: object;
errorMessage?: string;
onChange?: (e) => void;
disabled?: boolean;
fontSize?: string;
}

export default function InputLayout(props: InputLayoutProps) {
const { icon, text, type, name, placeholder, currency, register, registerSettings, errors, errorMessage, disabled } = props;
const {fontSize, icon, text, type, name, placeholder, currency, register, registerSettings, errors, errorMessage, disabled } = props;

return (
<div className="grid grid-cols-5 m-auto w-full">
Expand All @@ -36,8 +41,8 @@ export default function InputLayout(props: InputLayoutProps) {
onChange={(e) => {
props?.onChange && props.onChange(e);
}}
className="text-lg placeholder:text-black font-bold bg-transparent w-[95%] disabled:bg-white
file:border-none file:bg-transparent file:cursor-pointer focus:outline-none focus:placeholder:text-black-500"
className={`text-${fontSize ? fontSize : "lg"} placeholder:text-black font-bold bg-transparent w-[95%] disabled:bg-white
file:border-none file:bg-transparent file:cursor-pointer focus:outline-none focus:placeholder:text-black-500 truncate ...`}
type={type}
name={name}
placeholder={placeholder}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function display(text: string, editable: boolean, register?: UseFormRegister<Hom
}
return (
<textarea
className="h-32 placeholder:text-black text-xs w-full md:text-base bg-transparent
className="h-20 placeholder:text-black text-xs w-full md:text-base bg-transparent
file:border-none file:bg-transparent file:cursor-pointer resize-none"
name="energyInstructions"
placeholder={placeholder}
Expand Down
91 changes: 72 additions & 19 deletions src/pages/api/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import getRole from "../../lib/utils/getRole";
import Role from "../../lib/utils/roles";
import { countDecimal } from "../../components/forms/HomeForm/HomeForm";

function getDelegateEmails(delegates) {
if (typeof delegates == "string" && delegates.match(", ")) {
return Array.from(new Set(delegates.split(", ")));
} else if (typeof delegates == "string") {
return Array.from(new Set(delegates.split(",")));
} else {
return delegates;
}
}

// Form error function, so database connections are not needed to check for simple errors
function checkFormErrors(home, method): boolean {
// Check name
Expand All @@ -22,6 +32,21 @@ function checkFormErrors(home, method): boolean {
)
return false;

// Check delegates
if (home.delegates) {
let delegateEmails = getDelegateEmails(home.delegates);

delegateEmails.forEach((email) => {
if (
!email.match(
"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
)
) {
return false;
}
});
}

// Check numBeds
if (!home.numBeds || typeof home.numBeds !== "number" || home.numBeds <= 0 || !Number.isInteger(home.numBeds)) return false;

Expand Down Expand Up @@ -53,7 +78,9 @@ export default async function handler(req, res) {
let valid;
let role;
let owner;

let delegateEmails;
let delegates;

switch (method) {
case "POST":
if (getRole(session) != Role.Agency) {
Expand All @@ -67,20 +94,34 @@ export default async function handler(req, res) {
return res.status(400).json({ success: false });
}

owner = await User.findOne({ email: home.owner });
if (!owner) {
return res.status(400).json({ success: false });
}

console.log(`Creating home ${home.name}`);

owner = await User.findOne({email: home.owner});
// Find unique delegate emails, where the email does not equal the home owners email
delegateEmails = getDelegateEmails(home.delegates);
delegates = await User.find({
$and: [
{email: { $ne: owner.email }},
{email: { $in: delegateEmails }}
]
})

const newHome = await Home.create({
name: home.name,
owner: owner._id,
delegates: delegates,
numBeds: home.numBeds,
energyInstructions: home.energyInstructions,
energyTariff: home.energyTariff,
energyBuffer: home.energyBuffer,
image: home.image
})
image: home.image,
});

return res.json({success: true, id: newHome._id});
return res.json({ success: true, id: newHome._id });

case "PUT":
home = req.body;
Expand All @@ -90,51 +131,63 @@ export default async function handler(req, res) {
return res.status(400).json({ success: false });
}

homedb = await Home.findOne({_id: home._id});
homedb = await Home.findOne({ _id: home._id });
role = getRole(session, homedb);

if (role != Role.Agency && role != Role.Homeowner) {
return res.status(401).json({ success: false });
}

owner = await User.findOne({ email: home.owner });
if (!owner) {
return res.status(400).json({ success: false });
}

delegateEmails = getDelegateEmails(home.delegates);
delegates = await User.find({
$and: [
{email: { $ne: owner.email }},
{email: { $in: delegateEmails }}
]
})

console.log(`Editing home ${homedb.name}`);

owner = await User.findOne({email: home.owner});
const editHome = await Home.findByIdAndUpdate(homedb._id, {
name: home.name,
owner: role == Role.Agency ? owner._id : homedb.owner,
delegates: delegates,
numBeds: home.numBeds,
energyInstructions: home.energyInstructions,
energyTariff: home.energyTariff,
energyBuffer: home.energyBuffer,
image: home.image
})
if (editHome) return res.json({success: true, id: homedb._id});
image: home.image,
});
if (editHome) return res.json({ success: true, id: homedb._id });
break;

case "DELETE":
if (getRole(session) != Role.Agency) {
return res.status(400).json({ success: false });
}

const homeId = req.query.id;
homedb = await Home.findOne({_id: homeId});
homedb = await Home.findOne({ _id: homeId });

if (!homedb) {
return res.status(404).json({success: false});
return res.status(404).json({ success: false });
}

console.log(`Deleting home ${homedb.name}`);

const homeDelete = await Home.findByIdAndUpdate(homedb._id, {
isDeleted: true
isDeleted: true,
});
if (homeDelete) return res.json({success: true});
if (homeDelete) return res.json({ success: true });
break;
}

res.status(401).json({ success: false });

} catch (e) {
console.log(e);
res.status(500).json({ error: e.message });
Expand Down
11 changes: 8 additions & 3 deletions src/pages/homes/[id]/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import axios from "axios";
import User from "../../../db/models/User";

export default function EditHome(props) {
const { userSession, home, role } = props;
const { userSession, homeJSON, role } = props;

// Parse a bit of JSON here, as delegate lists need their emails extracted
const home = JSON.parse(homeJSON);
home.owner = home.owner.email;
home.delegates = home.delegates.map(obj => obj.email).join(", ");

const navItems = [
{
Expand Down Expand Up @@ -121,7 +126,7 @@ export async function getServerSideProps(context) {
const session = await getServerSession(context.req, context.res, authOptions);

// Find the home to be edited
const home = await Home.findOne({_id: context.params.id}).populate("owner", "email", User);
const home = await Home.findOne({_id: context.params.id}).populate("owner", "email", User).populate("delegates", "-_id email", User);
const role = getRole(session, home);

if (role != Role.Agency && role != Role.Homeowner) {
Expand Down Expand Up @@ -169,7 +174,7 @@ export async function getServerSideProps(context) {
bookingsLast12Months: (await bookingsLast12MonthsTask).toString(),
},
homes: homes.map((x) => ToSeriable(x)),
home: ToSeriable(home, "email"),
homeJSON: JSON.stringify(home),
userSession: session,
role: role,
},
Expand Down
Loading

0 comments on commit 70fe3e2

Please sign in to comment.