Created logic for creating lists
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Kasper Juul Hermansen 2022-04-25 22:32:24 +02:00
parent ab57857deb
commit 28069a92f1
Signed by: kjuulh
GPG Key ID: 0F95C140730F2F23
7 changed files with 185 additions and 105 deletions

View File

@ -1,85 +1,75 @@
import { FC, FormEvent, SyntheticEvent, useState } from 'react'
import { FC, FormEvent, SyntheticEvent, useEffect, useState } from 'react'
import { result } from '../../../common/result'
import { createWish, Wish } from '../models'
import { v4 as uuid } from 'uuid'
interface CreateWishProps {
onFocus: () => void
hasFocus: boolean
onSubmit: (wish: Wish) => void
wish: Wish
}
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 onFocus={() => props.onFocus()}>
<div className="space-y-6 rounded bg-pink-100 p-1">
<div className="bg-pink-100 transition-all">
<input
type="text"
name="wishName"
placeholder="Your wish!"
className="w-full appearance-none bg-pink-100 p-6 text-center text-lg outline-none"
onChange={(e) =>
props.onSubmit({ ...props.wish, name: e.target.value })
}
value={props.wish.name}
/>
<div className={props.hasFocus ? 'block' : 'hidden'}>
<InnerWishPrompt wish={props.wish} onChange={props.onSubmit} />
</div>
</form>
</BasePrompt>
</div>
</div>
</div>
)
}
const InnerWishPrompt: FC = (props) => {
interface InnerWishPromptInterface {
wish: Wish
onChange: (wish: Wish) => void
}
const InnerWishPrompt: FC<InnerWishPromptInterface> = (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"
className="block w-full resize-none appearance-none rounded bg-pink-100 py-3 px-4 outline-none"
onChange={(e) =>
props.onChange({ ...props.wish, description: e.target.value })
}
value={props.wish.description}
/>
<div className="h-0.5 w-3/4 m-auto w-content bg-gray-300" />
<div className="w-content m-auto h-0.5 w-3/4 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"
className="block w-full appearance-none rounded bg-pink-100 py-3 px-4 outline-none"
onChange={(e) =>
props.onChange({ ...props.wish, link: e.target.value })
}
value={props.wish.link}
/>
</div>
)
}
const BasePrompt: FC = (props) => <div>{props.children}</div>
export const BasePrompt: FC = (props) => (
<div>
<div className="space-y-6 bg-white p-4 shadow">{props.children}</div>
</div>
)
export default CreateWish

View File

