diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9a207b1f..029bc3c2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,20 +10,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: |
- docker build -t techno-event-core-admin .
+ - uses: actions/checkout@v2
+ - run: |
+ docker build -t techno-event-core-admin -f apps/core-admin/Dockerfile .
+
+ - run: |
+ docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
+ - run: |
+ docker tag techno-event-core-admin ${{ secrets.DOCKER_USER }}/techno-event-core-admin:latest
+ docker push ${{ secrets.DOCKER_USER }}/techno-event-core-admin:latest
- - run: |
- docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
- - run: |
- docker tag techno-event-core-admin ${{ secrets.DOCKER_USER }}/techno-event-core-admin:latest
- docker push ${{ secrets.DOCKER_USER }}/techno-event-core-admin:latest
-
redeploy-backend:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- - name: Call deploy hook
- run: |
- curl -X GET ${{ secrets.BACKEND_DEPLOY_HOOK }}
+ - name: Call deploy hook
+ run: |
+ curl -X GET ${{ secrets.BACKEND_DEPLOY_HOOK }}
diff --git a/Dockerfile b/apps/core-admin/Dockerfile
similarity index 92%
rename from Dockerfile
rename to apps/core-admin/Dockerfile
index b3f068db..b15551de 100644
--- a/Dockerfile
+++ b/apps/core-admin/Dockerfile
@@ -6,14 +6,12 @@ WORKDIR /app
RUN npm install -g pnpm turbo
-COPY . .
+COPY ../../. .
RUN pnpm install
RUN pnpm run build --filter=techno-event-core-admin...
-RUN ls -la node_modules
-
FROM node:18-alpine
COPY --from=builder /app/apps/core-admin/dist .
diff --git a/apps/core-admin/src/controllers/participants.ts b/apps/core-admin/src/controllers/participants.ts
new file mode 100644
index 00000000..cd4a9be1
--- /dev/null
+++ b/apps/core-admin/src/controllers/participants.ts
@@ -0,0 +1,148 @@
+import { Request, Response } from 'express';
+
+import prisma from '../utils/database';
+
+export const addNewParticipant = async (req: Request, res: Response) => {
+ try {
+ const { orgId, eventId } = req?.params;
+ const { firstName, lastName } = req?.body;
+
+ const newParticipant = await prisma.participant.create({
+ data: {
+ firstName,
+ lastName,
+ organizationId: orgId,
+ eventId,
+ },
+ });
+
+ if (!newParticipant) {
+ return res.status(500).json({ error: 'Something went wrong' });
+ }
+
+ return res.status(200).json({ newParticipant });
+ } catch (err: any) {
+ console.error(err);
+ return res.status(500).json({ error: 'Something went wrong' });
+ }
+};
+
+export const getAllParticipants = async (req: Request, res: Response) => {
+ try {
+ const { orgId, eventId } = req?.params;
+ const participants = await prisma.participant.findMany({
+ where: {
+ organizationId: orgId,
+ eventId,
+ },
+ });
+
+ if (!participants) {
+ return res.status(500).json({ error: 'Something went wrong' });
+ }
+
+ return res.status(200).json({ participants });
+ } catch (err: any) {
+ console.error(err);
+ return res.status(500).json({ error: 'Something went wrong' });
+ }
+};
+
+export const getAllParticipantsCheckInDetails = async (req: Request, res: Response) => {
+ try {
+ const { orgId, eventId } = req?.params;
+
+ const participantsCheckIn = await prisma.participant.findMany({
+ where: {
+ organizationId: orgId,
+ eventId,
+ },
+ include: {
+ participantCheckIn: {
+ select: {
+ checkedInAt: true,
+ checkedInByUser: true,
+ },
+ },
+ },
+ });
+
+ if (!participantsCheckIn) {
+ return res.status(500).json({ error: 'Something went wrong' });
+ }
+
+ return res.status(200).json({ participantsCheckIn });
+ } catch (err: any) {
+ console.error(err);
+ return res.status(500).json({ error: 'Something went wrong' });
+ }
+};
+
+export const getParticipantById = async (req: Request, res: Response) => {
+ try {
+ const { orgId, eventId, participantId } = req?.params;
+ const participant = await prisma.participant.findUnique({
+ where: {
+ id: participantId,
+ },
+ include: {
+ participantCheckIn: {
+ select: {
+ checkedInAt: true,
+ checkedInByUser: true,
+ },
+ },
+ },
+ });
+
+ if (!participant) {
+ return res.status(500).json({ error: 'Something went wrong' });
+ }
+
+ return res.status(200).json({ participant });
+ } catch (err: any) {
+ console.error(err);
+ return res.status(500).json({ error: 'Something went wrong' });
+ }
+};
+
+export const checkInParticipant = async (req: Request, res: Response) => {
+ try {
+ const userId = req?.auth?.payload?.sub;
+
+ const { orgId, eventId, participantId } = req?.params;
+
+ const { checkedInAt } = req?.body;
+
+ const participantAlreadyCheckedIn = await prisma.participantCheckIn.findFirst({
+ where: {
+ participantId,
+ organizationId: orgId,
+ eventId,
+ },
+ });
+
+ if (participantAlreadyCheckedIn) {
+ return res.status(400).json({ error: 'Participant already checked in' });
+ }
+
+ const participantCheckIn = await prisma.participantCheckIn.create({
+ data: {
+ participantId,
+ organizationId: orgId,
+ eventId,
+ checkedInBy: userId,
+ checkedInAt,
+ },
+ });
+
+ if (!participantCheckIn) {
+ return res.status(500).json({ error: 'Something went wrong' });
+ }
+
+ return res.status(200).json({ participantCheckIn });
+ } catch (err: any) {
+ console.error(err);
+ return res.status(500).json({ error: 'Something went wrong' });
+ }
+};
diff --git a/apps/core-admin/src/routes.ts b/apps/core-admin/src/routes.ts
index 861f2d52..c83f1e71 100644
--- a/apps/core-admin/src/routes.ts
+++ b/apps/core-admin/src/routes.ts
@@ -1,6 +1,14 @@
import express, { Router } from 'express';
import { createNewOrganization, getUsersOrganizations } from './controllers/organizations';
import { createNewEvent, getEvents } from './controllers/events';
+import {
+ addNewParticipant,
+ getAllParticipants,
+ getParticipantById,
+ checkInParticipant,
+ getAllParticipantsCheckInDetails,
+} from './controllers/participants';
+
const router: Router = express.Router();
router.get('/', (req: any, res: any) => {
@@ -18,4 +26,16 @@ router.post('/organizations', createNewOrganization);
router.get('/organizations/:orgId/events', getEvents);
router.post('/organizations/:orgId/events', createNewEvent);
+router.get('/organizations/:orgId/events/:eventId/participants', getAllParticipants);
+router.post('/organizations/:orgId/events/:eventId/participants', addNewParticipant);
+router.get(
+ '/organizations/:orgId/events/:eventId/participants/check-in',
+ getAllParticipantsCheckInDetails,
+);
+router.get('/organizations/:orgId/events/:eventId/participants/:participantId', getParticipantById);
+router.post(
+ '/organizations/:orgId/events/:eventId/participants/check-in/:participantId',
+ checkInParticipant,
+);
+
export default router;
diff --git a/apps/web-admin/src/components/Scanner/Scanner.jsx b/apps/web-admin/src/components/Scanner.jsx
similarity index 76%
rename from apps/web-admin/src/components/Scanner/Scanner.jsx
rename to apps/web-admin/src/components/Scanner.jsx
index 0369f9f3..d78d4b46 100644
--- a/apps/web-admin/src/components/Scanner/Scanner.jsx
+++ b/apps/web-admin/src/components/Scanner.jsx
@@ -1,11 +1,13 @@
import React, { useState } from 'react';
import { QrReader } from 'react-qr-reader';
import { Text } from '@chakra-ui/react';
+
const Scanner = ({ result, setResult }) => {
const handleScan = (result) => {
- console.log(result);
- setResult(result || '');
- //call checkin function
+ if (result) {
+ console.log(result);
+ setResult(result?.text);
+ }
};
const handleError = (err) => {
@@ -15,7 +17,6 @@ const Scanner = ({ result, setResult }) => {
return (
- {JSON.stringify(result)}
);
};
diff --git a/apps/web-admin/src/components/Sidebar.jsx b/apps/web-admin/src/components/Sidebar.jsx
index 828419fb..ba8208e1 100644
--- a/apps/web-admin/src/components/Sidebar.jsx
+++ b/apps/web-admin/src/components/Sidebar.jsx
@@ -12,12 +12,14 @@ const Sidebar = () => {
const { logout } = useAuth0();
- const router = useRouter();
-
const handleLogout = (e) => {
e.preventDefault();
setLoading(true);
- logout();
+ logout({
+ logoutParams: {
+ returnTo: process.env.NEXT_PUBLIC_AUTH0_REDIRECT_URI,
+ },
+ });
};
return (
diff --git a/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/index.jsx b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/index.jsx
new file mode 100644
index 00000000..1a642e5a
--- /dev/null
+++ b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/index.jsx
@@ -0,0 +1,12 @@
+import { useRouter } from 'next/router';
+import { useEffect } from 'react';
+
+export default function Event() {
+ const router = useRouter();
+
+ const { orgId, eventId } = router.query;
+
+ useEffect(() => {
+ router.push(`/organizations/${orgId}/events/${eventId}/participants`);
+ }, [orgId, eventId]);
+}
diff --git a/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/[participantId]/index.jsx b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/[participantId]/index.jsx
new file mode 100644
index 00000000..59e22dcd
--- /dev/null
+++ b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/[participantId]/index.jsx
@@ -0,0 +1,64 @@
+import { useRouter } from 'next/router';
+
+import {
+ Box,
+ Flex,
+ Table,
+ TableCaption,
+ Tbody,
+ Td,
+ Tfoot,
+ Th,
+ Thead,
+ Tr,
+ TableContainer,
+ Text,
+} from '@chakra-ui/react';
+
+import { useFetch } from '@/hooks/useFetch';
+
+import DashboardLayout from '@/layouts/DashboardLayout';
+import { useEffect, useState } from 'react';
+
+export default function Events() {
+ const router = useRouter();
+
+ const { orgId, eventId, participantId } = router.query;
+
+ const { loading, get } = useFetch();
+
+ const [participant, setParticipant] = useState({});
+
+ useEffect(() => {
+ const fetchParticipant = async () => {
+ const { data, status } = await get(
+ `/core/organizations/${orgId}/events/${eventId}/participants/${participantId}`,
+ );
+ setParticipant(data.participant || []);
+ console.log(data);
+ };
+ fetchParticipant();
+ }, [orgId, eventId, participantId]);
+
+ return (
+
+
+
+
+ Participant Details
+
+
+
+ {JSON.stringify(participant)}
+
+
+
+ );
+}
diff --git a/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/check-in/index.jsx b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/check-in/index.jsx
new file mode 100644
index 00000000..12cb1789
--- /dev/null
+++ b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/check-in/index.jsx
@@ -0,0 +1,95 @@
+import { useRouter } from 'next/router';
+
+import {
+ Box,
+ Flex,
+ Table,
+ TableCaption,
+ Tbody,
+ Td,
+ Tfoot,
+ Th,
+ Thead,
+ Tr,
+ TableContainer,
+ Text,
+} from '@chakra-ui/react';
+
+import { useFetch } from '@/hooks/useFetch';
+
+import DashboardLayout from '@/layouts/DashboardLayout';
+import { useEffect, useState } from 'react';
+
+export default function Events() {
+ const router = useRouter();
+
+ const { orgId, eventId } = router.query;
+
+ const { loading, get } = useFetch();
+
+ const [participantsCheckIn, setParticipantsCheckIn] = useState([]);
+
+ useEffect(() => {
+ const fetchParticipantsCheckIn = async () => {
+ const { data, status } = await get(
+ `/core/organizations/${orgId}/events/${eventId}/participants/check-in`,
+ );
+ setParticipantsCheckIn(data.participantsCheckIn || []);
+ console.log(data);
+ };
+ fetchParticipantsCheckIn();
+ }, [orgId, eventId]);
+
+ return (
+
+
+
+
+ Participants Check In
+
+
+
+
+
+ Participants Check In
+
+
+ ID |
+ First Name |
+ Last Name |
+ Status |
+ Check In At |
+ Checked In By |
+
+
+
+ {participantsCheckIn.map((participant) => (
+
+ {participant?.id} |
+ {participant?.firstName} |
+ {participant?.lastName} |
+ {participant?.participantCheckIn.length > 0 ? 'true' : 'false'} |
+ {participant?.participantCheckIn[0]?.checkedInAt} |
+ {participant?.participantCheckIn[0]?.checkedInByUser?.email} |
+
+ ))}
+
+
+
+ {participantsCheckIn.length} participants |
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/check-in/new/index.jsx b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/check-in/new/index.jsx
new file mode 100644
index 00000000..0dc30fde
--- /dev/null
+++ b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/check-in/new/index.jsx
@@ -0,0 +1,90 @@
+import { useState, useEffect } from 'react';
+
+import { useFetch } from '@/hooks/useFetch';
+
+import {
+ Button,
+ Box,
+ Card,
+ CardBody,
+ FormControl,
+ FormLabel,
+ Input,
+ Flex,
+ Text,
+ Select,
+} from '@chakra-ui/react';
+
+import { useRouter } from 'next/router';
+import DashboardLayout from '@/layouts/DashboardLayout';
+
+export default function NewOrganization() {
+ const { loading, get, post } = useFetch();
+
+ const router = useRouter();
+
+ const { orgId, eventId } = router.query;
+
+ const [participantId, setParticipantId] = useState('');
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ const { data, status } = await post(
+ `/core/organizations/${orgId}/events/${eventId}/participants/check-in/${participantId}`,
+ {},
+ {
+ checkedInAt: new Date().toISOString(),
+ },
+ );
+ if (status === 200) {
+ router.push(`/organizations/${orgId}/events/${eventId}/participants/${participantId}`);
+ } else {
+ alert(data.error);
+ }
+ };
+
+ return (
+
+
+
+
+ Check In Participant
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/check-in/new/scanner/index.jsx b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/check-in/new/scanner/index.jsx
new file mode 100644
index 00000000..deb43635
--- /dev/null
+++ b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/check-in/new/scanner/index.jsx
@@ -0,0 +1,83 @@
+import { useState, useEffect } from 'react';
+
+import { useFetch } from '@/hooks/useFetch';
+
+import {
+ Button,
+ Box,
+ Card,
+ CardBody,
+ FormControl,
+ FormLabel,
+ Input,
+ Flex,
+ Text,
+ Select,
+} from '@chakra-ui/react';
+
+import Scanner from '@/components/Scanner';
+
+import { useRouter } from 'next/router';
+import DashboardLayout from '@/layouts/DashboardLayout';
+
+export default function NewOrganization() {
+ const { loading, get, post } = useFetch();
+
+ const router = useRouter();
+
+ const { orgId, eventId } = router.query;
+
+ const [uninterruptedScanMode, setUninterruptedScanMode] = useState(true);
+ const [scanResult, setScanResult] = useState('');
+
+ useEffect(() => {
+ if (scanResult) {
+ handleSubmit();
+ }
+ }, [scanResult]);
+
+ const handleSubmit = async () => {
+ const { data, status } = await post(
+ `/core/organizations/${orgId}/events/${eventId}/participants/check-in/${scanResult}`,
+ {},
+ {
+ checkedInAt: new Date().toISOString(),
+ },
+ );
+ if (status === 200) {
+ if (uninterruptedScanMode) {
+ alert('Participant checked in successfully');
+ setScanResult('');
+ } else {
+ router.push(`/organizations/${orgId}/events/${eventId}/participants/${scanResult}`);
+ }
+ } else {
+ alert(data.error);
+ }
+ };
+
+ return (
+
+
+
+
+ Check In Participant
+
+
+
+
+
+ {JSON.stringify(scanResult)}
+
+
+
+
+ );
+}
diff --git a/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/index.jsx b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/index.jsx
new file mode 100644
index 00000000..42f10b54
--- /dev/null
+++ b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/index.jsx
@@ -0,0 +1,97 @@
+import { useRouter } from 'next/router';
+
+import {
+ Box,
+ Flex,
+ Table,
+ TableCaption,
+ Tbody,
+ Td,
+ Tfoot,
+ Th,
+ Thead,
+ Tr,
+ TableContainer,
+ Text,
+} from '@chakra-ui/react';
+
+import { useFetch } from '@/hooks/useFetch';
+
+import DashboardLayout from '@/layouts/DashboardLayout';
+import { useEffect, useState } from 'react';
+
+export default function Events() {
+ const router = useRouter();
+
+ const { orgId, eventId } = router.query;
+
+ const { loading, get } = useFetch();
+
+ const [participants, setParticipants] = useState([]);
+
+ useEffect(() => {
+ const fetchParticipants = async () => {
+ const { data, status } = await get(
+ `/core/organizations/${orgId}/events/${eventId}/participants`,
+ );
+ setParticipants(data.participants || []);
+ console.log(data);
+ };
+ fetchParticipants();
+ }, [orgId, eventId]);
+
+ return (
+
+
+
+
+ Participants
+
+
+
+
+
+ Participants
+
+
+ ID |
+ First Name |
+ Last Name |
+
+
+
+ {participants.map((participant) => (
+ {
+ router.push(
+ `/organizations/${orgId}/events/${eventId}/participants/${participant?.id}`,
+ );
+ }}
+ cursor="pointer"
+ >
+ {participant?.id} |
+ {participant?.firstName} |
+ {participant?.lastName} |
+
+ ))}
+
+
+
+ {participants.length} participants |
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/new/index.jsx b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/new/index.jsx
new file mode 100644
index 00000000..614c22a6
--- /dev/null
+++ b/apps/web-admin/src/pages/organizations/[orgId]/events/[eventId]/participants/new/index.jsx
@@ -0,0 +1,103 @@
+import { useState, useEffect } from 'react';
+
+import { useFetch } from '@/hooks/useFetch';
+
+import {
+ Button,
+ Box,
+ Card,
+ CardBody,
+ FormControl,
+ FormLabel,
+ Input,
+ Flex,
+ Text,
+ Select,
+} from '@chakra-ui/react';
+
+import { useRouter } from 'next/router';
+import DashboardLayout from '@/layouts/DashboardLayout';
+
+export default function NewOrganization() {
+ const { loading, get, post } = useFetch();
+
+ const router = useRouter();
+
+ const { orgId, eventId } = router.query;
+
+ const [firstName, setFirstName] = useState('');
+ const [lastName, setLastName] = useState('');
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ const { data, status } = await post(
+ `/core/organizations/${orgId}/events/${eventId}/participants`,
+ {},
+ {
+ firstName,
+ lastName,
+ },
+ );
+ if (status === 200) {
+ router.push(`/organizations/${orgId}/events/${eventId}/participants`);
+ } else {
+ alert(data.error);
+ }
+ };
+
+ return (
+
+
+
+
+ Add new participant
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web-admin/src/pages/organizations/[orgId]/index.jsx b/apps/web-admin/src/pages/organizations/[orgId]/index.jsx
new file mode 100644
index 00000000..6936ec1c
--- /dev/null
+++ b/apps/web-admin/src/pages/organizations/[orgId]/index.jsx
@@ -0,0 +1,12 @@
+import { useRouter } from 'next/router';
+import { useEffect } from 'react';
+
+export default function Organization() {
+ const router = useRouter();
+
+ const { orgId } = router.query;
+
+ useEffect(() => {
+ router.push(`/organizations/${orgId}/events`);
+ }, [orgId]);
+}
diff --git a/apps/web-admin/src/pages/scanner/scanner.jsx b/apps/web-admin/src/pages/scanner/scanner.jsx
deleted file mode 100644
index 97f686e2..00000000
--- a/apps/web-admin/src/pages/scanner/scanner.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import Scanner from '@/components/Scanner/Scanner';
-import DashboardLayout from '@/layouts/DashboardLayout';
-
-import { useState } from 'react';
-
-import { Box, Text, Button, Flex } from '@chakra-ui/react';
-
-export default function ScannerPage() {
- const [result, setResult] = useState('No result');
- return (
-
-
-
-
-
- );
-}
diff --git a/packages/database/prisma/migrations/20240131154020_y/migration.sql b/packages/database/prisma/migrations/20240201163536_/migration.sql
similarity index 100%
rename from packages/database/prisma/migrations/20240131154020_y/migration.sql
rename to packages/database/prisma/migrations/20240201163536_/migration.sql
diff --git a/packages/database/prisma/migrations/20240201165019_/migration.sql b/packages/database/prisma/migrations/20240201165019_/migration.sql
new file mode 100644
index 00000000..c3b99015
--- /dev/null
+++ b/packages/database/prisma/migrations/20240201165019_/migration.sql
@@ -0,0 +1,10 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `firstnName` on the `Participant` table. All the data in the column will be lost.
+ - Added the required column `firstName` to the `Participant` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- AlterTable
+ALTER TABLE "Participant" DROP COLUMN "firstnName",
+ADD COLUMN "firstName" TEXT NOT NULL;
diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma
index 9682c216..4268a42b 100644
--- a/packages/database/prisma/schema.prisma
+++ b/packages/database/prisma/schema.prisma
@@ -64,13 +64,13 @@ model Participant {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
- firstnName String
+ firstName String
lastName String?
eventId String @db.Uuid
event Event @relation(fields: [eventId], references: [id])
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
- ParticipantCheckin ParticipantCheckIn[]
+ participantCheckIn ParticipantCheckIn[]
}
model ParticipantCheckIn {