feat: wip on client form
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
header {
|
||||||
|
background-color: #181818;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
box-shadow: 0 8px 12px #000000b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header img {
|
||||||
|
aspect-ratio: 1;
|
||||||
|
border: 2px solid #b39cd0;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
width: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
background-color: #181818;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 22.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
box-shadow: 0 4px 8px #0000001a;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .group {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
form label {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input {
|
||||||
|
color: #e4e4e4;
|
||||||
|
background-color: #282828;
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input[type='number']::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input[type='number']::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input[type='number'] {
|
||||||
|
appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: #ff6b6b;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
form button {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #b39cd0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,118 @@
|
|||||||
import '@/App.css';
|
import '@/App.css';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
|
// TODO: Maybe be good to require
|
||||||
|
// the user provide some sort of
|
||||||
|
// identifier so we can display
|
||||||
|
// that as part of the feed
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const [amount, setAmount] = useState<number | ''>('');
|
||||||
|
const [email, setEmail] = useState<string>('');
|
||||||
|
const [errors, setErrors] = useState<{ amount?: string; email?: string }>({});
|
||||||
|
|
||||||
|
const amountInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const emailInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
function handleAmountInput(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
errors.amount = undefined;
|
||||||
|
const value = parseInt(event.target.value);
|
||||||
|
setAmount(isNaN(value) ? '' : value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEmailInput(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
errors.email = undefined;
|
||||||
|
setEmail(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateForm() {
|
||||||
|
const newErrors: { amount?: string; email?: string } = {};
|
||||||
|
|
||||||
|
if (amount === '' || amount <= 0) {
|
||||||
|
newErrors.amount = 'Please enter a valid amount greater than 0.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email.trim() !== '' && emailInputRef.current?.validity.typeMismatch) {
|
||||||
|
newErrors.email = 'Please enter a valid email address.';
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrors(newErrors);
|
||||||
|
|
||||||
|
const isValid = Object.keys(newErrors).length === 0;
|
||||||
|
|
||||||
|
if (isValid === false) {
|
||||||
|
if (newErrors.amount) {
|
||||||
|
amountInputRef.current?.focus();
|
||||||
|
} else if (newErrors.email) {
|
||||||
|
emailInputRef.current?.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const isValid = validateForm();
|
||||||
|
|
||||||
|
if (isValid === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: We will stop displaying our form
|
||||||
|
// and we will initialize the stripe embedded
|
||||||
|
// elements and pass along the amount and email
|
||||||
|
alert(`Form is valid! Amount: ${amount}, Email: ${email}`);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>Hello World</div>
|
<>
|
||||||
|
<header>
|
||||||
|
<img src="https://github.com/StevanFreeborn.png" alt="Logo" />
|
||||||
|
<p>Some information about me</p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
noValidate
|
||||||
|
>
|
||||||
|
<div className='group'>
|
||||||
|
<label htmlFor='amount'>Amount</label>
|
||||||
|
<input
|
||||||
|
ref={amountInputRef}
|
||||||
|
id='amount'
|
||||||
|
type='number'
|
||||||
|
min={1}
|
||||||
|
aria-describedby='amountErrorMessage'
|
||||||
|
aria-invalid={errors.amount ? 'true' : 'false'}
|
||||||
|
value={amount}
|
||||||
|
onInput={handleAmountInput}
|
||||||
|
/>
|
||||||
|
<span className='error-message'>{errors.amount}</span>
|
||||||
|
</div>
|
||||||
|
<div className='group'>
|
||||||
|
<label htmlFor='email'>Email</label>
|
||||||
|
<input
|
||||||
|
ref={emailInputRef}
|
||||||
|
id='email'
|
||||||
|
type='email'
|
||||||
|
aria-describedby='emailErrorMessage'
|
||||||
|
aria-invalid={errors.email ? 'true' : 'false'}
|
||||||
|
value={email}
|
||||||
|
onInput={handleEmailInput}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
id='emailErrorMessage'
|
||||||
|
className='error-message'
|
||||||
|
>
|
||||||
|
{errors.email}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button type='submit'>Buy</button>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'CaskaydiaCove NFM';
|
||||||
|
src: url('/CaskaydiaCoveNFM-Regular.eot');
|
||||||
|
src: url('/CaskaydiaCoveNFM-Regular.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('/CaskaydiaCoveNFM-Regular.woff2') format('woff2'),
|
||||||
|
url('/CaskaydiaCoveNFM-Regular.woff') format('woff'),
|
||||||
|
url('/CaskaydiaCoveNFM-Regular.svg#CaskaydiaCoveNFM-Regular') format('svg');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
/* CSS Reset */
|
/* CSS Reset */
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -16,6 +28,9 @@ button {
|
|||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -40,6 +55,23 @@ table {
|
|||||||
/* Base Styles */
|
/* Base Styles */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
--bg-color: #323232;
|
||||||
|
--text-color: #E4E4E4;
|
||||||
|
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: 'CaskaydiaCove NFM', monospace;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 1rem;
|
||||||
|
padding-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user