Skip to content

Commit

Permalink
feat: import parsed attendees to db dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
hyamero committed Aug 20, 2024
1 parent 2eff706 commit 6707482
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 140 deletions.
61 changes: 4 additions & 57 deletions src/app/event-setup/components/add-participants.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,14 @@
"use client";

import { toast } from "sonner";
import { nanoid } from "nanoid";
import React, { useState } from "react";
import { db } from "@/config/firebase";
import { Attendee } from "@/lib/types";
import { doc, setDoc } from "firebase/firestore";

import AdminPermDialog from "./admin-perm-dialog";
import { useParams } from "next/navigation";
import { XlsxForm } from "./xlsx-form";
import React from "react";
import JsonForm from "./json-form";
import { XlsxForm } from "./xlsx-form";
import ImportParticipantsDialog from "./import-participants-dialog";

export default function AddParticipants() {
const [attendees, setAttendees] = useState("");
const [parsedAttendees, setParsedAttendees] = useState<Attendee[]>([]);
const [loading, setLoading] = useState(false);
const [openAdminModal, setAdminModal] = useState(false);

const params = useParams<{ name: string }>();

const handleImport = () => {
setLoading(true);
if (parsedAttendees.length === 0) {
toast.error("No attendees to import");
return;
}

try {
parsedAttendees.forEach(async (attendee) => {
try {
await setDoc(
doc(db, `${params.name}/data/certificates`, nanoid(10)),
{
firstName: attendee.firstName,
lastName: attendee.lastName,
email: attendee.email,
},
);
} catch (err: any) {
toast.error(err.message);
}
});

setTimeout(() => {
setParsedAttendees([]);
setAttendees("");
}, 500);

toast.success("Certificates added");
} catch (err: any) {
toast.error(err.message);
}
setLoading(false);
};

return (
<div className="relative z-10 flex flex-col gap-16">
<AdminPermDialog
openAdminModal={openAdminModal}
setAdminModal={setAdminModal}
handleSubmit={handleImport}
/>
<ImportParticipantsDialog />
<XlsxForm />
<div className="flex items-center gap-3 text-center text-muted-foreground">
<div className="h-px w-full bg-muted" />
Expand Down
122 changes: 122 additions & 0 deletions src/app/event-setup/components/import-participants-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"use client";

import { toast } from "sonner";
import { nanoid } from "nanoid";
import { db } from "@/config/firebase";
import React, { useState } from "react";
import { useParams } from "next/navigation";
import { doc, setDoc } from "firebase/firestore";
import AdminPermDialog from "./admin-perm-dialog";
import { useAttendeesStore } from "@/hooks/use-attendees-store";

import { AlertDialog, AlertDialogContent } from "@/components/ui/alert-dialog";

import { Icons } from "@/components/icons";
import { Button } from "@/components/ui/button";
import { useDialogStore } from "@/hooks/use-dialog-store";

export default function ImportParticipantsDialog() {
const params = useParams<{ name: string }>();

const [openAdminModal, setAdminModal] = useState(false);
const [loading, setLoading] = useState(false);

const { importDialog, setImportDialog } = useDialogStore();
const { parsedAttendees, setParsedAttendees } = useAttendeesStore();

const handleImport = () => {
setLoading(true);
if (parsedAttendees.length === 0) {
toast.error("No attendees to import");
return;
}

try {
parsedAttendees.forEach(async (attendee) => {
try {
await setDoc(
doc(db, `${params.name}/data/certificates`, nanoid(10)),
{
firstName: attendee.firstName,
lastName: attendee.lastName,
email: attendee.email,
},
);
} catch (err: any) {
toast.error(err.message);
}
});

setTimeout(() => {
setParsedAttendees([]);
}, 500);

toast.success("Certificates added");
} catch (err: any) {
toast.error(err.message);
}
setLoading(false);
};

const cancelImport = () => {
setImportDialog(false);
setParsedAttendees([]);
};

return (
<>
<AdminPermDialog
openAdminModal={openAdminModal}
setAdminModal={setAdminModal}
handleSubmit={handleImport}
/>

<AlertDialog open={importDialog} onOpenChange={setImportDialog}>
<AlertDialogContent>
{parsedAttendees.length > 0 && (
<>
<p className="text-sm text-muted-foreground">
Total certificates to be added: {parsedAttendees.length}
</p>
<div className="mt-4 max-h-[300px] overflow-y-scroll rounded border px-6">
{parsedAttendees.map((m) => {
return (
<div
key={nanoid()}
className="flex space-x-8 border-b py-6 text-sm last-of-type:border-b-0"
>
<div className="[&>*]:text-muted-foreground">
<p>Email:</p>
<p>First Name:</p>
<p>Last Name:</p>
</div>
<div>
<p>{m.email}</p>
<p>{m.firstName}</p>
<p>{m.lastName}</p>
</div>
</div>
);
})}
</div>

<div className="flex flex-col gap-2">
<Button
disabled={loading}
type="button"
className="mt-4 w-full"
onClick={() => setAdminModal(!openAdminModal)}
>
{!loading ? "Import Certificates" : <Icons.spinner />}
</Button>
<Button variant="outline" onClick={cancelImport}>
Cancel
</Button>
</div>
</>
)}
</AlertDialogContent>
</AlertDialog>
</>
);
}
114 changes: 40 additions & 74 deletions src/app/event-setup/components/json-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@

import { z } from "zod";
import { toast } from "sonner";
import { nanoid } from "nanoid";
import React, { useState } from "react";
import { Attendee } from "@/lib/types";

import { Separator } from "@/components/ui/separator";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Icons } from "@/components/icons";
import { useDialogStore } from "@/hooks/use-dialog-store";
import { useAttendeesStore } from "@/hooks/use-attendees-store";

export default function JsonForm() {
const [attendees, setAttendees] = useState("");
const [parsedAttendees, setParsedAttendees] = useState<Attendee[]>([]);
const [loading, setLoading] = useState(false);
const [openAdminModal, setAdminModal] = useState(false);

const { setParsedAttendees } = useAttendeesStore();
const { setImportDialog } = useDialogStore();

const handleParse = () => {
setLoading(true);
Expand Down Expand Up @@ -43,27 +44,29 @@ export default function JsonForm() {

toast.success("Parsed successfully");
setParsedAttendees(_parsedAttendees);
setImportDialog(true);
} catch {
toast.error("Invalid JSON format");
}
setLoading(false);
};

return (
<form className="mx-auto flex w-full max-w-screen-sm">
<div className="flex w-full flex-col gap-5">
<div>
<h3 className="text-lg font-medium">Import via JSON</h3>
<p className="text-sm text-muted-foreground">
<span className="font-semibold text-destructive">Strict: </span>
Make sure to follow the same JSON Format
</p>
</div>
<Separator />
<p className="text-sm text-muted-foreground">Sample JSON</p>
<pre className="bg-bg rounded border px-8 py-4 text-orange-600 dark:text-yellow-500">
<code>
{`[
<>
<form className="mx-auto flex w-full max-w-screen-sm">
<div className="flex w-full flex-col gap-5">
<div>
<h3 className="text-lg font-medium">Import via JSON</h3>
<p className="text-sm text-muted-foreground">
<span className="font-semibold text-destructive">Strict: </span>
Make sure to follow the same JSON Format
</p>
</div>
<Separator />
<p className="text-sm text-muted-foreground">Sample JSON</p>
<pre className="bg-bg rounded border px-8 py-4 text-orange-600 dark:text-yellow-500">
<code>
{`[
{
"firstName": "John",
"lastName": "Doe",
Expand All @@ -75,65 +78,28 @@ export default function JsonForm() {
"email": "sally@smith.com"
}
]`}
</code>
</pre>
</code>
</pre>

<div>
<Textarea
placeholder="Paste JSON"
className="max-h-[350px] min-h-[150px]"
value={attendees}
onChange={(e) => setAttendees(e.target.value)}
/>
<div>
<Textarea
placeholder="Paste JSON"
className="max-h-[350px] min-h-[150px]"
value={attendees}
onChange={(e) => setAttendees(e.target.value)}
/>

<Button
disabled={loading}
type="button"
className="mt-2 w-full"
onClick={handleParse}
>
Parse Data
</Button>
</div>
</div>

{parsedAttendees.length > 0 && (
<div className="mt-8">
<p className="text-sm text-muted-foreground">
Total certificates to be added: {parsedAttendees.length}
</p>
<div className="mt-4 max-h-[300px] overflow-y-scroll rounded border px-6">
{parsedAttendees.map((m) => {
return (
<div
key={nanoid()}
className="flex space-x-8 border-b py-6 text-sm last-of-type:border-b-0"
>
<div className="[&>*]:text-muted-foreground">
<p>Email:</p>
<p>First Name:</p>
<p>Last Name:</p>
</div>
<div>
<p>{m.email}</p>
<p>{m.firstName}</p>
<p>{m.lastName}</p>
</div>
</div>
);
})}
<Button
disabled={loading}
type="button"
className="mt-2 w-full"
onClick={handleParse}
>
Parse Data
</Button>
</div>

<Button
disabled={loading}
type="button"
className="mt-4 w-full"
onClick={() => setAdminModal(!openAdminModal)}
>
{!loading ? "Import Certificates" : <Icons.spinner />}
</Button>
</div>
)}
</form>
</form>
</>
);
}
Loading

0 comments on commit 6707482

Please sign in to comment.