Skip to content

Commit

Permalink
certfication generation initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
Dileepadari committed Feb 2, 2025
1 parent bc8330e commit 888ac0d
Show file tree
Hide file tree
Showing 22 changed files with 2,675 additions and 412 deletions.
1,232 changes: 824 additions & 408 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@
"framer-motion": "^11.11.9",
"graphql": "^16.9.0",
"graphql-tag": "^2.12.6",
"html2pdf.js": "^0.10.2",
"jwt-decode": "^4.0.0",
"libphonenumber-js": "^1.11.12",
"next": "^14.2.15",
"next-mdx-remote": "^5.0.0",
"nprogress": "^0.2.0",
"path-to-regexp": "^8.2.0",
"prop-types": "^15.8.1",
"qrcode": "^1.5.4",
"react": "^18.3.1",
"react-custom-scrollbars-2": "^4.5.0",
"react-dom": "^18.3.1",
Expand Down
3 changes: 3 additions & 0 deletions src/acl/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ const routes = {
"/manage/holidays": ["cc", "slo"],
"/manage/holidays/new": ["cc", "slo"], // has to be higher to not conflict with :id
"/manage/holidays/:id": ["cc", "slo"],

"/certificates": ["cc", "slo"],
"/certificates/all": ["cc", "slo"],
};

export default routes;
24 changes: 24 additions & 0 deletions src/actions/certificates/approve/server_action.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"use server";

import { getClient } from "gql/client";
import { APPROVE_CERTIFICATE } from "gql/mutations/members";

export async function approveCertificate(certificateNumber) {
const response = { ok: false, error: null };

const { data, error } = await getClient().mutation(APPROVE_CERTIFICATE, {
certificateNumber,
});

if (error) {
response.error = {
title: error.name,
messages: error?.graphQLErrors?.map((ge) => ge?.message),
};
} else {
response.ok = true;
response.data = data.approveCertificate;
}

return response;
}
24 changes: 24 additions & 0 deletions src/actions/certificates/reject/server_action.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"use server";

import { getClient } from "gql/client";
import { REJECT_CERTIFICATE } from "gql/mutations/members";

export async function rejectCertificate(certificateNumber) {
const response = { ok: false, error: null };

const { data, error } = await getClient().mutation(REJECT_CERTIFICATE, {
certificateNumber,
});

if (error) {
response.error = {
title: error.name,
messages: error?.graphQLErrors?.map((ge) => ge?.message),
};
} else {
response.ok = true;
response.data = data.rejectCertificate;
}

return response;
}
29 changes: 29 additions & 0 deletions src/actions/certificates/request/server_action.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use server";

import { getClient } from "gql/client";
import { REQUEST_CERTIFICATE } from "gql/mutations/members";

export async function requestCertificate(requestReason) {
try {
const client = getClient();

const variables = {
certificateInput: {
requestReason,
},
};

const { data, error } = await client.mutation(
REQUEST_CERTIFICATE,
variables
);

if (error) {
return { ok: false, error };
} else {
return { ok: true, data: data.requestCertificate };
}
} catch (err) {
return { ok: false, error: err };
}
}
37 changes: 37 additions & 0 deletions src/actions/certificates/verify/server_action.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use server";

import { getClient } from "gql/client";
import { VERIFY_CERTIFICATE } from "gql/queries/members";

export async function verifyCertificate(certificateNumber, key) {
const response = { ok: false, data: null, error: null };

try {
const { error, data } = await getClient().query(VERIFY_CERTIFICATE, {
certificateNumber,
key,
});

if (error) {
response.error = {
title: error.name,
messages: error?.graphQLErrors?.map((ge) => ge?.message) || [
"An unknown error occurred",
],
};
} else if (data && data.verifyCertificate) {
response.ok = true;
response.data = data.verifyCertificate;
} else {
throw new Error("No data returned from the server");
}
} catch (err) {
console.error("Error verifying certificate:", err);
response.error = {
title: "Error",
messages: [err.message || "An unexpected error occurred"],
};
}

return response;
}
189 changes: 189 additions & 0 deletions src/app/certificates/verify/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
"use client";

import { useState, useEffect, useRef } from "react";
import {
Container,
Typography,
TextField,
Button,
Box,
Paper,
CircularProgress,
} from "@mui/material";
import { useSearchParams } from "next/navigation";

import { getFullUser } from "actions/users/get/full/server_action";
import { verifyCertificate } from "actions/certificates/verify/server_action";
import { useToast } from "components/Toast";
import { ISOtoHuman } from "utils/formatTime";

