Skip to content

Commit

Permalink
Feat/create form (#6)
Browse files Browse the repository at this point in the history
* add simple application form
  • Loading branch information
skumpulainen authored Nov 5, 2024
1 parent d0e3022 commit bf03bab
Show file tree
Hide file tree
Showing 13 changed files with 311 additions and 13 deletions.
16 changes: 16 additions & 0 deletions src/web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@stytch/vanilla-js": "^5.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "7.53.0",
"react-router-dom": "^6.26.2"
},
"devDependencies": {
Expand Down
23 changes: 23 additions & 0 deletions src/web/src/applicationForm/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export interface IApplicationForm {
id: number
name: string
description: string
questions: IQuestion[]
state: string
}

export interface IQuestion {
title: string
responseType: string
}

export enum ResponseType {
TextField = "TextField",
Radio = "Radio",
Dropdown = "Dropdown",
}

export enum ApplicationState {
Draft = "Draft",
Ready = "Ready",
}
76 changes: 76 additions & 0 deletions src/web/src/components/ApplicationForm/ApplicationForm.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
.application-form {
max-width: 80%;
margin: 0 auto;
}

.application-form button {
cursor: pointer;
}

.application-form label {
text-align: left;
display: block;
margin: 20px 0 10px 0;
}

.application-form input,
.application-form textarea {
display: block;
box-sizing: border-box;
width: 100%;
padding: 5px 8px 5px;
border: 1px solid var(--trey-greige);
font-size: 16px;
height: 34px;
}

.error-message {
margin: 8px 0 0 0;
text-align: start;
font-size: 12px;
color: red;
}

.question-area {
margin-top: 20px;
margin-bottom: 20px;
position: relative;
}

.question-input-area {
display: flex;
}

.question-input-area :first-child {
flex: 1;
}

.question-area select {
border-color: var(--trey-greige);
margin: 0 10px 0 10px;
height: 34px;
cursor: pointer;
}

.button-container {
display: flex;
justify-content: space-between;
margin: 20px 0 20px 0;
}

.form-table {
border-collapse: collapse;
width: 100%;
margin-bottom: 30px;
}

.form-table td,
th {
border: 1px solid var(--trey-greige);
text-align: left;
padding: 8px;
}

.form-table tr:nth-child(even) {
background-color: rgba(214, 212, 203, 0.2);
}
118 changes: 118 additions & 0 deletions src/web/src/components/ApplicationForm/ApplicationForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React, { BaseSyntheticEvent, useState } from "react"
import { useFieldArray, useForm } from "react-hook-form"
import { ApplicationState, IApplicationForm, ResponseType } from "../../applicationForm/types"
import { Button } from "../Button/Button"
import styles from "./ApplicationForm.module.css"
import { ApplicationFormTable } from "./ApplicationFormTable"

export const ApplicationForm: React.FC = () => {
const {
register,
control,
handleSubmit,

formState: { errors },
} = useForm<IApplicationForm>({
defaultValues: {
name: "",
description: "",
state: ApplicationState.Draft,
questions: [{ title: "", responseType: ResponseType.TextField }],
},
mode: "onSubmit",
})

const [formData, setFormData] = useState<IApplicationForm>()

const { fields, append, remove } = useFieldArray({
name: "questions",
control,
})

const onSubmit = (
data: IApplicationForm,
e: BaseSyntheticEvent<object, any, any> | undefined,
) => {
if (e) {
setFormData(data)
}
}

const preventEnterKeySubmit = (
e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
) => {
if (e.key === "Enter") {
e.preventDefault()
}
}

return (
<>
<form
onSubmit={(event) => void handleSubmit(onSubmit)(event)}
className={styles["application-form"]}
>
<section>
<h2>Hakemuksen perustiedot</h2>
<label htmlFor="name">Hakemuksen nimi</label>
<input
onKeyDown={preventEnterKeySubmit}
aria-label="Kirjoita hakemuksen nimi"
{...register("name", { required: true })}
placeholder="Kirjoita hakemuksen nimi"
/>
{errors.name && <p className={styles["error-message"]}>Pakollinen kenttä</p>}
<label htmlFor="description">Hakemuksen kuvaus</label>
<textarea
onKeyDown={preventEnterKeySubmit}
aria-label="Kirjoita hakemuksen kuvaus"
{...register("description", { required: true })}
placeholder="Kirjoita hakemuksen kuvaus"
/>
{errors.description && <p className={styles["error-message"]}>Pakollinen kenttä</p>}
</section>
<section>
<h2>Hakemuksen kysymykset</h2>
{fields.map((question, i) => (
<div key={question.id} className={styles["question-area"]}>
<div className={styles["question-input-area"]}>
<input
onKeyDown={preventEnterKeySubmit}
aria-label="Kirjoita kysymys"
placeholder="Kysymyksen otsikko"
{...register(`questions.${i}.title`, { required: true })}
/>
<select
onKeyDown={preventEnterKeySubmit}
{...register(`questions.${i}.responseType`, { required: true })}
>
<option value={ResponseType.TextField}>Tekstikenttä</option>
</select>
<Button disabled={i === 0} variant="secondary" onClick={() => remove(i)}>
Poista
</Button>
</div>

{errors.questions && errors.questions[i]?.title?.type === "required" && (
<p className={styles["error-message"]}>Pakollinen kenttä</p>
)}
</div>
))}
</section>
<div className={styles["button-container"]}>
<Button
variant="primary"
type="button"
onClick={() => append({ title: "", responseType: ResponseType.TextField })}
>
+ Lisää kysymyksiä
</Button>
<Button variant="primary" type="submit">
Tallenna hakemus
</Button>
</div>
</form>
{formData && <ApplicationFormTable formData={formData} />}
</>
)
}
33 changes: 33 additions & 0 deletions src/web/src/components/ApplicationForm/ApplicationFormTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react"
import { IApplicationForm } from "../../applicationForm/types"
import styles from "./ApplicationForm.module.css"

interface IApplicationFormTable {
formData: IApplicationForm
}
export const ApplicationFormTable: React.FC<IApplicationFormTable> = ({ formData }) => {
return (
<table className={styles["form-table"]}>
<tbody>
<tr>
<th>Hakemuksen nimi</th>
<th>Hakemuksen kuvaus</th>
</tr>
<tr>
<td>{formData?.name}</td>
<td>{formData?.description}</td>
</tr>
<tr>
<th>Kysymys</th>
<th>Vastaustyyppi</th>
</tr>
{formData?.questions.map((question, i) => (
<tr key={`question_${i}`}>
<td>{question.title}</td>
<td>{question.responseType}</td>
</tr>
))}
</tbody>
</table>
)
}
13 changes: 13 additions & 0 deletions src/web/src/components/Button/Button.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.primary {
background-color: rgba(214, 212, 203, 0.2);
border: 1px solid var(--trey-greige);
border-radius: 3px;
padding: 6px 18px;
}

.secondary {
composes: primary;
background-color: transparent;
border: 1px solid red;
color: red;
}
13 changes: 13 additions & 0 deletions src/web/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from "react"
import styles from "./Button.module.css"

export interface IButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant: string
}
export const Button: React.FC<IButton> = ({ children, onClick, type, variant }) => {
return (
<button type={type} className={styles[variant]} onClick={onClick}>
{children}
</button>
)
}
10 changes: 9 additions & 1 deletion src/web/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { Container } from "@radix-ui/themes"
import { ApplicationForm } from "./ApplicationForm/ApplicationForm"

