diff options
| author | Armand Philippot <git@armandphilippot.com> | 2021-12-16 13:40:12 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2021-12-16 13:40:12 +0100 |
| commit | eb459cc248a5940a14193b20d263ffee3d345026 (patch) | |
| tree | 92ea7b91d4481b4b61f86bef5299227e0bca1c3d | |
| parent | 94f1c1fbea903e677476a167e51b549bb0c53330 (diff) | |
chore: create contact page
| -rw-r--r-- | src/components/Buttons/ButtonSubmit/ButtonSubmit.tsx | 11 | ||||
| -rw-r--r-- | src/components/Buttons/Buttons.module.scss | 34 | ||||
| -rw-r--r-- | src/components/Buttons/index.tsx | 3 | ||||
| -rw-r--r-- | src/components/Form/Form.module.scss | 31 | ||||
| -rw-r--r-- | src/components/Form/Form.tsx | 16 | ||||
| -rw-r--r-- | src/components/Form/FormItem/FormItem.tsx | 7 | ||||
| -rw-r--r-- | src/components/Form/Input/Input.tsx | 47 | ||||
| -rw-r--r-- | src/components/Form/TextArea/TextArea.tsx | 42 | ||||
| -rw-r--r-- | src/components/Form/index.tsx | 6 | ||||
| -rw-r--r-- | src/config/seo.ts | 4 | ||||
| -rw-r--r-- | src/pages/contact.tsx | 104 |
11 files changed, 305 insertions, 0 deletions
diff --git a/src/components/Buttons/ButtonSubmit/ButtonSubmit.tsx b/src/components/Buttons/ButtonSubmit/ButtonSubmit.tsx new file mode 100644 index 0000000..fd7fc62 --- /dev/null +++ b/src/components/Buttons/ButtonSubmit/ButtonSubmit.tsx @@ -0,0 +1,11 @@ +import styles from '../Buttons.module.scss'; + +const ButtonSubmit: React.FunctionComponent = ({ children }) => { + return ( + <button type="submit" className={`${styles.btn} ${styles.submit}`}> + {children} + </button> + ); +}; + +export default ButtonSubmit; diff --git a/src/components/Buttons/Buttons.module.scss b/src/components/Buttons/Buttons.module.scss new file mode 100644 index 0000000..93dfb9d --- /dev/null +++ b/src/components/Buttons/Buttons.module.scss @@ -0,0 +1,34 @@ +@use "@styles/abstracts/functions" as fun; + +.btn { + display: block; + padding: var(--spacing-2xs) var(--spacing-md); + border: none; + font-size: var(--font-size-md); +} + +.submit { + margin: auto; + background: var(--color-primary); + border-radius: fun.convert-px(3); + box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-primary-dark); + color: var(--color-fg-inverted); + font-weight: 600; + text-shadow: fun.convert-px(2) fun.convert-px(2) 0 var(--color-shadow); + transition: all 0.3s ease-in-out 0s; + + &:hover, + &:focus { + background: var(--color-primary-light); + box-shadow: fun.convert-px(7) fun.convert-px(7) 0 0 + var(--color-primary-dark); + transform: translateX(#{fun.convert-px(-4)}) + translateY(#{fun.convert-px(-4)}); + } + + &:active { + background: var(--color-primary-dark); + box-shadow: 0 0 0 0 var(--color-primary-dark); + transform: translateX(#{fun.convert-px(3)}) translateY(#{fun.convert-px(3)}); + } +} diff --git a/src/components/Buttons/index.tsx b/src/components/Buttons/index.tsx new file mode 100644 index 0000000..ea145ca --- /dev/null +++ b/src/components/Buttons/index.tsx @@ -0,0 +1,3 @@ +import ButtonSubmit from './ButtonSubmit/ButtonSubmit'; + +export { ButtonSubmit }; diff --git a/src/components/Form/Form.module.scss b/src/components/Form/Form.module.scss new file mode 100644 index 0000000..f23dfde --- /dev/null +++ b/src/components/Form/Form.module.scss @@ -0,0 +1,31 @@ +@use "@styles/abstracts/functions" as fun; + +.wrapper { + width: 100%; +} + +.item { + margin: var(--spacing-xs) 0; + max-width: 45ch; +} + +.label { + display: block; + font-size: var(--font-size-sm); + font-variant: small-caps; + font-weight: 600; +} + +.field { + border: fun.convert-px(1) solid var(--color-border); + width: 100%; + padding: var(--spacing-2xs) var(--spacing-xs); + + &:focus { + border-color: var(--color-primary); + } +} + +.textarea { + min-height: fun.convert-px(200); +} diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx new file mode 100644 index 0000000..cc97526 --- /dev/null +++ b/src/components/Form/Form.tsx @@ -0,0 +1,16 @@ +import { FormEvent } from 'react'; +import styles from './Form.module.scss'; + +const Form: React.FunctionComponent = ({ children }) => { + const submitForm = (e: FormEvent) => { + e.preventDefault(); + }; + + return ( + <form onSubmit={submitForm} className={styles.wrapper}> + {children} + </form> + ); +}; + +export default Form; diff --git a/src/components/Form/FormItem/FormItem.tsx b/src/components/Form/FormItem/FormItem.tsx new file mode 100644 index 0000000..0f12e64 --- /dev/null +++ b/src/components/Form/FormItem/FormItem.tsx @@ -0,0 +1,7 @@ +import styles from '../Form.module.scss'; + +const FormItem: React.FunctionComponent = ({ children }) => { + return <div className={styles.item}>{children}</div>; +}; + +export default FormItem; diff --git a/src/components/Form/Input/Input.tsx b/src/components/Form/Input/Input.tsx new file mode 100644 index 0000000..901b9ab --- /dev/null +++ b/src/components/Form/Input/Input.tsx @@ -0,0 +1,47 @@ +import { ChangeEvent, SetStateAction } from 'react'; +import styles from '../Form.module.scss'; + +type InputType = 'text' | 'number'; + +const Input = ({ + id, + name, + value, + setValue, + type = 'text', + required = false, + label, +}: { + id: string; + name: string; + value: string; + setValue: (value: SetStateAction<string>) => void; + type?: InputType; + required?: boolean; + label?: string; +}) => { + const updateValue = (e: ChangeEvent<HTMLInputElement>) => { + setValue(e.target.value); + }; + + return ( + <> + {label && ( + <label htmlFor={id} className={styles.label}> + {label} + {required && <span> *</span>} + </label> + )} + <input + type={type} + id={id} + name={name} + value={value} + onChange={updateValue} + className={styles.field} + /> + </> + ); +}; + +export default Input; diff --git a/src/components/Form/TextArea/TextArea.tsx b/src/components/Form/TextArea/TextArea.tsx new file mode 100644 index 0000000..729ef6d --- /dev/null +++ b/src/components/Form/TextArea/TextArea.tsx @@ -0,0 +1,42 @@ +import { ChangeEvent, SetStateAction } from 'react'; +import styles from '../Form.module.scss'; + +const TextArea = ({ + id, + name, + value, + setValue, + required = false, + label, +}: { + id: string; + name: string; + value: string; + setValue: (value: SetStateAction<string>) => void; + required?: boolean; + label?: string; +}) => { + const updateValue = (e: ChangeEvent<HTMLTextAreaElement>) => { + setValue(e.target.value); + }; + + return ( + <> + {label && ( + <label htmlFor={id} className={styles.label}> + {label} + {required && <span> *</span>} + </label> + )} + <textarea + id={id} + name={name} + value={value} + onChange={updateValue} + className={`${styles.field} ${styles.textarea}`} + /> + </> + ); +}; + +export default TextArea; diff --git a/src/components/Form/index.tsx b/src/components/Form/index.tsx new file mode 100644 index 0000000..987e013 --- /dev/null +++ b/src/components/Form/index.tsx @@ -0,0 +1,6 @@ +import Form from './Form'; +import FormItem from './FormItem/FormItem'; +import Input from './Input/Input'; +import TextArea from './TextArea/TextArea'; + +export { Form, FormItem, Input, TextArea }; diff --git a/src/config/seo.ts b/src/config/seo.ts index dc75cd2..f2a22a6 100644 --- a/src/config/seo.ts +++ b/src/config/seo.ts @@ -13,6 +13,10 @@ export const seo = { title: t`CV Front-end developer | Armand Philippot`, description: t`Discover the curriculum of Armand Philippot, front-end developer located in France: skills, experiences and training.`, }, + contact: { + title: t`Contact form | Armand Philippot`, + description: t`Contact Armand Philippot through its website. All you need to do it's to fill the contact form.`, + }, legalNotice: { title: t`Legal notice | Armand Philippot`, description: t`Discover the ArmandPhilippot.com website legal notice.`, diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx new file mode 100644 index 0000000..15e1ad5 --- /dev/null +++ b/src/pages/contact.tsx @@ -0,0 +1,104 @@ +import { ButtonSubmit } from '@components/Buttons'; +import { Form, FormItem, Input, TextArea } from '@components/Form'; +import Layout from '@components/Layouts/Layout'; +import { seo } from '@config/seo'; +import { t } from '@lingui/macro'; +import { NextPageWithLayout } from '@ts/types/app'; +import { loadTranslation } from '@utils/helpers/i18n'; +import { GetStaticProps, GetStaticPropsContext } from 'next'; +import Head from 'next/head'; +import { ReactElement, useState } from 'react'; + +const ContactPage: NextPageWithLayout = () => { + const [name, setName] = useState(''); + const [email, setEmail] = useState(''); + const [subject, setSubject] = useState(''); + const [message, setMessage] = useState(''); + + const resetForm = () => { + setName(''); + setEmail(''); + setSubject(''); + setMessage(''); + }; + + return ( + <> + <Head> + <title>{seo.contact.title}</title> + <meta name="description" content={seo.contact.description} /> + </Head> + <article> + <header> + <h1>{t`Contact`}</h1> + </header> + <div> + <p>{t`All fields marked with * are required.`}</p> + <Form> + <FormItem> + <Input + id="contact-name" + name="name" + value={name} + setValue={setName} + label={t`Name`} + required={true} + /> + </FormItem> + <FormItem> + <Input + id="contact-email" + name="email" + value={email} + setValue={setEmail} + label={t`Email`} + required={true} + /> + </FormItem> + <FormItem> + <Input + id="contact-subject" + name="subject" + value={subject} + setValue={setSubject} + label={t`Subject`} + /> + </FormItem> + <FormItem> + <TextArea + id="contact-message" + name="message" + value={message} + setValue={setMessage} + label={t`Message`} + required={true} + /> + </FormItem> + <FormItem> + <ButtonSubmit>{t`Send`}</ButtonSubmit> + </FormItem> + </Form> + </div> + </article> + </> + ); +}; + +ContactPage.getLayout = (page: ReactElement) => <Layout>{page}</Layout>; + +export const getStaticProps: GetStaticProps = async ( + context: GetStaticPropsContext +) => { + const translation = await loadTranslation( + context.locale!, + process.env.NODE_ENV === 'production' + ); + + return { + props: { + translation, + }, + }; +}; + +export default ContactPage; |