export default function VerifyCertificatePage() {
const searchParams = useSearchParams();
const [certificateNumber, setCertificateNumber] = useState("");
const [key, setKey] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [certificate, setCertificate] = useState(null);
const [user, setUser] = useState(null);
const { triggerToast } = useToast();

const autoSubmitTriggered = useRef(false);

useEffect(() => {
const certificateId = searchParams.get("certificateId");
const validationKey = searchParams.get("validationKey");

if (certificateId && validationKey) {
setCertificateNumber(certificateId);
setKey(validationKey);
}
}, [searchParams]);

useEffect(() => {
if (certificateNumber && key && !autoSubmitTriggered.current) {
autoSubmitTriggered.current = true; // Ensure it runs only once
handleSubmit();
}
}, [certificateNumber, key]);

const handleSubmit = async () => {
setLoading(true);
setError(null);
setCertificate(null);


try {
const result = await verifyCertificate(certificateNumber, key);

if (result.ok && result.data) {
setCertificate(result.data);

const res = await getFullUser(result.data.userId);

if (!res.ok) {
triggerToast({
title: "Unable to fetch user data",
messages: res.error.messages,
severity: "error",
});
} else {
console.log(res.data);
setUser(res.data);
}
} else {
setError(result.error?.messages?.[0] || "Failed to verify certificate");
}
} catch (err) {
// console.error("Error verifying certificate:", err);
setError("An error occurred while verifying the certificate");
} finally {
setLoading(false);
}
};

return (
<Container maxWidth="md">
<Box sx={{ my: 4 }}>
<Typography variant="h4" component="h1" gutterBottom>
Verify Certificate
</Typography>
<Paper elevation={3} sx={{ p: 3 }}>
<TextField
fullWidth
label="Certificate Number"
variant="outlined"
value={certificateNumber}
onChange={(e) => setCertificateNumber(e.target.value)}
margin="normal"
required
/>
<TextField
fullWidth
label="Verification Key"
variant="outlined"
value={key}
onChange={(e) => setKey(e.target.value)}
margin="normal"
required
/>
<Button
variant="contained"
color="primary"
size="large"
fullWidth
sx={{ mt: 2 }}
disabled={loading}
onClick={handleSubmit}
>
{loading ? <CircularProgress size={24} /> : "Verify Certificate"}
</Button>
</Paper>

{error ? (
<Typography
variant="body1"
color="error"
sx={{ mt: 2, textTransform: "capitalize" }}
>
<center>{error}</center>
</Typography>
) : null}

{certificate && (
<Paper elevation={3} sx={{ mt: 3, p: 3 }}>
<Typography variant="h6" gutterBottom>
Certificate Details
</Typography>
<Box
sx={{
display: "grid",
gridTemplateColumns: "auto 1fr",
gap: 2,
alignItems: "baseline",
mt: 3,
}}
>
<Typography variant="subtitle2" fontWeight="bold">
Certificate Number:
</Typography>
<Typography variant="body1">
{certificate.certificateNumber}
</Typography>

<Typography variant="subtitle2" fontWeight="bold">
Name:
</Typography>
<Typography variant="body1">
{user?.firstName} {user?.lastName}
</Typography>

<Typography variant="subtitle2" fontWeight="bold">
Email:
</Typography>
<Typography variant="body1">{user?.email}</Typography>

<Typography variant="subtitle2" fontWeight="bold">
Roll Number:
</Typography>
<Typography variant="body1">{user?.rollno}</Typography>

<Typography variant="subtitle2" fontWeight="bold">
Batch:
</Typography>
<Typography variant="body1">
{user?.batch.toUpperCase()} · {user?.stream.toUpperCase()}
</Typography>

<Typography variant="subtitle2" fontWeight="bold">
Issued Date:
</Typography>
<Typography variant="body1">
{ISOtoHuman(certificate.status.requestedAt)}
</Typography>
</Box>
</Paper>
)}
</Box>
</Container>
);
}
28 changes: 28 additions & 0 deletions src/app/manage/certificates/all/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { getClient } from "gql/client";

import { Box, Button, Grid, Typography } from "@mui/material";
import AllCertificatesTable from "components/certificates/AllCertificatesTable";
import { GET_ALL_CERTIFICATES } from "gql/queries/members"

export default async function AllCertificates() {
const { error, data : { getAllCertificates } = {} } = await getClient().query(GET_ALL_CERTIFICATES);
return (
<div className="container mx-auto p-4">
<Grid
container
item
sx={{ display: "flex", justifyContent: "space-between" }}
>
<Typography variant="h3" gutterBottom mt={2}>
All Certificates
</Typography>
<Box mt={3} mb={4}>
<Button variant="contained" color="primary" href="/manage/certificates">
View Pending Certificates
</Button>
</Box>
</Grid>
<AllCertificatesTable Certs={getAllCertificates} />
</div>
);
}
28 changes: 28 additions & 0 deletions src/app/manage/certificates/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { getClient } from "gql/client";

import { Box, Button, Grid, Typography } from "@mui/material";
import PendingCertificatesTable from "components/certificates/PendingCertificatesTable";
import { GET_PENDING_CERTIFICATES } from "gql/queries/members"

export default async function PendingCertificates() {
const { data: { getPendingCertificates } = {} } = await getClient().query(GET_PENDING_CERTIFICATES);
return (
<div className="container mx-auto p-4">
<Grid
container
item
sx={{ display: "flex", justifyContent: "space-between" }}
>
<Typography variant="h3" gutterBottom mt={2}>
Pending Certificate Requests
</Typography>
<Box mt={3} mb={4}>
<Button variant="contained" color="primary" href="/manage/certificates/all">
View All Certificates
</Button>
</Box>
</Grid>
<PendingCertificatesTable pendingCerts={getPendingCertificates} />
</div>
);
}
Loading

0 comments on commit 888ac0d

Please sign in to comment.