Skip to content

Commit

Permalink
feat: persiapan untuk membuat unit vote processor
Browse files Browse the repository at this point in the history
  • Loading branch information
reacto11mecha committed Jul 2, 2024
1 parent 578c150 commit a665e05
Show file tree
Hide file tree
Showing 19 changed files with 622 additions and 211 deletions.
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ DB_PASSWORD=''
# node -e 'console.log(require("crypto").randomBytes(50).toString("base64"));'
# @see https://next-auth.js.org/configuration/options#secret
AUTH_SECRET='supersecret'

# RabbitMQ
# This env variable will connect to rabbitmq instance
PROCESSOR_AMQP_URL="amqp://localhost"

# API Endpoint
# This env variable will tell the vote processor where is the trpc endpoint
PROCESSOR_API_URL="http://localhost:3000/api/trpc"
14 changes: 9 additions & 5 deletions apps/clients/chooser/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useState } from "react";
import { ParticipantProvider } from "@/context/participant-context";
import { ServerSettingProvider } from "@/context/server-setting";
import MainPage from "@/routes/main-page";
import VotePage from "@/routes/vote-page";
import { api } from "@/utils/api";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/client";
Expand All @@ -15,8 +17,8 @@ const router = createBrowserRouter([
element: <MainPage />,
},
{
path: "about",
element: <div>About</div>,
path: "vote",
element: <VotePage />,
},
]);

