Skip to content

Commit

Permalink
WEB-22: Add confirmation popup
Browse files Browse the repository at this point in the history
  • Loading branch information
vinnie4k committed Dec 12, 2024
1 parent 6adae61 commit a61c327
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 126 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": {
"@nextui-org/date-picker": "^2.1.8",
"@nextui-org/theme": "^2.2.11",
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-progress": "^1.1.0",
Expand Down
4 changes: 2 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className="bg-other-background">
{children}
<body className={`${sfProDisplay.variable} font-sans bg-other-background`}>
<main>{children}</main>
<Toaster />
</body>
</html>
Expand Down
171 changes: 95 additions & 76 deletions src/components/announcement/announcementForm.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import CrossThinIcon from "@/icons/crossThinIcon";
import SpeakerIcon from "@/icons/speakerIcon";
import { Announcement } from "@/models/announcement";
import { DateFormat } from "@/models/enums/dateFormat";
import { useUserStore } from "@/stores/useUserStore";
import { formatDate } from "@/utils/utils";
import { addDays } from "date-fns";
import { Speaker, X } from "lucide-react";
import { useMemo, useState } from "react";
import AlertPopup from "../system/alertPopup";
import ButtonPrimary1 from "../system/button/buttonPrimary1";
import errorToast from "../system/errorToast";
import InputDatePicker from "../system/input/inputDatePicker";
Expand Down Expand Up @@ -39,6 +41,8 @@ export default function AnnouncementForm({ onClose, editingAnnouncement }: Props
const { user } = useUserStore();

const [announcement, setAnnouncement] = useState<Announcement>(editingAnnouncement ?? dummyAnnouncement);
const [showAlert, setShowAlert] = useState<boolean>(false);

const handleChange = (field: keyof Announcement, value: any) => {
setAnnouncement((prev) => ({ ...prev, [field]: value }));
};
Expand Down Expand Up @@ -70,88 +74,103 @@ export default function AnnouncementForm({ onClose, editingAnnouncement }: Props
};

return (
<div className="fixed inset-0 bg-black/80 flex justify-center items-center z-50">
<div className="flex flex-col gap-6 p-6 rounded-lg bg-neutral-white overflow-auto h-4/5 w-full mx-4 md:mx-8 lg:w-[1128px]">
{/* Header */}
<div className="flex flex-row justify-between items-start gap-8">
<div className="flex flex-row items-center gap-4">
<SpeakerIcon className="size-[40px] fill-neutral-800" />
<div className="flex flex-col gap-1">
<h4 className="text-neutral-800">Create Announcement</h4>
<p className="b1 text-neutral-600">Schedule an announcement to our apps with this form.</p>
<>
{/* Alert */}
<AlertPopup
title="Schedule Announcement"
description={`Are you sure you want to schedule this announcement from ${formatDate(
new Date(announcement.startDate),
DateFormat.SHORT
)} to ${formatDate(new Date(announcement.endDate), DateFormat.SHORT)}?`}
actionText="Schedule"
action={scheduleAnnouncement}
open={showAlert}
onOpenChange={(val) => setShowAlert(val)}
/>

<div className="fixed inset-0 bg-black/80 flex justify-center items-center z-50">
<div className="flex flex-col gap-6 p-6 rounded-lg bg-neutral-white overflow-auto h-4/5 w-full mx-4 md:mx-8 lg:w-[1128px]">
{/* Header */}
<div className="flex flex-row justify-between items-start gap-8">
<div className="flex flex-row items-center gap-4">
<Speaker className="size-[40px] stroke-neutral-800" />
<div className="flex flex-col gap-1">
<h4 className="text-neutral-800">Create Announcement</h4>
<p className="b1 text-neutral-600">Schedule an announcement to our apps with this form.</p>
</div>
</div>
<button onClick={onClose}>
<X className="size-[32px] stroke-neutral-400 opacity-hover" />
</button>
</div>
<button onClick={onClose}>
<CrossThinIcon className="size-[32px] fill-neutral-400 opacity-hover" />
</button>
</div>

<div className="flex flex-col gap-6 lg:flex-row-reverse">
{/* Preview */}
<div className="flex flex-col justify-center items-center rounded-md border-[1px] border-other-stroke bg-other-offWhite p-8 flex-1">
<AnnouncementBanner announcement={announcement}></AnnouncementBanner>
</div>
<div className="flex flex-col gap-6 lg:flex-row-reverse">
{/* Preview */}
<div className="flex flex-col justify-center items-center rounded-md border-[1px] border-other-stroke bg-other-offWhite p-8 flex-1">
<AnnouncementBanner announcement={announcement}></AnnouncementBanner>
</div>

{/* Form */}
<div className="flex flex-col gap-6 lg:w-[400px]">
<InputText
name="Title"
placeholder="Enter the announcement title"
value={announcement.title}
onChange={(event) => handleChange("title", event.target.value)}
/>
<InputText
name="Description"
placeholder="Enter the announcement description"
value={announcement.body}
onChange={(event) => handleChange("body", event.target.value)}
/>
<div className="flex flex-col gap-2">
<h6 className="text-neutral-800">Date</h6>
<InputDatePicker
value={{
from: new Date(announcement.startDate),
to: new Date(announcement.endDate),
}}
setDateRange={(dateRange) => {
handleChange("startDate", dateRange?.from ?? "");
handleChange("endDate", dateRange?.to ?? "");
}}
{/* Form */}
<div className="flex flex-col gap-6 lg:w-[400px]">
<InputText
name="Title"
placeholder="Enter the announcement title"
value={announcement.title}
onChange={(event) => handleChange("title", event.target.value)}
/>
</div>
<InputText
name="Link"
placeholder="Enter the announcement link"
value={announcement.link}
onChange={(event) => handleChange("link", event.target.value)}
/>
<div className="flex flex-col gap-2">
<h6 className="text-neutral-800">Apps</h6>
<InputMultiSelect value={announcement.apps} setValues={(apps) => handleChange("apps", apps)} />
</div>
<div className="flex flex-col gap-2">
<h6 className="text-neutral-800">Upload Image</h6>
<InputUpload setUrl={(url) => handleChange("imageUrl", url)} />
</div>
<InputText
name="Description"
placeholder="Enter the announcement description"
value={announcement.body}
onChange={(event) => handleChange("body", event.target.value)}
/>
<div className="flex flex-col gap-2">
<h6 className="text-neutral-800">Date</h6>
<InputDatePicker
value={{
from: new Date(announcement.startDate),
to: new Date(announcement.endDate),
}}
setDateRange={(dateRange) => {
handleChange("startDate", dateRange?.from ?? "");
handleChange("endDate", dateRange?.to ?? "");
}}
/>
</div>
<InputText
name="Link"
placeholder="Enter the announcement link"
value={announcement.link}
onChange={(event) => handleChange("link", event.target.value)}
/>
<div className="flex flex-col gap-2">
<h6 className="text-neutral-800">Apps</h6>
<InputMultiSelect value={announcement.apps} setValues={(apps) => handleChange("apps", apps)} />
</div>
<div className="flex flex-col gap-2">
<h6 className="text-neutral-800">Upload Image</h6>
<InputUpload setUrl={(url) => handleChange("imageUrl", url)} />
</div>

{/* Submit Button */}
<ButtonPrimary1
text="Schedule Announcement"
action={scheduleAnnouncement}
disabled={!canSchedule}
className="lg:hidden"
/>
{/* Submit Button */}
<ButtonPrimary1
text="Schedule Announcement"
action={scheduleAnnouncement}
disabled={!canSchedule}
className="lg:hidden"
/>
</div>
</div>
</div>

{/* Submit Button */}
<ButtonPrimary1
text="Schedule Announcement"
action={scheduleAnnouncement}
disabled={!canSchedule}
className="max-lg:hidden w-full"
/>
{/* Submit Button */}
<ButtonPrimary1
text="Schedule Announcement"
action={() => setShowAlert(true)}
disabled={!canSchedule}
className="max-lg:hidden w-full"
/>
</div>
</div>
</div>
</>
);
}
130 changes: 90 additions & 40 deletions src/components/announcement/announcementModal.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import AppIcon from "@/icons/appIcon";
import CrossThinIcon from "@/icons/crossThinIcon";
import { Announcement } from "@/models/announcement";
import { DateFormat } from "@/models/enums/dateFormat";
import { useUserStore } from "@/stores/useUserStore";
import { dateInRange, formatDate } from "@/utils/utils";
import { X } from "lucide-react";
import { useState } from "react";
import AlertPopup from "../system/alertPopup";
import ButtonPrimary2 from "../system/button/buttonPrimary2";
import ButtonSecondary2 from "../system/button/buttonSecondary2";
import errorToast from "../system/errorToast";
import AnnouncementBanner from "./announcementBanner";
import AnnouncementIndicator from "./announcementIndicator";

