From 510d4520ec54e9b8839350285312ec8733e3f42a Mon Sep 17 00:00:00 2001 From: Clover Date: Tue, 3 Sep 2024 18:18:26 -0400 Subject: [PATCH 1/2] feat: add application form procedure --- .../userInformation/ApplicationForm.tsx | 164 ++++++++++++++++++ .../procedures/setUserInformation.ts | 40 +++++ 2 files changed, 204 insertions(+) create mode 100644 apps/hackathon/src/app/_components/userInformation/ApplicationForm.tsx create mode 100644 apps/hackathon/src/server/api/routers/applicationForm/procedures/setUserInformation.ts diff --git a/apps/hackathon/src/app/_components/userInformation/ApplicationForm.tsx b/apps/hackathon/src/app/_components/userInformation/ApplicationForm.tsx new file mode 100644 index 0000000..fc3a366 --- /dev/null +++ b/apps/hackathon/src/app/_components/userInformation/ApplicationForm.tsx @@ -0,0 +1,164 @@ +'use client' +import type { FormEvent } from 'react' +import React, { useState } from 'react' +import type { UserInformation } from '@prisma/client' +import { api } from '~/trpc/react' + +interface ApplicationFormProps { + session: { + user: { + id: string + } + } +} + +export function ApplicationForm(props: ApplicationFormProps) { + const [subscribe, setSubscribe] = useState(false) + const [formData, setFormData] = useState({ + id: '1', + email: '', + first_name: '', + last_name: '', + levels_of_study: '', + major: '', + date_of_birth: new Date(0), + gender: '', + phone_number: '', + school: '', + userId: props.session?.user.id || '', + }) + + const submitApplicationForm = api.userInformation.useMutation() + const subscribeToNewsletter = api.subscribeToNewsletter.useMutation() + const handleSubmit = async (e: FormEvent) => { + e.preventDefault() + try { + if (subscribe && formData.email) { + subscribeToNewsletter.mutateAsync({ email: formData.email }) + } + await submitApplicationForm.mutateAsync(formData) + } + catch (error) { + console.error('Error submitting form:', error) + } + } + + const handleSubscribe = (e: React.ChangeEvent) => { + if (e.target && formData.email) { + setSubscribe(!subscribe) + } + else { + e.target.checked = false + } + } + + const handleOnChange = (e: React.ChangeEvent) => { + if (e.target) { + setFormData({ + ...formData, + [e.target.name]: e.target.value, + }) + if (e.target.name === 'email' && subscribe) { + handleSubscribe(e) + } + } + } + + return ( +
+ + + + + + + + + + + subscribe to newsletter with email + + + + + + + + + + + + + + + + + + + + +
+ ) +}; + +export default ApplicationForm diff --git a/apps/hackathon/src/server/api/routers/applicationForm/procedures/setUserInformation.ts b/apps/hackathon/src/server/api/routers/applicationForm/procedures/setUserInformation.ts new file mode 100644 index 0000000..b7c340a --- /dev/null +++ b/apps/hackathon/src/server/api/routers/applicationForm/procedures/setUserInformation.ts @@ -0,0 +1,40 @@ +import { z } from 'zod' + +import { + protectedProcedure, +} from '~/server/api/trpc' + +export const userInformationRouter = protectedProcedure + .input( + z.object({ + id: z.string(), + email: z.string().email().nullable(), + first_name: z.string().nullable(), + last_name: z.string().nullable(), + levels_of_study: z.string().nullable(), + major: z.string().nullable(), + date_of_birth: z.date().nullable(), + gender: z.string().nullable(), + phone_number: z.string().nullable(), + school: z.string().nullable(), + userId: z.string(), + }), + ) + .mutation(async ({ ctx, input }) => { + const data = await ctx.db.userInformation.create({ + data: { + id: input.id, + email: input.email, + first_name: input.first_name, + last_name: input.last_name, + levels_of_study: input.levels_of_study, + major: input.major, + date_of_birth: input.date_of_birth, + gender: input.gender, + phone_number: input.phone_number, + school: input.school, + userId: input.userId, + }, + }) + return data + }) From cc09883f44b01d6d3c72cf4bb9a9edaea671f567 Mon Sep 17 00:00:00 2001 From: Clover Date: Tue, 3 Sep 2024 18:39:04 -0400 Subject: [PATCH 2/2] feat: automated-email-setup --- apps/hackathon/.env.example | 9 +++- apps/hackathon/.github/workflows/BUILD.yml | 4 ++ apps/hackathon/.github/workflows/RELEASE.yml | 4 ++ apps/hackathon/.github/workflows/TEST_e2e.yml | 4 ++ apps/hackathon/src/env.js | 8 ++++ apps/hackathon/src/server/api/root.ts | 4 ++ .../procedures/subscribeToNewsLetter.ts | 47 +++++++++++++++++++ 7 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 apps/hackathon/src/server/api/routers/subscribeToNewsLetter/procedures/subscribeToNewsLetter.ts diff --git a/apps/hackathon/.env.example b/apps/hackathon/.env.example index 5ed9ce7..47542d7 100644 --- a/apps/hackathon/.env.example +++ b/apps/hackathon/.env.example @@ -20,4 +20,11 @@ DATABASE_URL="postgresql://postgres:password@localhost:5432/hackathon" # NEXTAUTH_SECRET="" NEXTAUTH_URL="http://localhost:8000" GOOGLE_CLIENT_ID="YOUR_GOOGLE_CLIENT_ID" -GOOGLE_CLIENT_SECRET="YOUR_GOOGLE_CLIENT_SECRET" \ No newline at end of file +GOOGLE_CLIENT_SECRET="YOUR_GOOGLE_CLIENT_SECRET" + + +# AWS CONFIG +AWS_ACCESS_KEY_ID="YOUR_AWS_ACCESS_KEY_ID" +AWS_SECRET_ACCESS_KEY="YOUR_AWS_SECRET_ACCESS_KEY" +AWS_REGION="YOUR_AWS_REGION" +SES_SOURCE_EMAIL="YOUR_SES_SOURCE_EMAIL" \ No newline at end of file diff --git a/apps/hackathon/.github/workflows/BUILD.yml b/apps/hackathon/.github/workflows/BUILD.yml index f1e7295..65491f0 100644 --- a/apps/hackathon/.github/workflows/BUILD.yml +++ b/apps/hackathon/.github/workflows/BUILD.yml @@ -33,6 +33,10 @@ jobs: NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }} GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} + SES_SOURCE_EMAIL: ${{ secrets.SES_SOURCE_EMAIL }} run: bun run --bun build - name: 💰 Profit diff --git a/apps/hackathon/.github/workflows/RELEASE.yml b/apps/hackathon/.github/workflows/RELEASE.yml index 4aa8b3b..47ec196 100644 --- a/apps/hackathon/.github/workflows/RELEASE.yml +++ b/apps/hackathon/.github/workflows/RELEASE.yml @@ -34,6 +34,10 @@ jobs: NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }} GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} + SES_SOURCE_EMAIL: ${{ secrets.SES_SOURCE_EMAIL }} run: bun run --bun semantic-release - name: 💰 Profit diff --git a/apps/hackathon/.github/workflows/TEST_e2e.yml b/apps/hackathon/.github/workflows/TEST_e2e.yml index 9d49d9a..36d1274 100644 --- a/apps/hackathon/.github/workflows/TEST_e2e.yml +++ b/apps/hackathon/.github/workflows/TEST_e2e.yml @@ -37,6 +37,10 @@ jobs: NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }} GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} + SES_SOURCE_EMAIL: ${{ secrets.SES_SOURCE_EMAIL }} run: bun run --bun playwright test - name: 📄 Upload Artifact diff --git a/apps/hackathon/src/env.js b/apps/hackathon/src/env.js index 1f2008d..05d705a 100644 --- a/apps/hackathon/src/env.js +++ b/apps/hackathon/src/env.js @@ -25,6 +25,10 @@ export const env = createEnv({ ), GOOGLE_CLIENT_ID: z.string(), GOOGLE_CLIENT_SECRET: z.string(), + AWS_ACCESS_KEY_ID: z.string(), + AWS_SECRET_ACCESS_KEY: z.string(), + AWS_REGION: z.string(), + SES_SOURCE_EMAIL: z.string(), }, /** @@ -47,6 +51,10 @@ export const env = createEnv({ NEXTAUTH_URL: process.env.NEXTAUTH_URL, GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, + AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY, + AWS_REGION: process.env.AWS_REGION, + SES_SOURCE_EMAIL: process.env.SES_SOURCE_EMAIL, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially diff --git a/apps/hackathon/src/server/api/root.ts b/apps/hackathon/src/server/api/root.ts index 1b5dc32..6732d94 100644 --- a/apps/hackathon/src/server/api/root.ts +++ b/apps/hackathon/src/server/api/root.ts @@ -1,5 +1,7 @@ import { postRouter } from '~/server/api/routers/post' import { createCallerFactory, createTRPCRouter } from '~/server/api/trpc' +import { userInformationRouter } from '~/server/api/routers/applicationForm/procedures/setUserInformation' +import { subscribeToNewsletterRouter } from '~/server/api/routers/subscribeToNewsLetter/procedures/subscribeToNewsLetter' /** * This is the primary router for your server. @@ -8,6 +10,8 @@ import { createCallerFactory, createTRPCRouter } from '~/server/api/trpc' */ export const appRouter = createTRPCRouter({ post: postRouter, + userInformation: userInformationRouter, + subscribeToNewsletter: subscribeToNewsletterRouter, }) // export type definition of API diff --git a/apps/hackathon/src/server/api/routers/subscribeToNewsLetter/procedures/subscribeToNewsLetter.ts b/apps/hackathon/src/server/api/routers/subscribeToNewsLetter/procedures/subscribeToNewsLetter.ts new file mode 100644 index 0000000..4dece7a --- /dev/null +++ b/apps/hackathon/src/server/api/routers/subscribeToNewsLetter/procedures/subscribeToNewsLetter.ts @@ -0,0 +1,47 @@ +import process from 'node:process' +import { z } from 'zod' +import aws from 'aws-sdk' +import { + protectedProcedure, +} from '~/server/api/trpc' + +aws.config.update({ + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + region: process.env.AWS_REGION, +}) + +const ses = new aws.SES({ apiVersion: '2010-12-01' }) + +export const subscribeToNewsletterRouter = protectedProcedure + .input( + z.object({ + email: z.string().email(), + }), + ) + .mutation(async ({ input }) => { + const { email } = input + const params = { + Destination: { + ToAddresses: [email], + }, + Message: { + Body: { + Text: { Data: 'Thanks for subscribing to our newsletter!' }, + }, + Subject: { + Data: 'Welcome to CuHack Newsletter', + }, + }, + Source: process.env.EMAIL_SOURCE, + } + + try { + await ses.sendEmail(params).promise() + return { success: true } + } + catch (error) { + console.error('Error sending email:', error) + throw new Error('Failed to send email. Please try again later.') + } + })