const Dashboard = () => {
return <h1>Dashboard</h1>
return (
<Container align="center">
<h1>Luo hakemus</h1>
<ApplicationForm />
</Container>
)
}

export default Dashboard
3 changes: 3 additions & 0 deletions src/web/src/components/Layout/Layout.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.page-container {
background-color: var(--white);
}
4 changes: 2 additions & 2 deletions src/web/src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react"
import { Container } from "@radix-ui/themes"
import { Header } from "../Header/Header"
import styles from "./Layout.module.css"

interface ILayout {
children: React.ReactNode
Expand All @@ -10,7 +10,7 @@ export const Layout: React.FC<ILayout> = ({ children }) => {
return (
<>
<Header />
<Container align="center">{children}</Container>
<div className={styles["page-container"]}>{children}</div>
</>
)
}
9 changes: 3 additions & 6 deletions src/web/src/components/Navigation/Navigation.module.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
/* reset */
button,
p {
all: unset;
}

.navigation {
position: relative;
display: flex;
Expand Down Expand Up @@ -53,4 +47,7 @@ p {
justify-content: space-between;
gap: 2px;
cursor: pointer;
border: none;
background-color: transparent;
font-size: 16px;
}
5 changes: 1 addition & 4 deletions src/web/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
--trey-yellow: #ffcc00;
--white: #ffffff;
--blue-focus: #4d4aff;
--trey-greige: #d6d4cb;
}

body {
Expand All @@ -15,7 +16,3 @@ body {
margin: 0;
padding: 0;
}

.rt-ContainerInner {
background-color: var(--white);
}

0 comments on commit bf03bab

Please sign in to comment.