Expand All @@ -14,55 +18,101 @@ interface AnnouncementModalProps {
}

export default function AnnouncementModal({ onClose, announcement }: AnnouncementModalProps) {
const { user } = useUserStore();

const [showDeleteAlert, setShowDeleteAlert] = useState<boolean>(false);
const [showEndAlert, setShowEndAlert] = useState<boolean>(false);

if (!announcement) return null;

// End Announcement
const endAnnouncement = async () => {
if (!user) return;

try {
console.log("Ending", announcement);
} catch (err) {
console.error(err);
errorToast();
}
};

// Delete Announcement
const deleteAnnouncement = async () => {
if (!user) return;

try {
console.log("Deleting", announcement);
} catch (err) {
console.error(err);
errorToast();
}
};

return (
<div className="fixed inset-0 bg-black/80 flex justify-center items-center z-50">
<div className="bg-neutral-white rounded-lg p-8 max-md:w-full m-4 md:m-8 lg:max-w-[1128px]">
<div className="flex flex-col gap-4 md:gap-6">
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-1">
<div className="flex flex-row items-center justify-between gap-1">
<h4 className="text-neutral-800 break-all">{announcement.title}</h4>
<button className="h-[24px] w-[24px] fill-neutral-400 opacity-hover" onClick={onClose}>
<CrossThinIcon />
</button>
<>
{/* Alerts */}
<AlertPopup
title="End Announcement"
description={"Are you sure you want to end this live announcement?"}
actionText="End"
action={endAnnouncement}
open={showEndAlert}
onOpenChange={(val) => setShowEndAlert(val)}
/>
<AlertPopup
title="Delete Announcement"
description={"Are you sure you want to delete this announcement? This cannot be undone."}
actionText="Delete"
action={deleteAnnouncement}
open={showDeleteAlert}
onOpenChange={(val) => setShowDeleteAlert(val)}
/>

<div className="fixed inset-0 bg-black/80 flex justify-center items-center z-50">
<div className="bg-neutral-white rounded-lg p-8 max-md:w-full m-4 md:m-8 lg:max-w-[1128px]">
<div className="flex flex-col gap-4 md:gap-6">
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-1">
<div className="flex flex-row items-center justify-between gap-1">
<h4 className="text-neutral-800 break-all">{announcement.title}</h4>
<button className="size-[24px] opacity-hover" onClick={onClose}>
<X className="size-[24px] fill-neutral-400" />
</button>
</div>
<p className="b1 text-neutral-600">
{`${formatDate(new Date(announcement.startDate), DateFormat.SHORT)} - ${formatDate(
new Date(announcement.endDate),
DateFormat.SHORT
)}`}
</p>
</div>
<div className="flex flex-row items-center gap-2">
<img src={announcement.creator.imageUrl} className="size-[24px] rounded-xl" />
<p className="b2 text-neutral-400">{`Scheduled by ${announcement.creator.name}`}</p>
</div>
<p className="b1 text-neutral-600">
{`${formatDate(new Date(announcement.startDate), DateFormat.SHORT)} - ${formatDate(
new Date(announcement.endDate),
DateFormat.SHORT
)}`}
</p>
</div>
<div className="flex flex-row items-center gap-2">
<img src={announcement.creator.imageUrl} className="size-[24px] rounded-xl" />
<p className="b2 text-neutral-400">{`Scheduled by ${announcement.creator.name}`}</p>
<div className="flex flex-col md:flex-row gap-4 items-left md:justify-between">
<div className="flex flex-row items-center gap-2">
{announcement.apps.map((app) => (
<AppIcon key={app} appName={app} className="rounded-sm size-[32px]" />
))}
</div>
<AnnouncementIndicator announcement={announcement} />
</div>
</div>
<div className="flex flex-col md:flex-row gap-4 items-left md:justify-between">
<div className="flex flex-row items-center gap-2">
{announcement.apps.map((app) => (
<AppIcon key={app} appName={app} className="rounded-sm size-[32px]" />
))}

<div className="flex flex-col p-4 justify-center items-center rounded-md border border-other-stroke bg-other-offWhite">
<AnnouncementBanner announcement={announcement} />
</div>
<AnnouncementIndicator announcement={announcement} />
</div>

<div className="flex flex-col p-4 justify-center items-center rounded-md border border-other-stroke bg-other-offWhite">
<AnnouncementBanner announcement={announcement} />
{dateInRange(new Date(announcement.startDate), new Date(announcement.endDate), new Date()) ? (
<ButtonPrimary2 text="End Live Announcement" action={() => setShowEndAlert(true)} />
) : (
<ButtonSecondary2 text="Delete Announcement" action={() => setShowDeleteAlert(true)} />
)}
</div>

{dateInRange(new Date(announcement.startDate), new Date(announcement.endDate), new Date()) ? (
<ButtonPrimary2
text="End Live Announcement"
action={() => console.log("End Live Announcement button tapped")}
/>
) : (
<ButtonSecondary2 text="Delete Announcement" action={() => console.log("Delete button tapped")} />
)}
</div>
</div>
</div>
</>
);
}
Loading

0 comments on commit a61c327

Please sign in to comment.