diff --git a/src/GroundsForSupport.Client/src/App.css b/src/GroundsForSupport.Client/src/App.css index e69de29..ef377c2 100644 --- a/src/GroundsForSupport.Client/src/App.css +++ b/src/GroundsForSupport.Client/src/App.css @@ -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; +} diff --git a/src/GroundsForSupport.Client/src/App.tsx b/src/GroundsForSupport.Client/src/App.tsx index d53000e..fe0fb1a 100644 --- a/src/GroundsForSupport.Client/src/App.tsx +++ b/src/GroundsForSupport.Client/src/App.tsx @@ -1,8 +1,118 @@ 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() { + const [amount, setAmount] = useState(''); + const [email, setEmail] = useState(''); + const [errors, setErrors] = useState<{ amount?: string; email?: string }>({}); + + const amountInputRef = useRef(null); + const emailInputRef = useRef(null); + + function handleAmountInput(event: React.ChangeEvent) { + errors.amount = undefined; + const value = parseInt(event.target.value); + setAmount(isNaN(value) ? '' : value); + } + + function handleEmailInput(event: React.ChangeEvent) { + 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) { + 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 ( -
Hello World
+ <> +
+ Logo +

Some information about me

+
+
+
+
+ + + {errors.amount} +
+
+ + + + {errors.email} + +
+ +
+
+ ); } diff --git a/src/GroundsForSupport.Client/src/index.css b/src/GroundsForSupport.Client/src/index.css index fb9af47..7b39103 100644 --- a/src/GroundsForSupport.Client/src/index.css +++ b/src/GroundsForSupport.Client/src/index.css @@ -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 */ * { @@ -16,6 +28,9 @@ button { border: none; outline: none; cursor: pointer; + font-family: inherit; + font-size: inherit; + color: inherit; } a { @@ -40,6 +55,23 @@ table { /* Base Styles */ :root { + --bg-color: #323232; + --text-color: #E4E4E4; + + font-size: 16px; + font-family: 'CaskaydiaCove NFM', monospace; font-size: 16px; } +html, +body { + height: 100%; + background-color: var(--bg-color); + color: var(--text-color); +} + +main { + padding: 1rem; + padding-top: 2rem; +} +