Skip to content

Commit

Permalink
added checkin
Browse files Browse the repository at this point in the history
  • Loading branch information
blahkheart committed Oct 26, 2024
1 parent 21d80e4 commit 88929cd
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 1 deletion.
25 changes: 25 additions & 0 deletions packages/nextjs/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import React, { useEffect, useState } from "react";
import Image from "next/image";
import type { NextPage } from "next";
import { useAccount } from "wagmi";
import { CheckInForm } from "~~/components/ui/CheckIn";
import CountdownTimer from "~~/components/ui/CountDownTimer";
import { RegistrationModal } from "~~/components/ui/RegistrationModal";
import { RegistrationsClosedModal } from "~~/components/ui/RegistrationsClosedModal";
import { useScaffoldReadContract } from "~~/hooks/scaffold-eth/useScaffoldReadContract";
Expand All @@ -12,13 +15,27 @@ const Home: NextPage = () => {
const [selectedClass, setSelectedClass] = useState("");
const [isModalOpen, setIsModalOpen] = useState(false);
const [isClosedModalOpen, setIsClosedModalOpen] = useState(false);
const [isOwner, setIsOwner] = useState(false);

const { classData, setHackerClass, setLockAddress } = useGlobalState();
const { address: connectedAddress } = useAccount();

const { data: areRegistrationsOpen, isLoading } = useScaffoldReadContract({
contractName: "ETHRwandaHackathonOnboard",
functionName: "getAreRegistrationsOpen",
});

const { data: ownerAddress } = useScaffoldReadContract({
contractName: "ETHRwandaHackathonOnboard",
functionName: "owner",
});

useEffect(() => {
if (connectedAddress && ownerAddress) {
setIsOwner(connectedAddress.toLowerCase() === ownerAddress.toLowerCase());
}
}, [connectedAddress, ownerAddress]);

useEffect(() => {
const defaultClass = "University";
if (classData[defaultClass]) {
Expand Down Expand Up @@ -121,6 +138,14 @@ const Home: NextPage = () => {
shape the future of blockchain technology.
</p>
</div>
<div className="flex items-center justify-centerp-4 flex-col">
<CountdownTimer onComplete={() => console.log("Timer completed!")} />
{isOwner && (
<div className="mt-2 pt-1">
<CheckInForm endpoint="/api/checkin/inperson" />
</div>
)}
</div>
{isLoading ? (
<div className="flex justify-center items-center h-screen">
<span className="loading loading-spinner loading-xs"></span>
Expand Down
56 changes: 56 additions & 0 deletions packages/nextjs/components/ui/CheckIn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useState } from "react";
import { notification } from "~~/utils/scaffold-eth/notification";

export function CheckInForm({ endpoint }: { endpoint: string }) {
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);

const handleCheckIn = async () => {
if (!email) {
notification.error("Email is required");
return;
}

setLoading(true);

try {
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
});

if (!response.ok) {
throw new Error("Failed to check in");
}

const result = await response.json();
notification.success(result.message);
setEmail(""); // Clear the input field
} catch (error) {
notification.error(`${(error as Error).message}`);
} finally {
setLoading(false);
}
};

return (
<div>
<div className="flex items-center join w-full max-w-2xl mt-8">
<input
className="input input-bordered join-item w-full max-w-2xl"
value={email}
onChange={e => setEmail(e.target.value)}
type="email"
placeholder="Email"
/>
<button onClick={handleCheckIn} disabled={loading} className="btn btn-secondary join-item rounded-r-full">
{" "}
{loading ? "Checking in..." : "Check In"}
</button>
</div>
</div>
);
}
86 changes: 86 additions & 0 deletions packages/nextjs/components/ui/CountDownTimer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"use client";

import { useEffect, useState } from "react";
import { CheckInForm } from "./CheckIn";

type CountdownTimerProps = {
onComplete?: () => void;
};

export default function CountdownTimer({ onComplete }: CountdownTimerProps) {
const calculateInitialTime = (): number => {
const now = new Date();
const target = new Date();
target.setHours(15, 0, 0, 0); // Set target time to 2 PM today

const timeDifference = target.getTime() - now.getTime();
const initialTimeInSeconds = Math.max(0, Math.floor(timeDifference / 1000));

return initialTimeInSeconds;
};

const [timeLeft, setTimeLeft] = useState(calculateInitialTime());

useEffect(() => {
if (timeLeft <= 0) {
onComplete?.();
return;
}

const timer = setInterval(() => {
setTimeLeft(prevTime => prevTime - 1);
}, 1000);

return () => clearInterval(timer);
}, [timeLeft, onComplete]);

const formatTime = (time: number): { hours: string; minutes: string; seconds: string } => {
if (isNaN(time) || time < 0) return { hours: "00", minutes: "00", seconds: "00" };

const hours = Math.floor(time / 3600);
const minutes = Math.floor((time % 3600) / 60);
const seconds = time % 60;
return {
hours: hours.toString().padStart(2, "0"),
minutes: minutes.toString().padStart(2, "0"),
seconds: seconds.toString().padStart(2, "0"),
};
};

const { hours, minutes, seconds } = formatTime(timeLeft);

return (
<div className="w-full max-w-2xl mx-auto overflow-hidden">
{timeLeft > 0 ? (
<div className="p-6 sm:p-10">
<h2 className="text-2xl font-bold text-gray-800 mb-4 text-center">Online Check-in closes in </h2>
<div className="flex justify-center items-center space-x-4 sm:space-x-8">
<div className="flex flex-col items-center">
<span className="text-4xl sm:text-6xl font-bold text-primary tabular-nums" aria-live="polite">
{hours}
</span>
<span className="text-sm sm:text-base text-gray-600 mt-1">Hours</span>
</div>
<span className="text-4xl sm:text-6xl font-bold text-gray-400">:</span>
<div className="flex flex-col items-center">
<span className="text-4xl sm:text-6xl font-bold text-primary tabular-nums" aria-live="polite">
{minutes}
</span>
<span className="text-sm sm:text-base text-gray-600 mt-1">Minutes</span>
</div>
<span className="text-4xl sm:text-6xl font-bold text-gray-400">:</span>
<div className="flex flex-col items-center">
<span className="text-4xl sm:text-6xl font-bold text-primary tabular-nums" aria-live="polite">
{seconds}
</span>
<span className="text-sm sm:text-base text-gray-600 mt-1">Seconds</span>
</div>
</div>
<CheckInForm endpoint="/api/checkin/online" />
</div>
) : (
<p className="mt-6 text-xl font-semibold text-center text-gray-700">Online Check-in is now closed</p>
)}
</div>
);
}
2 changes: 1 addition & 1 deletion packages/nextjs/components/ui/RegistrationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function RegistrationModal({ isOpen, onClose, selectedClass }: Registrati
const [isSuccess, setIsSuccess] = useState(false);
const isRegistrationWithEmailSaved = useIsEmailRegistered(hackers, formData.email);
const isRegistrationWithNumberSaved = useIsPhoneNumberRegistered(hackers, formData.phone);
console.log("Hackers:::", hackers);

const { data: isEmailRegistered, isLoading } = useScaffoldReadContract({
contractName: "ETHRwandaHackathonOnboard",
functionName: "getIsEmailRegistered",
Expand Down
31 changes: 31 additions & 0 deletions packages/nextjs/pages/api/checkin/inperson.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NextApiRequest, NextApiResponse } from "next";
import clientPromise from "~~/utils/mongodb";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}

const { email } = req.body;

if (!email) {
return res.status(400).json({ error: "Email is required" });
}

try {
const client = await clientPromise;
const db = client.db("eth-rwanda-hackathon");
const collection = db.collection("hackers-checkin");

await collection.insertOne({
email,
inperson: true,
checkedInAt: new Date(),
});

res.status(200).json({ message: "Checked in in-person successfully" });
} catch (error) {
console.error("Error during in-person check-in:", error);
res.status(500).json({ error: "Internal Server Error" });
}
}
31 changes: 31 additions & 0 deletions packages/nextjs/pages/api/checkin/online.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NextApiRequest, NextApiResponse } from "next";
import clientPromise from "~~/utils/mongodb";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}

const { email } = req.body;

if (!email) {
return res.status(400).json({ error: "Email is required" });
}

try {
const client = await clientPromise;
const db = client.db("eth-rwanda-hackathon");
const collection = db.collection("hackers-checkin");

await collection.insertOne({
email,
inperson: false,
checkedInAt: new Date(),
});
console.log("Checked in online successfully");
res.status(200).json({ message: "Checked in online successfully" });
} catch (error) {
console.error("Error during online check-in:", error);
res.status(500).json({ error: "Internal Server Error" });
}
}

0 comments on commit 88929cd

Please sign in to comment.