Add base app

This commit is contained in:
2022-04-24 23:12:11 +02:00
commit de8c03f342
33 changed files with 1380 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
import { FC } from 'react'
import NextLink from 'next/link'
interface LinkProps {
page: string
className: string
}
const Link: FC<LinkProps> = (props) => (
<NextLink href={props.page}>
<a className={props.className + ' underline hover:text-gray-800'}>
{props.children}
</a>
</NextLink>
)
export default Link

View File

@@ -0,0 +1 @@
export * as result from './result'

View File

@@ -0,0 +1,60 @@
import { result } from '.'
type Ok<T> = {
type: 'ok'
val: T
}
export const ok = <T>(val: T): Ok<T> => ({ type: 'ok', val })
type Err<T> = {
type: 'err'
err: T
}
export const err = <T>(err: T): Err<T> => ({ type: 'err', err })
export type Result<TOk, TErr> = Ok<TOk> | Err<TErr>
export const isOk = <TOk, TErr>(r: result.Result<TOk, TErr>): boolean => {
switch (r.type) {
case 'ok':
return true
default:
return false
}
}
export const getValue = <TOk, TErr>(r: result.Result<TOk, TErr>): TOk => {
if (r.type === 'ok') {
return r.val
}
throw new Error(`couldn't get value as type is error: ${r.err}`)
}
export const tap = <TOk, TErr>(
r: result.Result<TOk, TErr>,
okFunc: (okRes: TOk) => void,
errFunc: (errRes: TErr) => void = (err) => {
console.log("default tap catch of error, note tap doesn't break chain")
}
): result.Result<TOk, TErr> => {
switch (r.type) {
case 'ok':
okFunc(r.val)
break
case 'err':
errFunc(r.err)
break
}
return r
}
export const getError = <TOk, TErr = string>(
r: result.Result<TOk, TErr>
): TErr => {
if (r.type === 'err') {
return r.err
}
throw new Error(`couldn't get value as type is ok: ${r.val}`)
}

View File

@@ -0,0 +1,85 @@
import { FC, FormEvent, SyntheticEvent, useState } from 'react'
import { result } from '../../../common/result'
import { createWish, Wish } from '../models'
interface CreateWishProps {
onFocus: () => void
onSubmit: (wish: Wish) => void
}
const CreateWish: FC<CreateWishProps> = (props) => {
const [focused, setFocused] = useState<boolean>(false)
const onSubmit = (e: SyntheticEvent) => {
e.preventDefault()
const target = e.target as typeof e.target & {
wishName: { value: string }
wishDescription: { value?: string }
wishLink: { value?: string }
}
const wishRes = createWish(
target.wishName.value,
target.wishDescription.value,
target.wishLink.value
)
result.tap(wishRes, (wish) => {
props.onSubmit(wish)
})
}
return (
<div onFocus={() => setFocused(true)}>
<BasePrompt>
<form onSubmit={onSubmit}>
<div className="shadow p-4 space-y-6 bg-white">
<div className="p-1 rounded space-y-6 bg-pink-100">
<div className="bg-pink-100 transition-all">
<input
type="text"
name="wishName"
placeholder="Your wish!"
className="appearance-none w-full bg-pink-100 text-lg text-center p-6 outline-none"
/>
<div className={focused ? 'block' : 'hidden'}>
<InnerWishPrompt />
</div>
</div>
</div>
<button
type="submit"
className="py-2 px-4 bg-pink-400 rounded text-white"
>
Submit
</button>
</div>
</form>
</BasePrompt>
</div>
)
}
const InnerWishPrompt: FC = (props) => {
return (
<div className="space-y-4">
<textarea
name="wishDescription"
placeholder="Description"
className="block appearance-none w-full resize-none rounded py-3 px-4 outline-none bg-pink-100"
/>
<div className="h-0.5 w-3/4 m-auto w-content bg-gray-300" />
<input
type="text"
name="wishLink"
placeholder="https://example.mywish"
className="block appearance-none w-full rounded py-3 px-4 outline-none bg-pink-100"
/>
</div>
)
}
const BasePrompt: FC = (props) => <div>{props.children}</div>
export default CreateWish

View File

@@ -0,0 +1,23 @@
import { result } from '../../common/result'
export type Wish = {
name: string
description?: string
link?: string
}
export const createWish = (
name: string,
description?: string,
link?: string
): result.Result<Wish, string> => {
if (typeof name !== 'undefined' && name.length > 0) {
return result.ok({
name,
description,
link,
} as Wish)
}
return result.err(`validation of wish failed via. createWish`)
}

View File

@@ -0,0 +1,4 @@
.root {
display: grid;
grid-template-columns: 1fr 2fr;
}

View File

@@ -0,0 +1,55 @@
import { FC } from 'react'
import styles from './AuthLayout.module.scss'
const AuthLayout: FC = (props) => (
<div className={`${styles.root} min-h-screen`}>
<aside className="bg-pink-300 flex justify-center items-center">
<h1 className="text-4xl">Wishes</h1>
</aside>
<main className="flex flex-col justify-center items-center">
<div>{props.children}</div>
</main>
</div>
)
interface AuthFieldProps {
name: string
required?: boolean
type: 'text' | 'password'
}
const AuthField: FC<AuthFieldProps> = (props) => {
return (
<div>
<label
htmlFor={props.name}
className="block text-gray-800 text-sm font-bold mb-2"
>
{props.children}
</label>
<input
type="text"
name={props.name}
required
className="appearance-none border rounded w-full py-2 px-3 text-gray-800 leading-tight"
/>
</div>
)
}
interface AuthPasswordProps {
name: string
required?: boolean
}
export const AuthPassword: FC<AuthPasswordProps> = (props) => (
<AuthField {...props} type="password" />
)
interface AuthInputProps {
name: string
required?: boolean
}
export const AuthInput: FC<AuthPasswordProps> = (props) => (
<AuthField {...props} type="text" />
)
export default AuthLayout

View File

@@ -0,0 +1,6 @@
.root {
display: grid;
grid-template-columns: 1fr 3fr 1fr;
max-width: 60%;
margin: 0 auto;
}

View File

@@ -0,0 +1,21 @@
import { FC, ReactElement } from 'react'
import { inspect } from 'util'
import styles from './HomeLayout.module.scss'
interface HomeLayoutProps {
page: string
}
const HomeLayout: FC<HomeLayoutProps> = (props) => {
return (
<div className={styles.root + ' space-x-6 pt-14'}>
<h1 className="text-2xl space-x-2">
<span>Wishes</span>
<span>/</span>
<span>{props.page}</span>
</h1>
<main className="pt-20">{props.children}</main>
</div>
)
}
export default HomeLayout