9 Commits
Author SHA1 Message Date
auto 8fda5d9fb9 Initial appeal commit 2024-10-06 00:27:29 +02:00
auto ee54e91051 Add event application form
Implemented a new event application form with validations and necessary fields such as username, email, and event experience. Incorporated the form into the home page with a navigation link and updated form interfaces and data configurations.
2024-09-20 18:01:38 +02:00
auto 1c4f4b154b Enhance email verification layout styling
Added CSS styles for the fields and field containers to improve the layout and alignment of the email verification page. This change ensures a more structured and visually appealing presentation of the form data.
2024-08-11 19:13:49 +02:00
auto 30720d0738 Refactor type and string handling in ThankYou component
Changed the `DynamicFormData` type to accept any data type, enhancing flexibility. Updated string handling to be more explicit with type checks, ensuring newline handling works correctly for strings only.
2024-08-11 19:09:31 +02:00
auto 25c6a15413 Handle single-line and multi-line email verification messages
Previously, multi-line messages were parsed for line breaks while single-line messages were not appropriately handled. This update checks if the value contains line breaks and processes it accordingly, ensuring both single-line and multi-line messages are displayed correctly.
2024-08-11 19:01:00 +02:00
auto 821e63953b Refactor email verification UI to enhance data display
Replaced table with div and paragraph elements to improve readability of user-submitted data. Added logic to handle multiline form data and remove empty lines, ensuring cleaner and more user-friendly presentation.
2024-08-11 18:47:19 +02:00
auto ceaca60ece Add additional_info field to form requirements
Include extra guidance for the user to specify their timezone. This aims to reduce ambiguity and ensure better form submissions. The change is minimal but improves overall user experience.
2024-08-11 18:41:56 +02:00
auto b00b857f29 Add "Apply for staff" link to homepage
This commit enhances the homepage by adding a new link to the "Apply for staff" page, making it easier for visitors to find and access staff application information directly from the main page. The new link is placed alongside the existing "Contact us" link for better visibility.
2024-08-11 18:32:57 +02:00
auto cf7bdeb481 Enforce integer validation on age and average time fields
Added integer validation messages to the age and avg_time fields to ensure users enter whole numbers. This enhances the form's data integrity by preventing input errors.
2024-08-10 04:35:54 +02:00
12 changed files with 346 additions and 22 deletions
+2
View File
@@ -9,6 +9,7 @@ import DEBUG from "./components/DEBUG/DEBUG";
import {getFormProperties} from "./components/form/formData";
import {FormProperties} from "./components/form/formInterfaces";
import FormActiveRedirect from "./components/form/formActiveRedirect";
import Appeal from "./components/appeal";
function App() {
return (
@@ -26,6 +27,7 @@ function App() {
))}
<Route path="/verify-email" element={<VerifyMail/>}/>
<Route path="/thank-you" element={<ThankYou/>}/>
<Route path="/appeal" element={<Appeal/>}/>
{process.env.NODE_ENV === 'development' && <Route path="/debug" element={<DEBUG/>}/>}
</Routes>
</BrowserRouter>
+21
View File
@@ -0,0 +1,21 @@
// you might need to adjust imports according to your project structure
import React, { FunctionComponent } from 'react';
import { Helmet } from 'react-helmet';
const Appeal: FunctionComponent = () => {
return (
<div>
<Helmet>
<title>Appeals Selection</title>
<meta name="Appeals selection page" content="Choose the type of appeal"/>
</Helmet>
<header className="App-header">
<h1>Welcome to the Appeals page</h1>
<h2><a href="/appeal/minecraft">Appeal a Minecraft punishment.</a></h2>
<h2><a href="/appeal/discord">Appeal a Discord punishment.</a></h2>
</header>
</div>
);
}
export default Appeal;
+104
View File
@@ -0,0 +1,104 @@
import {FormData} from "../formInterfaces";
import * as Yup from "yup";
type PunishmentsForUser = {
punishments: string[]
};
type UserForPunishments = {
username: string
};
export const minecraft_appeal: FormData = {
steps: [
{
label: "What is your Minecraft username?",
additional_info: "Use the username you had when you last tried to join the server.",
name: "username",
type: "text",
min_length: 3,
max_length: 16,
required: true,
},
{
label: "What is your email?",
additional_info: "It does not have to be your minecraft email.",
name: "email",
type: "email",
min_length: 3,
max_length: 254,
required: true,
},
{
label: "What punishment would you like to appeal?",
additional_info: "Please select it below.",
name: "punishment",
type: "dropdown",
min_length: 10,
max_length: 2000,
required: true,
drop_down: [],
processInput: (input: string): Promise<string[]> => {
return new Promise((resolve, reject) => {
const userForPunishments: UserForPunishments = {
username: input,
}
fetch(`${process.env.REACT_APP_BACKEND_BASE_URL}/api/appeal/retrieve-minecraft-punishments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userForPunishments)
})
.then(response => {
if (!response.ok) {
console.log(response)
reject(new Error('Invalid username'));
}
return response.json()
})
.then((punishmentsForUser: PunishmentsForUser) => {
resolve(punishmentsForUser.punishments);
})
.catch(error => {
console.error('Received an unexpected error: ' + error);
reject(error);
})
});
}
},
{
label: "Why should your punishment be reduced or removed?",
additional_info: "Please take your time writing this, we're more likely to accept an appeal if effort was put into it.",
name: "appeal",
type: "textarea",
min_length: 2,
max_length: 2000,
required: true,
},
],
//TODO warning if no punishments
backend: `${process.env.REACT_APP_BACKEND_BASE_URL}/api/appeal/minecraft`,
userInput: {username: '', email: '', punishment: '', appeal: ''},
spec: Yup.object().shape({
username: Yup.string()
.min(3, 'Username should be at least 3 characters')
.max(16, 'Username should not exceed 16 characters')
.matches(/^[a-zA-Z0-9_]*$/, 'Username should only include alphanumeric characters and underscore')
.required('Username is required'),
email: Yup.string()
.email('Invalid email')
.min(3, 'Email should be at least 3 characters')
.max(254, 'Email should not exceed 254 characters')
.required('Email is required'),
punishment: Yup.string()
.required('You are required to select a punishment'),
appeal: Yup.string()
.min(3, 'Your appeal needs to be at least 100 characters')
.max(2000, 'Your appeal can not be longer than 2000 characters')
.required()
}),
title: "Minecraft Appeal",
backendFormName: "MinecraftAppeal",
};
+3
View File
@@ -89,6 +89,7 @@ export const apply: FormData = {
min_length: 3,
max_length: 256,
required: true,
additional_info: "Please include your timezone."
},
{
label: "Do you have any previous experience being staff?",
@@ -163,6 +164,7 @@ export const apply: FormData = {
age: Yup.number()
.typeError('Input must be a number')
.integer('Please enter a whole number')
.min(0, 'Please enter a valid age')
.max(999, 'We do not accept players older than 999 years old sorry!')
.required('You are required to fill out your age'),
@@ -175,6 +177,7 @@ export const apply: FormData = {
avg_time: Yup.number()
.typeError('Please enter a number')
.integer('Please enter a whole number')
.min(0, 'Please enter a positive number')
.max(168, 'There are only 168 hours in a week')
.required('Please enter the average time you will be available each week'),
+140
View File
@@ -0,0 +1,140 @@
import {FormData} from "../formInterfaces";
import * as Yup from "yup";
export const event_apply: FormData = {
steps: [
{
label: "What is your Minecraft username?",
name: "username",
type: "text",
min_length: 3,
max_length: 16,
required: true,
},
{
label: "What is your email?",
name: "email",
type: "email",
min_length: 3,
max_length: 254,
required: true,
},
{
label: "What is your Discord username?",
name: "discord",
type: "text",
min_length: 2,
max_length: 32,
required: true,
},
{
label: "What is your age?",
name: "age",
type: "text",
min_length: 1,
max_length: 3,
required: true,
},
{
label: "What is your preferred pronoun?",
name: "pronoun",
type: "dropdown",
min_length: 0,
max_length: 16,
required: true,
drop_down: ["They/them", "She/her", "He/him", ""],
},
{
label: "On average, how many hours per week are you available to help with events?",
name: "avg_time",
type: "text",
min_length: 1,
max_length: 2,
required: true,
},
{
label: "Do you have any previous experience running events of any kind?",
name: "event_experience",
type: "textarea",
min_length: 2,
max_length: 2000,
required: true,
},
{
label: "Are you able to use voice chat on Discord",
name: "discord_vc",
type: "dropdown",
min_length: 2,
max_length: 3,
required: true,
additional_info: "Use voice chat in Discord with reasonable audio quality\n" +
"Take screenshots through your pc (normally f2 in minecraft)\n" +
"Record your screen through your pc at 20fps+ and 720p+ at normal Minecraft settings (with free programs like OBS)\n",
drop_down: ["Yes", "No", ""],
},
{
label: "You may share anything else you wish here.",
name: "other",
type: "textarea",
min_length: 2,
max_length: 2000,
required: false,
},
],
backend: `${process.env.REACT_APP_BACKEND_BASE_URL}/api/apply/staffApplication`,
userInput: {
username: '', email: '', discord: ''
, age: '', pronoun: '', avg_time: '', event_experience: ''
, discord_vc: '', other: ''
},
spec: Yup.object().shape({
username: Yup.string()
.min(3, 'Username should be at least 3 characters')
.max(16, 'Username should not exceed 16 characters')
.matches(/^[a-zA-Z0-9_]*$/, 'Username should only include alphanumeric characters and underscore')
.required('Please enter your username'),
email: Yup.string()
.email('Invalid email')
.min(3, 'Email should be at least 3 characters')
.max(254, 'Email should not exceed 254 characters')
.required('Please enter your email'),
discord: Yup.string()
.min(2, 'Discord name should be at least 2 characters')
.max(32, 'Discord name should be at most 32 characters')
.matches(/^(?!.*\.\.)([a-z0-9._]{2,32})$/, 'Please enter a valid Discord name')
.required('Please enter your Discord name'),
age: Yup.number()
.typeError('Input must be a number')
.integer('Please enter a whole number')
.min(0, 'Please enter a valid age')
.max(999, 'We do not accept players older than 999 years old sorry!')
.required('You are required to fill out your age'),
pronoun: Yup.string()
.max(16, 'Pronouns can\'t be longer than 16 characters'),
avg_time: Yup.number()
.typeError('Please enter a number')
.integer('Please enter a whole number')
.min(0, 'Please enter a positive number')
.max(168, 'There are only 168 hours in a week')
.required('Please enter the average time you will be available each week'),
event_experience: Yup.string()
.min(2, 'Please provide your experience with running/working on events, if you don\'t have any you can say that instead')
.max(2000, 'Please provide at most 2000 characters')
.required('Please enter your event experience or simply say you don\'t have any'),
discord_vc: Yup.string()
.matches(/(yes|no)$/i, 'Yes or no')
.required('An answer is required'),
other: Yup.string()
.max(2000, 'Please provide at most 2000 characters')
}),
title: "Event Application",
backendFormName: "EventApplication",
};
+9 -7
View File
@@ -7,14 +7,16 @@ const FormActiveRedirect = (formData: FormData) => {
const [isFormActive, setFormActive] = useState(false);
const handleCheckForm = useCallback(async () => {
const result = await fetch(`${process.env.REACT_APP_BACKEND_BASE_URL}/api/checks/formActive`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ formName: formData.backendFormName })
});
const response = await result.json();
setFormActive(response.isActive);
setFormActive(true);
setLoading(false);
// const result = await fetch(`${process.env.REACT_APP_BACKEND_BASE_URL}/api/checks/formActive`, {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ formName: formData.backendFormName })
// });
// const response = await result.json();
// setFormActive(response.isActive);
// setLoading(false);
}, [formData.backendFormName]);
useEffect(() => {
+10
View File
@@ -1,6 +1,8 @@
import {FormProperties} from "./formInterfaces";
import {contact} from "./data/contact";
import {apply} from "./data/apply";
import {event_apply} from "./data/event_apply";
import {minecraft_appeal} from "./data/appeal";
const formProperties: FormProperties[] = [
{
@@ -11,6 +13,14 @@ const formProperties: FormProperties[] = [
path: 'apply',
formData: apply
},
{
path: 'event-apply',
formData: event_apply
},
{
path: 'appeal/minecraft',
formData: minecraft_appeal
},
]
export function getFormProperties(): FormProperties[] {
+23 -1
View File
@@ -1,4 +1,4 @@
import React, {useState} from "react";
import React, {useEffect, useState} from "react";
import './GenericForm.css';
import {Field} from "formik";
import {FormHandlerProps} from "./formInterfaces";
@@ -22,6 +22,22 @@ const FormHTML: React.FC<FormHandlerProps> = ({
const [selectedOptions, setSelectedOptions] = useState(fieldValues);
const [options, setOptions] = useState<string[]>([]);
useEffect(() => {
const { processInput, drop_down = [] } = steps[currentStep];
if (processInput) {
processInput(values['username']).then(newOptions => {
setOptions(newOptions);
//TODO be an unsetter for some error field
});
} else {
setOptions(drop_down);
console.log("No data") //TODO be a setter for some error field
}
}, [currentStep, steps]);
if (currentField.type === 'select') {
return (
<ReactSelect
@@ -65,6 +81,12 @@ const FormHTML: React.FC<FormHandlerProps> = ({
{option}
</option>
))}
{options.map((option, i) => (
<option key={i} value={option}>
{option}
</option>
))}
{options.length === 0 ? "<p>No data found, please check if your username is valid. Or if this was a discord punishment, please use the discord appeal form</p>":"<p></p>"}
</Field>
);
} else {
+2 -1
View File
@@ -3,7 +3,7 @@ import React from "react";
type InputNames = "username" | "email" | "question" | "discord" | "pc_requirements" | "age" | "pronoun" | "join_date" |
"avg_time" | "available_days" | "available_time" | "staff_experience" | "plugin_experience" | "why_staff" |
"expectations_mod" | "other";
"expectations_mod" | "event_experience" | "discord_vc" | "other" | "punishment" | "appeal";
export interface Step {
label: string;
@@ -15,6 +15,7 @@ export interface Step {
additional_info?: string;
drop_down?: string[]
multiple?: boolean
processInput?: (input: string) => Promise<string[]>;
}
export interface UserInput {
+3
View File
@@ -11,6 +11,9 @@ const Home: FunctionComponent = () => {
<header className="App-header">
<h1>Welcome to the Altitude forms page</h1>
<h2><a href="/contact">Contact us.</a></h2>
<h2><a href="/apply">Apply for staff.</a></h2>
<h2><a href="/event-apply">Apply for events.</a></h2>
<h2><a href="/appeal">Appeal a punishment.</a></h2>
</header>
</div>
);
+12
View File
@@ -37,4 +37,16 @@ table {
.form-data-value {
border: 1px solid #ddd;
padding: 10px;
}
.fields {
display: flex;
flex-direction: column;
align-items: flex-start;
max-width: 1020px;
width: 80%;
}
.field {
margin-bottom: 20px;
}
+17 -13
View File
@@ -6,7 +6,7 @@ import './ThankYou.css';
const ThankYou: FC = () => {
const location = useLocation();
type DynamicFormData = {
[key: string]: string;
[key: string]: any;
};
if (location.state === null || location.state.formData === undefined) {
@@ -26,19 +26,23 @@ const ThankYou: FC = () => {
</Helmet>
<header className="header">Thank you for completing the form and verifying your email!<br></br>This is the data you entered:</header>
<div className="fields">
<table>
<tbody>
{Object.entries(formData).map(([key, value]) => (
<tr className="form-data-row" id={key}>
<td className="form-data-key">{key}</td>
<td className="form-data-value">{value}</td>
</tr>
))}
</tbody>
</table>
{Object.entries(formData).map(([key, value]) => (
<div className={"field"}>
<p><strong>{key}</strong></p>
{
typeof value === "string" && value.includes("\n") ?
value
.split("\n")
.filter(str => str.trim() !== "")
.map((str, index) => <p style={{ wordWrap: 'break-word' }} key={index}>{str}</p>)
: <p style={{ wordWrap: 'break-word' }}>{value}</p>
}
</div>
))}
</div>
</div>
);
</div>
)
;
}