@ -1,18 +1,21 @@
import { result } from '../../common/result'
export type Wish = {
id: string
name: string
description?: string
link?: string
}
export const createWish = (
id: string,
name: string,
description?: string,
link?: string
description: string = '',
link: string = ''
): result.Result<Wish, string> => {
if (typeof name !== 'undefined' && name.length > 0) {
if (typeof name !== 'undefined') {
return result.ok({
id,
name,
description,
link,

View File

@ -7,13 +7,15 @@ interface HomeLayoutProps {
}
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 className="bg-pink-50 min-h-screen">
<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>
</div>
)
}

View File

@ -7,12 +7,15 @@
},
"dependencies": {
"next": "latest",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-uuid": "^1.0.2",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/node": "17.0.4",
"@types/react": "17.0.38",
"@types/uuid": "^8.3.4",
"autoprefixer": "^10.4.0",
"postcss": "^8.4.5",
"prettier": "^2.6.2",

View File

@ -1,20 +1,90 @@
import { NextPage } from 'next'
import HomeLayout from '../../components/layout/home/HomeLayout'
import CreateWish from '../../components/domain/wishes/createWish/CreateWish'
import CreateWish, {
BasePrompt,
} from '../../components/domain/wishes/createWish/CreateWish'
import { useEffect, useMemo, useState } from 'react'
import { createWish, Wish } from '../../components/domain/wishes/models'
import { v4 } from 'uuid'
import { result } from '../../components/common/result'
interface WishItem {
value: Wish
dirty: boolean
focus: boolean
}
const HomePage: NextPage = (props) => {
const stableId = useMemo(() => v4(), [])
const [wishes, setWishes] = useState<{ [key: string]: WishItem }>({
[stableId]: {
value: result.getValue(createWish(stableId, '')),
dirty: false,
focus: false,
},
})
const wishArr = useMemo(() => Array.from(Object.values(wishes)), [wishes])
const allDirty = useMemo(
() => wishArr.map((w) => w.dirty).reduce((prev, curr) => prev && curr),
[wishArr]
)
useMemo(() => {
if (!allDirty) {
return
}
const id = v4()
setWishes((wishes) => ({
...wishes,
[id]: {
value: result.getValue(createWish(id, '')),
dirty: false,
focus: false,
},
}))
}, [allDirty])
const submit = (wishes: Wish[]) => {
console.log(wishes)
}
return (
<HomeLayout page="home">
<div>
<CreateWish
onSubmit={(wish) => {
console.log(wish)
}}
onFocus={() => {
console.log('focused')
}}
/>
</div>
<BasePrompt>
{wishArr.map((wish) => (
<CreateWish
key={wish.value.id}
hasFocus={wish.focus}
onFocus={() => {
setWishes((wishes) => ({
...Array.from(Object.values(wishes)) // Get array
.map((w) => ({ ...w, focus: false })) // Reset focus for all
.reduce((obj, cur) => ({ ...obj, [cur.value.id]: cur }), {}), // Translate to object
[wish.value.id]: { ...wish, focus: true },
}))
}}
onSubmit={(wish) => {
setWishes((ws) => ({
...ws,
[wish.id]: { value: wish, dirty: true, focus: true },
}))
}}
wish={wish.value}
/>
))}
<button
type="button"
className="rounded bg-pink-400 py-2 px-4 text-white"
onClick={() =>
submit(wishArr.filter((w) => w.dirty).map((w) => w.value))
}
>
Submit
</button>
</BasePrompt>
</HomeLayout>
)
}

View File

@ -3,25 +3,31 @@ lockfileVersion: 5.3
specifiers:
'@types/node': 17.0.4
'@types/react': 17.0.38
'@types/uuid': ^8.3.4
autoprefixer: ^10.4.0
next: latest
postcss: ^8.4.5
prettier: ^2.6.2
prettier-plugin-tailwindcss: ^0.1.1
react: ^17.0.2
react-dom: ^17.0.2
react: ^18.0.0
react-dom: ^18.0.0
react-uuid: ^1.0.2
sass: ^1.50.1
tailwindcss: ^3.0.7
typescript: 4.5.4
uuid: ^8.3.2
dependencies:
next: 12.1.5_b87f661d11de3633273d58c9869b3544
react: 17.0.2
react-dom: 17.0.2_react@17.0.2
next: 12.1.5_d7d27f3bc85bacccfb9132af84144489
react: 18.0.0
react-dom: 18.0.0_react@18.0.0
react-uuid: 1.0.2
uuid: 8.3.2
devDependencies:
'@types/node': 17.0.4
'@types/react': 17.0.38
'@types/uuid': 8.3.4
autoprefixer: 10.4.5_postcss@8.4.12
postcss: 8.4.12
prettier: 2.6.2
@ -185,6 +191,10 @@ packages:
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
dev: true
/@types/uuid/8.3.4:
resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==}
dev: true
/acorn-node/1.8.2:
resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==}
dependencies:
@ -457,7 +467,7 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
/next/12.1.5_b87f661d11de3633273d58c9869b3544:
/next/12.1.5_d7d27f3bc85bacccfb9132af84144489:
resolution: {integrity: sha512-YGHDpyfgCfnT5GZObsKepmRnne7Kzp7nGrac07dikhutWQug7hHg85/+sPJ4ZW5Q2pDkb+n0FnmLkmd44htIJQ==}
engines: {node: '>=12.22.0'}
hasBin: true
@ -478,10 +488,10 @@ packages:
'@next/env': 12.1.5
caniuse-lite: 1.0.30001332
postcss: 8.4.5
react: 17.0.2
react-dom: 17.0.2_react@17.0.2
react: 18.0.0
react-dom: 18.0.0_react@18.0.0
sass: 1.50.1
styled-jsx: 5.0.1_react@17.0.2
styled-jsx: 5.0.1_react@18.0.0
optionalDependencies:
'@next/swc-android-arm-eabi': 12.1.5
'@next/swc-android-arm64': 12.1.5
@ -514,11 +524,6 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/object-assign/4.1.1:
resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=}
engines: {node: '>=0.10.0'}
dev: false
/object-hash/3.0.0:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
@ -627,23 +632,25 @@ packages:
engines: {node: '>=10'}
dev: true
/react-dom/17.0.2_react@17.0.2:
resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==}
/react-dom/18.0.0_react@18.0.0:
resolution: {integrity: sha512-XqX7uzmFo0pUceWFCt7Gff6IyIMzFUn7QMZrbrQfGxtaxXZIcGQzoNpRLE3fQLnS4XzLLPMZX2T9TRcSrasicw==}
peerDependencies:
react: 17.0.2
react: ^18.0.0
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
react: 17.0.2
scheduler: 0.20.2
react: 18.0.0
scheduler: 0.21.0
dev: false
/react/17.0.2:
resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
/react-uuid/1.0.2:
resolution: {integrity: sha512-5e0GM16uuQj9MdRJlZ0GdLC8LKMwpU9PIqYmF27s3fIV2z+rLyASTaiW4aKzSY4DyGanz+ImzsECkftwGWfAwA==}
dev: false
/react/18.0.0:
resolution: {integrity: sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A==}
engines: {node: '>=0.10.0'}
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
dev: false
/readdirp/3.6.0:
@ -683,18 +690,17 @@ packages:
source-map-js: 1.0.2
dev: true
/scheduler/0.20.2:
resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==}
/scheduler/0.21.0:
resolution: {integrity: sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==}
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
dev: false
/source-map-js/1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
/styled-jsx/5.0.1_react@17.0.2:
/styled-jsx/5.0.1_react@18.0.0:
resolution: {integrity: sha512-+PIZ/6Uk40mphiQJJI1202b+/dYeTVd9ZnMPR80pgiWbjIwvN2zIp4r9et0BgqBuShh48I0gttPlAXA7WVvBxw==}
engines: {node: '>= 12.0.0'}
peerDependencies:
@ -707,7 +713,7 @@ packages:
babel-plugin-macros:
optional: true
dependencies:
react: 17.0.2
react: 18.0.0
dev: false
/supports-preserve-symlinks-flag/1.0.0:
@ -762,6 +768,11 @@ packages:
resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=}
dev: true
/uuid/8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
dev: false
/xtend/4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}

View File

@ -13,7 +13,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
"incremental": true,
"downlevelIteration":true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]