Expand Down Expand Up @@ -45,9 +47,11 @@ export default function App() {
return (
<api.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<ServerSettingProvider>
<RouterProvider router={router} />
</ServerSettingProvider>
<ParticipantProvider>
<ServerSettingProvider>
<RouterProvider router={router} />
</ServerSettingProvider>
</ParticipantProvider>
</QueryClientProvider>
</api.Provider>
);
Expand Down
171 changes: 30 additions & 141 deletions apps/clients/chooser/src/components/scanner/index.tsx
Original file line number Diff line number Diff line change
@@ -1,169 +1,58 @@
import { useCallback, useState } from "react";
import { useParticipant } from "@/context/participant-context";
import { api } from "@/utils/api";
import { successTimeoutAtom } from "@/utils/atom";
import { motion } from "framer-motion";
import { useAtomValue } from "jotai";
import { Loader, RotateCcw } from "lucide-react";
import { Navigate } from "react-router-dom";

import { Button } from "@sora-vp/ui/button";
import { Separator } from "@sora-vp/ui/separator";

import { UniversalError } from "../universal-error";
import { UniversalLoading } from "../universal-loading";
import { MainScanner } from "./main-scanner";

export function ScannerComponent() {
const { qrId, setQRCode } = useParticipant();

const [isQrInvalid, setInvalidQr] = useState(false);
const successTimeout = useAtomValue(successTimeoutAtom);

const participantAttend = api.clientConsumer.participantAttend.useMutation({
onSuccess() {
setTimeout(() => participantAttend.reset(), successTimeout);
},
});
const participantAttended =
api.clientConsumer.checkParticipantAttended.useMutation({
onSuccess() {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
setQRCode(participantAttended.variables!);
},
});

const setIsQrValid = useCallback(
(invalid: boolean) => setInvalidQr(invalid),
[],
);
const mutateData = useCallback(
(qrId: string) => participantAttend.mutate(qrId),
[participantAttend],
(qrId: string) => participantAttended.mutate(qrId),
[participantAttended],
);

if (participantAttend.isPending)
return (
<motion.div
initial={{ opacity: 0, x: "-250px" }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: "-250px" }}
className="flex h-screen w-screen flex-col items-center justify-center"
>
<Loader
size={78}
strokeWidth={1.5}
className="animate-pulse animate-spin"
/>
if (qrId) return <Navigate to="/vote" />;

<div>
<h3 className="mt-8 scroll-m-20 text-3xl font-semibold tracking-tight">
Mengirim Data Kehadiran...
</h3>
<p className="text-xl font-light leading-7">
Mohon tunggu proses ini sampai selesai.
</p>
</div>
</motion.div>
);

if (participantAttend.isSuccess)
if (participantAttended.isPending)
return (
<div className="flex h-screen w-screen flex-col items-center justify-center gap-3 bg-green-600 p-6">
<div className="w-[80%] text-center">
<motion.h1
initial={{ opacity: 0, y: -25 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -25 }}
transition={{
duration: 0.3,
}}
className="scroll-m-20 font-mono text-4xl font-extrabold tracking-tight text-red-100 lg:text-5xl"
>
Berhasil Mengisi Kehadiran!
</motion.h1>
<motion.p
initial={{ opacity: 0, y: -25 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -25 }}
transition={{
duration: 0.3,
delay: 0.2,
}}
className="text-center text-xl leading-7 text-red-100/90 [&:not(:first-child)]:mt-4"
>
Silahkan menuju ke komputer pemilihan dan gunakan hak suara anda.
</motion.p>
</div>
<Separator className="w-[60%]" />
<motion.div
initial={{ opacity: 0, y: -25 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -25 }}
transition={{
duration: 0.3,
delay: 0.4,
}}
className="flex gap-3"
>
<div className="select-none">
<pre className="mb-[-10px] font-mono">
{participantAttend.data.qrId.slice(0, 5)}
</pre>
<pre className="font-mono">
{participantAttend.data.qrId.slice(5, 10)}
</pre>
<pre className="mt-[-10px] font-mono">
{participantAttend.data.qrId.slice(10, 15)}
</pre>
</div>
<Separator orientation="vertical" />
<div>
<p className="text-lg">{participantAttend.data.name}</p>
<span className="font-mono">{participantAttend.data.subpart}</span>
</div>
</motion.div>
</div>
<UniversalLoading
title="Mengambil data status anda..."
description="Mohon tunggu, selanjutnya anda dapat memilih."
/>
);

if (isQrInvalid || participantAttend.isError)
if (isQrInvalid || participantAttended.isError)
return (
<div className="flex h-screen w-screen flex-col items-center justify-center gap-5 bg-red-600 p-6">
<div className="w-[80%] text-center">
<motion.h1
initial={{ opacity: 0, x: 25 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 25 }}
transition={{
duration: 0.3,
}}
className="scroll-m-20 font-mono text-4xl font-extrabold tracking-tight text-red-100 lg:text-5xl"
>
Gagal Mengisi Kehadiran
</motion.h1>
<motion.p
initial={{ opacity: 0, x: 25 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 25 }}
transition={{
duration: 0.3,
delay: 0.2,
}}
className="text-center text-2xl leading-7 text-red-100 [&:not(:first-child)]:mt-6"
>
{participantAttend.isError
? participantAttend.error.message
: "QR Code yang anda tunjukkan tidak valid. Beritahu panitia untuk memperbaiki masalah ini."}
</motion.p>
</div>

<Button
onDoubleClick={() => {
if (isQrInvalid) setInvalidQr(false);
else participantAttend.reset();
}}
>
<motion.div
initial={{ rotate: -95 }}
animate={{ rotate: 0 }}
transition={{
type: "spring",
delay: 0.4,
}}
className="mr-2"
>
<RotateCcw className="h-4 w-4" />
</motion.div>
Coba Lagi
</Button>
</div>
<UniversalError
title="Gagal Verifikasi Status!"
description={
participantAttended.isError
? participantAttended.error.message
: "QR Code yang anda tunjukkan tidak valid. Beritahu panitia untuk memperbaiki masalah ini."
}
/>
);

return <MainScanner mutateData={mutateData} setInvalidQr={setIsQrValid} />;
Expand Down
71 changes: 71 additions & 0 deletions apps/clients/chooser/src/components/universal-error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { motion } from "framer-motion";
import { RotateCcw } from "lucide-react";

import { Button } from "@sora-vp/ui/button";

export function UniversalError(props: {
title: string;
description: string;
errorMessage?: string;
}) {
return (
<div className="flex h-screen w-screen flex-col items-center justify-center gap-5 p-6">
<div className="w-[80%] text-center">
<motion.h1
initial={{ opacity: 0, y: -25 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -25 }}
transition={{
duration: 0.3,
}}
className="scroll-m-20 font-mono text-4xl font-extrabold tracking-tight text-red-600 lg:text-5xl"
>
{props.title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: -25 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -25 }}
transition={{
duration: 0.3,
delay: 0.2,
}}
className="text-center text-xl leading-7 [&:not(:first-child)]:mt-6"
>
{props.description}
</motion.p>
</div>

{props.errorMessage ? (
<motion.div
initial={{ opacity: 0, y: -25 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -25 }}
transition={{
duration: 0.3,
delay: 0.4,
}}
className="flex flex-col items-center"
>
<p>Pesan Error:</p>
<pre className="w-full border p-2 font-mono">{}</pre>
</motion.div>
) : null}

<Button onDoubleClick={() => location.reload()}>
<motion.div
initial={{ rotate: -95 }}
animate={{ rotate: 0 }}
transition={{
type: "spring",
delay: 0.7,
}}
className="mr-2"
>
<RotateCcw className="h-4 w-4" />
</motion.div>
Muat Ulang
</Button>
</div>
);
}
29 changes: 29 additions & 0 deletions apps/clients/chooser/src/components/universal-loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { motion } from "framer-motion";
import { Loader } from "lucide-react";

export function UniversalLoading(props: {
title: string;
description: string;
}) {
return (
<motion.div
initial={{ opacity: 0, x: "-250px" }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: "-250px" }}
className="flex h-screen w-screen flex-col items-center justify-center"
>
<Loader
size={78}
strokeWidth={1.5}
className="animate-pulse animate-spin"
/>

<div>
<h3 className="mt-8 scroll-m-20 text-3xl font-semibold tracking-tight">
{props.title}
</h3>
<p className="text-xl font-light leading-7">{props.description}</p>
</div>
</motion.div>
);
}
Loading

0 comments on commit a665e05

Please sign in to comment.