diff --git a/src/app/cohorts/cohorts.module.css b/src/app/cohorts/cohorts.module.css index 4219980..c055b8f 100644 --- a/src/app/cohorts/cohorts.module.css +++ b/src/app/cohorts/cohorts.module.css @@ -31,6 +31,80 @@ color: hsl(var(--white)); } +.notificationFormContainer { + border: 5px solid hsl(var(--light-blue)); + max-width: 650px; + margin: 0 auto; + padding: 2rem; + border-radius: 2rem; +} + +.notificationFormContainer button { + margin-top: 1rem; + width: 100%; + background-color: hsl(var(--light-blue)); + color: hsl(var(--white)); + border: none; + border-radius: 5px; + padding: 1rem; +} + +.formGroup { + display: flex; + justify-content: space-between; + margin-bottom: 15px; + align-items: center; +} + +.formGroup label { + flex: 1; + text-align: left; +} + +.formGroup input, +.formGroup textarea, +.formGroup select { + flex: 2; + padding: 10px; + border: 1px solid #ccc; + border-radius: 4px; +} +.formGroup textarea { + resize: vertical; +} +.submit-btn { + display: block; + margin: 0 auto; + padding: 10px 20px; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} +.submit-btn:hover { + background-color: #0056b3; +} + +.recaptcha { + display: flex; + justify-content: center; +} + +.formMessage { + margin-top: 1rem; +} + +.errorMessage { + color: #dc3545; + font-size: 1rem; +} + +.successMessage { + color: #198754; + font-size: 1; +} + /* Mobile devices */ @media only screen and (max-width: 850px) { } diff --git a/src/app/cohorts/notificationForm.tsx b/src/app/cohorts/notificationForm.tsx new file mode 100644 index 0000000..24bbe1c --- /dev/null +++ b/src/app/cohorts/notificationForm.tsx @@ -0,0 +1,127 @@ +import { useRef, useState } from 'react'; +import ReCAPTCHA from 'react-google-recaptcha'; +import styles from './cohorts.module.css'; + +class NotificationFormClass { + name: string; + email: string; + token: string; + + constructor(name: string, email: string, token: string) { + this.name = name; + this.email = email; + this.token = token; + } +} + +type Message = { + message: string; + type: 'error' | 'success'; +}; + +const localEnv = + process.env.NODE_ENV === 'development' && + process.env.NEXT_PUBLIC_APPWRITE_HASKEY === 'false'; + +const siteKey = process.env.NEXT_PUBLIC_RECAPTCHA_SITEKEY ?? ''; + +export default function NotificationForm() { + const [formData, setFormData] = useState( + new NotificationFormClass('', '', '') + ); + const [message, setMessage] = useState(null); + + const captchaRef = useRef(null); + + const handleChange = (event: any) => { + const { name, value } = event.target; + setFormData({ ...formData, [name]: value }); + }; + + const handleSubmit = async (event: any) => { + event.preventDefault(); + const token = localEnv ? 'localEnv' : captchaRef.current?.getValue(); + if (!(token || localEnv)) { + console.log('display robot message'); + setMessage({ + message: 'Are you a robot? Please complete the reCAPTCHA', + type: 'error', + }); + return; + } + + formData.token = token ?? ''; + + try { + const responese = await fetch('/api/notificationForm', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + if (!responese.ok) { + setMessage({ + message: responese.statusText, + type: 'error', + }); + throw new Error(`Failed to submit form: ${responese.statusText}`); + } + + setMessage({ + message: 'Success! You will be notified when the next cohort opens.', + type: 'success', + }); + + setFormData(new NotificationFormClass('', '', '')); + } catch (error) { + console.error('error', error); + } + }; + + return ( +
+
+
+ + +
+
+ + +
+
+ {!localEnv && } +
+ +
+
+ {message?.message} +
+
+ ); +} diff --git a/src/app/cohorts/page.tsx b/src/app/cohorts/page.tsx index 50c359e..7ebda66 100644 --- a/src/app/cohorts/page.tsx +++ b/src/app/cohorts/page.tsx @@ -1,12 +1,13 @@ 'use client'; -import React from 'react'; +import React, { useMemo } from 'react'; import styles from './cohorts.module.css'; -import OfferingCard from '../components/offeringCard/offeringCard'; import CohortCard from '../components/cohortCard/cohortCard'; import Section from '../components/Section/section'; - -// TODO: Add appropriate links +import NotificationForm from './notificationForm'; +import { useQuery } from '@tanstack/react-query'; +import Button from '../components/button/button'; +import { useGlobalState } from '../hooks/useGlobalState/useGlobalState'; interface Group { id: number; @@ -16,12 +17,24 @@ interface Group { imageUrl?: string; } +interface CohortStatus { + documentId: number; + statusType: string; + message: string; + active: boolean; +} + type CohortData = { [year: number]: Group[]; }; -const cohortStatusMessage = - 'Cohorts are currently closed and registration will be announced in Discord when the next one opens.'; +const defaultCohortStatusMessage = { + documentId: 0, + statusType: 'closed', + message: + 'Cohorts are currently closed and registration will be announced in Discord when the next one opens.', + active: false, +} as CohortStatus; // Data for the cohorts, add more elements to each year as needed const cohortData: CohortData = { @@ -62,9 +75,31 @@ const cohortData: CohortData = { }, ], }; + export default function CohortPage() { const [selectedYear, setSelectedYear] = React.useState(2024); + const { actionLinks } = useGlobalState(); + + const { data: cohortStatusResponse, isLoading } = useQuery({ + queryKey: ['cohortStatus'], + queryFn: async () => { + const response = await fetch('/api/cohort', { cache: 'no-store' }); + return response.json(); + }, + }); + + const currentCohortStatusData = useMemo(() => { + if (!cohortStatusResponse) { + return defaultCohortStatusMessage; + } + return cohortStatusResponse ?? defaultCohortStatusMessage; + }, [cohortStatusResponse]); + + if (isLoading) { + return

Loading...

; + } + return ( <>
@@ -78,16 +113,28 @@ export default function CohortPage() {

- {/* - //TODO: Uncomment when we find a way to handle cohort registration and notifications

Cohort Information

- -
*/} +

+ {currentCohortStatusData.message} + {currentCohortStatusData.statusType === 'open' && ( +