-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #32 from osakunta/next-site
Refactor cards, styling changes, and page blocking
- Loading branch information
Showing
34 changed files
with
1,487 additions
and
849 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import { Contact } from "@/lib/cmsClient"; | ||
import useTranslate from "@/hooks/useTranslate"; | ||
import { | ||
TableContainer, | ||
Table, | ||
TableHead, | ||
TableRow, | ||
TableCell, | ||
TableBody, | ||
TextField, | ||
TableSortLabel, | ||
} from "@mui/material"; | ||
import styles from "@/styles/contactTable.module.css"; | ||
import { useMemo, useState } from "react"; | ||
|
||
export type ContactTableProps = { | ||
contactData: Contact[]; | ||
}; | ||
|
||
type Order = "asc" | "desc"; | ||
|
||
const ContactTable = ({ contactData }: ContactTableProps) => { | ||
const t = useTranslate(); | ||
const [searchQuery, setSearchQuery] = useState(""); | ||
const [order, setOrder] = useState<Order>("asc"); | ||
const [orderBy, setOrderBy] = useState<keyof Contact>("first_name"); | ||
|
||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
setSearchQuery(event.target.value); | ||
}; | ||
|
||
const handleSort = (property: keyof Contact) => { | ||
const isAsc = orderBy === property && order === "asc"; | ||
setOrder(isAsc ? "desc" : "asc"); | ||
setOrderBy(property); | ||
}; | ||
|
||
const filteredData = useMemo( | ||
() => | ||
contactData | ||
.filter((contact) => | ||
["first_name", "last_name", "contact", "label_key"].some((key) => | ||
contact[key as keyof Contact] | ||
?.toString() | ||
.toLowerCase() | ||
.includes(searchQuery.toLowerCase()), | ||
), | ||
) | ||
.sort((a, b) => { | ||
if (a[orderBy] < b[orderBy]) return order === "asc" ? -1 : 1; | ||
if (a[orderBy] > b[orderBy]) return order === "asc" ? 1 : -1; | ||
return 0; | ||
}), | ||
[searchQuery, order, orderBy, contactData], | ||
); | ||
|
||
return ( | ||
<div className={styles.tableContainer}> | ||
<TableContainer className={styles.table}> | ||
<div className={styles.tableLabel}> | ||
<h2>{t("contact:tableLabel")}</h2> | ||
<TextField | ||
label={t("contact:searchLabel")} | ||
fullWidth | ||
value={searchQuery} | ||
onChange={handleSearchChange} | ||
className={styles.searchBar} | ||
sx={{ | ||
"& .MuiOutlinedInput-root": { | ||
height: "2.5rem", | ||
"& fieldset": { | ||
borderRadius: "5rem", | ||
}, | ||
"&.Mui-focused fieldset": { | ||
borderColor: "#132c43", // Border color on focus | ||
}, | ||
}, | ||
"& .MuiInputLabel-root": { | ||
lineHeight: "1rem", // Align the label vertically to the center | ||
}, | ||
"& .MuiInputLabel-root.Mui-focused": { | ||
color: "#132c43", // Label color on focus | ||
}, | ||
}} | ||
/> | ||
</div> | ||
<Table> | ||
<TableHead className={styles.tableHead}> | ||
<TableRow> | ||
<TableCell> | ||
<TableSortLabel | ||
active={orderBy === "label_key"} | ||
direction={order} | ||
onClick={() => handleSort("label_key")} | ||
> | ||
{t("contact:title")} | ||
</TableSortLabel> | ||
</TableCell> | ||
<TableCell> | ||
<TableSortLabel | ||
active={orderBy === "first_name"} | ||
direction={order} | ||
onClick={() => handleSort("first_name")} | ||
> | ||
{t("contact:firstName")} | ||
</TableSortLabel> | ||
</TableCell> | ||
<TableCell> | ||
<TableSortLabel | ||
active={orderBy === "last_name"} | ||
direction={order} | ||
onClick={() => handleSort("last_name")} | ||
> | ||
{t("contact:lastName")} | ||
</TableSortLabel> | ||
</TableCell> | ||
<TableCell> | ||
<TableSortLabel | ||
active={orderBy === "contact"} | ||
direction={order} | ||
onClick={() => handleSort("contact")} | ||
> | ||
{t("contact:contactLabel")} | ||
</TableSortLabel> | ||
</TableCell> | ||
</TableRow> | ||
</TableHead> | ||
<TableBody> | ||
{filteredData.map((contact) => ( | ||
<TableRow key={contact.id}> | ||
<TableCell>{t(contact.label_key)}</TableCell> | ||
<TableCell>{contact.first_name}</TableCell> | ||
<TableCell>{contact.last_name}</TableCell> | ||
<TableCell>{contact.contact}</TableCell> | ||
</TableRow> | ||
))} | ||
</TableBody> | ||
</Table> | ||
</TableContainer> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ContactTable; |
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,243 @@ | ||
import { useState } from "react"; | ||
import { Form, Formik, FormikHelpers } from "formik"; | ||
import { | ||
TextField, | ||
Button, | ||
Switch, | ||
FormControlLabel, | ||
Alert, | ||
Snackbar, | ||
} from "@mui/material"; | ||
import * as Yup from "yup"; | ||
import styles from "@/styles/harassmentForm.module.css"; | ||
|
||
interface Values { | ||
incident: string; | ||
partiesInvolved: string; | ||
wantsAnswer: boolean; | ||
name: string; | ||
email: string; | ||
honeypot1: string; | ||
honeypot2: string; | ||
} | ||
|
||
const validationSchema = Yup.object({ | ||
email: Yup.string() | ||
.email("Invalid email address") | ||
.when("wantsAnswer", (wantsAnswer, schema) => | ||
wantsAnswer ? schema.required("Email is required") : schema, | ||
), | ||
name: Yup.string().when("wantsAnswer", (wantsAnswer, schema) => | ||
wantsAnswer ? schema.required("Name is required") : schema, | ||
), | ||
}); | ||
|
||
const noValidationSchema = Yup.object({ | ||
email: Yup.string(), | ||
name: Yup.string(), | ||
}); | ||
|
||
const HarassmentForm = () => { | ||
const [wantsAnswer, setWantsAnswer] = useState(false); | ||
const [snackbarOpen, setSnackbarOpen] = useState(false); | ||
const [snackbarMessage, setSnackbarMessage] = useState(""); | ||
const [snackbarSeverity, setSnackbarSeverity] = useState<"success" | "error">( | ||
"success", | ||
); | ||
const [buttonText, setButtonText] = useState("Submit"); | ||
const [buttonDisabled, setButtonDisabled] = useState(false); | ||
const corsProxy: string = "https://cors-anywhere.herokuapp.com/"; | ||
|
||
const handleSnackbarClose = () => { | ||
setSnackbarOpen(false); | ||
}; | ||
|
||
return ( | ||
<> | ||
<Formik | ||
initialValues={{ | ||
incident: "", | ||
partiesInvolved: "", | ||
wantsAnswer: false, | ||
name: "", | ||
email: "", | ||
honeypot1: "", | ||
honeypot2: "", | ||
}} | ||
validationSchema={wantsAnswer ? validationSchema : noValidationSchema} | ||
onSubmit={( | ||
values: Values, | ||
{ setSubmitting, resetForm }: FormikHelpers<Values>, | ||
) => { | ||
if (values.honeypot1 || values.honeypot2) { | ||
setSnackbarMessage("Service denied"); | ||
setSnackbarSeverity("error"); | ||
setSnackbarOpen(true); | ||
setSubmitting(false); | ||
setButtonText("Submit"); | ||
setButtonDisabled(false); | ||
return; | ||
} | ||
|
||
setButtonText("Sending"); | ||
setButtonDisabled(true); | ||
|
||
const formData = new FormData(); | ||
formData.append("entry.898010333", values.incident); // Example ID for 'incident' | ||
formData.append("entry.1968992256", values.partiesInvolved); // Example ID for 'partiesInvolved' | ||
formData.append("entry.200517309", values.name); // Example ID for 'name' | ||
formData.append("entry.1828602041", values.email); // Example ID for 'email' | ||
|
||
fetch(`${corsProxy + process.env.NEXT_PUBLIC_HARASSMENT_FORM_URL}`, { | ||
method: "POST", | ||
body: formData, | ||
}) | ||
.then((response) => { | ||
if (response.ok) { | ||
setSnackbarMessage( | ||
"Your report has been submitted successfully.", | ||
); | ||
setSnackbarSeverity("success"); | ||
resetForm(); // Clear the form on successful submission | ||
} else { | ||
setSnackbarMessage( | ||
"There was a problem submitting your report.", | ||
); | ||
setSnackbarSeverity("error"); | ||
} | ||
setSnackbarOpen(true); | ||
}) | ||
.catch((error) => { | ||
console.error("An error occurred:", error); | ||
setSnackbarMessage("An error occurred. Please try again later."); | ||
setSnackbarSeverity("error"); | ||
setSnackbarOpen(true); | ||
}) | ||
.finally(() => { | ||
setSubmitting(false); | ||
setButtonText("Submit"); | ||
setButtonDisabled(false); | ||
}); | ||
}} | ||
> | ||
{({ values, handleChange, handleBlur, touched, errors }) => ( | ||
<Form className={styles.form}> | ||
{/* Honeypot Fields */} | ||
<div style={{ display: "none" }}> | ||
<TextField | ||
id="honeypot1" | ||
name="honeypot1" | ||
value={values.honeypot1} | ||
onChange={handleChange} | ||
onBlur={handleBlur} | ||
/> | ||
<TextField | ||
id="honeypot2" | ||
name="honeypot2" | ||
value={values.honeypot2} | ||
onChange={handleChange} | ||
onBlur={handleBlur} | ||
/> | ||
</div> | ||
|
||
<TextField | ||
fullWidth | ||
id="incident" | ||
name="incident" | ||
label="Tell about what happened in your own words" | ||
multiline | ||
rows={4} | ||
variant="outlined" | ||
value={values.incident} | ||
onChange={handleChange} | ||
onBlur={handleBlur} | ||
error={touched.incident && Boolean(errors.incident)} | ||
helperText={touched.incident && errors.incident} | ||
className={styles.inputField} | ||
/> | ||
|
||
<TextField | ||
fullWidth | ||
id="partiesInvolved" | ||
name="partiesInvolved" | ||
label="Were there other people there?" | ||
multiline | ||
rows={4} | ||
variant="outlined" | ||
value={values.partiesInvolved} | ||
onChange={handleChange} | ||
onBlur={handleBlur} | ||
error={touched.partiesInvolved && Boolean(errors.partiesInvolved)} | ||
helperText={touched.partiesInvolved && errors.partiesInvolved} | ||
className={styles.inputField} | ||
/> | ||
|
||
<FormControlLabel | ||
control={ | ||
<Switch | ||
checked={wantsAnswer} | ||
onChange={() => setWantsAnswer(!wantsAnswer)} | ||
name="wantsAnswer" | ||
color="success" | ||
/> | ||
} | ||
label="I want to be answered" | ||
className={styles.switchLabel} | ||
/> | ||
|
||
{wantsAnswer && ( | ||
<> | ||
<TextField | ||
fullWidth | ||
id="name" | ||
name="name" | ||
label="Your Name" | ||
variant="outlined" | ||
value={values.name} | ||
onChange={handleChange} | ||
onBlur={handleBlur} | ||
error={touched.name && Boolean(errors.name)} | ||
helperText={touched.name && errors.name} | ||
className={styles.inputField} | ||
/> | ||
|
||
<TextField | ||
fullWidth | ||
id="email" | ||
name="email" | ||
label="Email" | ||
variant="outlined" | ||
value={values.email} | ||
onChange={handleChange} | ||
onBlur={handleBlur} | ||
error={touched.email && Boolean(errors.email)} | ||
helperText={touched.email && errors.email} | ||
className={styles.inputField} | ||
/> | ||
</> | ||
)} | ||
|
||
<Button | ||
type="submit" | ||
className="button darkBlue" | ||
disabled={buttonDisabled} | ||
> | ||
{buttonText} | ||
</Button> | ||
</Form> | ||
)} | ||
</Formik> | ||
<Snackbar | ||
open={snackbarOpen} | ||
autoHideDuration={6000} | ||
onClose={handleSnackbarClose} | ||
> | ||
<Alert onClose={handleSnackbarClose} severity={snackbarSeverity}> | ||
{snackbarMessage} | ||
</Alert> | ||
</Snackbar> | ||
</> | ||
); | ||
}; | ||
|
||
export default HarassmentForm; |
Oops, something went wrong.