-
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: initial certificate page * feat: show date from event data * fix: db provider * feat: initial certificate page * feat: show date from event data * feat: add certificate table * feat: validation user has certificate * feat: add check user helper * feat: add button download certificate * feat: only show button when event is finished * feat: complete certificate.json for event 2024-02-18 * feat: remove certificate json * refactor(certificate): move from page to component content * feat(certificate): add verification certificate url * feat(certificate): update id to slug * fix(certificate): message error * fix(certificate): better message error user not registered yet * docs: certificate descrition on readme * fix(certificate): image not show * fix(certificate): get image server using fs * fix(certificate): move convert image to server * fix(certificate): using image url * fix(certificate): naming variable * fix(certificate): remove unused classname
- Loading branch information
1 parent
43022fb
commit 32ca6eb
Showing
14 changed files
with
789 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import { | ||
Document, | ||
Image, | ||
Page, | ||
StyleSheet, | ||
Text, | ||
View, | ||
} from "@react-pdf/renderer" | ||
import { parsedEnv } from "~/utils/env.server" | ||
|
||
const styles = StyleSheet.create({ | ||
page: { | ||
flexDirection: "row", | ||
backgroundColor: "white", | ||
position: "relative", | ||
}, | ||
container: { | ||
width: "100vw", | ||
height: "100vh", | ||
}, | ||
backgroundImage: { | ||
height: "100vh", | ||
width: "70vw", | ||
position: "absolute", | ||
objectFit: "contain", | ||
objectPosition: "top right", | ||
opacity: 0.05, | ||
top: 0, | ||
right: 0, | ||
zIndex: 1, | ||
}, | ||
section: { | ||
margin: "20 30", | ||
padding: 20, | ||
flexGrow: 1, | ||
display: "flex", | ||
flexDirection: "column", | ||
justifyContent: "space-between", | ||
zIndex: 10, | ||
}, | ||
title: { | ||
fontSize: "30px", | ||
}, | ||
bandungDevIcon: { | ||
width: "200px", | ||
height: "50px", | ||
objectPosition: "center left", | ||
objectFit: "contain", | ||
marginBottom: "10px", | ||
}, | ||
containerContent: { | ||
display: "flex", | ||
flexDirection: "column", | ||
gap: "20px", | ||
}, | ||
name: { | ||
fontSize: "36px", | ||
color: "#3C83F6", | ||
}, | ||
containerContentDetail: { | ||
display: "flex", | ||
flexDirection: "column", | ||
gap: "5px", | ||
}, | ||
containerContentFooter: { | ||
display: "flex", | ||
flexDirection: "row", | ||
justifyContent: "space-between", | ||
alignItems: "flex-end", | ||
}, | ||
signature: { | ||
height: "60px", | ||
width: "60px", | ||
}, | ||
signatureTitle: { | ||
marginTop: "5px", | ||
fontSize: "10px", | ||
}, | ||
containerVerificationCertificate: { | ||
display: "flex", | ||
flexDirection: "column", | ||
alignItems: "flex-end", | ||
fontSize: "14px", | ||
gap: "4px", | ||
}, | ||
url: { | ||
fontSize: "10px", | ||
}, | ||
}) | ||
|
||
interface CertificateType { | ||
eventName: string | ||
fullName: string | ||
date: string | ||
url: string | ||
} | ||
|
||
export function Certificate({ | ||
eventName, | ||
fullName, | ||
date, | ||
url, | ||
}: CertificateType) { | ||
const { SIGNATURE_URL, APP_URL } = parsedEnv | ||
|
||
return ( | ||
<Document> | ||
<Page size="A4" style={styles.page} orientation="landscape"> | ||
<View style={styles.container}> | ||
<Image | ||
style={styles.backgroundImage} | ||
source={`${APP_URL}/images/logos/png/bandungdev-icon-white.png`} | ||
/> | ||
<View style={styles.section}> | ||
<View> | ||
<Image | ||
style={styles.bandungDevIcon} | ||
source={`${APP_URL}/images/logos/png/bandungdev-logo-text.png`} | ||
/> | ||
<Text style={styles.title}>CERTIFICATE OF ATTENDANCE</Text> | ||
</View> | ||
<View style={styles.containerContent}> | ||
<Text>This certificate is presented to</Text> | ||
<Text style={styles.name}>{fullName}</Text> | ||
<View style={styles.containerContentDetail}> | ||
<Text>for attending {eventName}</Text> | ||
<Text>{date}</Text> | ||
</View> | ||
</View> | ||
<View style={styles.containerContentFooter}> | ||
<View> | ||
<Image style={styles.signature} source={SIGNATURE_URL} /> | ||
<Text>M. Haidar Hanif</Text> | ||
<Text style={styles.signatureTitle}>Lead BandungDev</Text> | ||
</View> | ||
<View style={styles.containerVerificationCertificate}> | ||
<Text>Certificate verification</Text> | ||
<Text style={styles.url}>{url}</Text> | ||
</View> | ||
</View> | ||
</View> | ||
</View> | ||
</Page> | ||
</Document> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { type Certficate } from "@prisma/client" | ||
import { prisma } from "~/libs/db.server" | ||
|
||
export const modelCertificate = { | ||
getByGlug({ slug }: Pick<Certficate, "slug">) { | ||
return prisma.certficate.findUnique({ | ||
where: { slug }, | ||
include: { user: true, event: true }, | ||
}) | ||
}, | ||
|
||
getBySlugEventAndEmail({ | ||
slugEvent, | ||
email, | ||
}: Pick<Certficate, "slugEvent" | "email">) { | ||
return prisma.certficate.findFirst({ | ||
where: { | ||
slugEvent, | ||
email, | ||
}, | ||
}) | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { renderToStream } from "@react-pdf/renderer" | ||
import { type LoaderFunctionArgs } from "@remix-run/node" | ||
import { Certificate } from "~/components/contents/certificate" | ||
import { requireUser } from "~/helpers/auth" | ||
import { prisma } from "~/libs/db.server" | ||
import { modelCertificate } from "~/models/certificate.server" | ||
import { modelEvent } from "~/models/event.server" | ||
import { formatCertificateDate } from "~/utils/datetime" | ||
import { parsedEnv } from "~/utils/env.server" | ||
import { invariant, invariantResponse } from "~/utils/invariant" | ||
|
||
export const loader = async ({ params, request }: LoaderFunctionArgs) => { | ||
const { user } = await requireUser(request) | ||
|
||
invariant(params.eventSlug, "params.eventSlug unavailable") | ||
|
||
const [event, certificate] = await prisma.$transaction([ | ||
modelEvent.getBySlug({ slug: params.eventSlug }), | ||
modelCertificate.getBySlugEventAndEmail({ | ||
slugEvent: params.eventSlug, | ||
email: user.email, | ||
}), | ||
]) | ||
|
||
invariantResponse(event, "Event not found", { status: 404 }) | ||
|
||
invariantResponse(certificate, "Certificate not found", { status: 404 }) | ||
|
||
const dateTimeFormatted = formatCertificateDate( | ||
event.dateTimeStart, | ||
event.dateTimeEnd, | ||
) | ||
|
||
const { APP_URL } = parsedEnv | ||
|
||
const stream = await renderToStream( | ||
<Certificate | ||
eventName={event.title} | ||
fullName={user.fullname} | ||
date={dateTimeFormatted} | ||
url={`${APP_URL}/events/certificate/${certificate.slug}.pdf`} | ||
/>, | ||
) | ||
|
||
const body: Buffer = await new Promise((resolve, reject) => { | ||
const buffers: Uint8Array[] = [] | ||
stream.on("data", data => { | ||
buffers.push(data) | ||
}) | ||
stream.on("end", () => { | ||
resolve(Buffer.concat(buffers)) | ||
}) | ||
stream.on("error", reject) | ||
}) | ||
|
||
const headers = new Headers({ "Content-Type": "application/pdf" }) | ||
return new Response(body, { status: 200, headers }) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.