Add base app
This commit is contained in:
16
components/common/Link.tsx
Normal file
16
components/common/Link.tsx
Normal 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
|
1
components/common/result/index.ts
Normal file
1
components/common/result/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * as result from './result'
|
60
components/common/result/result.ts
Normal file
60
components/common/result/result.ts
Normal 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}`)
|
||||
}
|
85
components/domain/wishes/createWish/CreateWish.tsx
Normal file
85
components/domain/wishes/createWish/CreateWish.tsx
Normal 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
|
23
components/domain/wishes/models.ts
Normal file
23
components/domain/wishes/models.ts
Normal 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`)
|
||||
}
|
4
components/layout/authentication/AuthLayout.module.scss
Normal file
4
components/layout/authentication/AuthLayout.module.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
.root {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
}
|
55
components/layout/authentication/AuthLayout.tsx
Normal file
55
components/layout/authentication/AuthLayout.tsx
Normal 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
|
6
components/layout/home/HomeLayout.module.scss
Normal file
6
components/layout/home/HomeLayout.module.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
.root {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr 1fr;
|
||||
max-width: 60%;
|
||||
margin: 0 auto;
|
||||
}
|
21
components/layout/home/HomeLayout.tsx
Normal file
21
components/layout/home/HomeLayout.tsx
Normal 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
|
Reference in New Issue
Block a user