mirror of
https://github.com/cupcakearmy/obolus.git
synced 2024-12-22 16:16:27 +00:00
shopping list
This commit is contained in:
parent
63d4b4a0fe
commit
c50a37b08a
24
api/src/entities/item.ts
Normal file
24
api/src/entities/item.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { BaseEntity, Column, Entity, PrimaryColumn } from 'typeorm'
|
||||||
|
import UUID from 'uuid/v4'
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export default class Item extends BaseEntity {
|
||||||
|
|
||||||
|
@PrimaryColumn()
|
||||||
|
id!: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
text: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
done: boolean
|
||||||
|
|
||||||
|
constructor(text: string, done: boolean = false) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.id = UUID()
|
||||||
|
this.text = text
|
||||||
|
this.done = done
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,13 +1,15 @@
|
|||||||
import Router from 'koa-router'
|
import Router from 'koa-router'
|
||||||
|
|
||||||
|
import item from './item'
|
||||||
import purchase from './purchase'
|
import purchase from './purchase'
|
||||||
import user from './user'
|
import user from './user'
|
||||||
|
|
||||||
const r = new Router({
|
const r = new Router({
|
||||||
prefix: '/api'
|
prefix: '/api',
|
||||||
})
|
})
|
||||||
|
|
||||||
r.use(user.routes(), user.allowedMethods())
|
r.use(user.routes(), user.allowedMethods())
|
||||||
r.use(purchase.routes(), purchase.allowedMethods())
|
r.use(purchase.routes(), purchase.allowedMethods())
|
||||||
|
r.use(item.routes(), purchase.allowedMethods())
|
||||||
|
|
||||||
export default r
|
export default r
|
60
api/src/routes/item.ts
Normal file
60
api/src/routes/item.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import Router from 'koa-router'
|
||||||
|
|
||||||
|
import Item from '../entities/item'
|
||||||
|
import { withAuth } from '../lib/auth'
|
||||||
|
import { Success } from '../lib/responses'
|
||||||
|
|
||||||
|
const r = new Router({
|
||||||
|
prefix: '/items',
|
||||||
|
})
|
||||||
|
|
||||||
|
r.get('/', withAuth(async ctx => {
|
||||||
|
return Success(ctx, await Item.find())
|
||||||
|
}))
|
||||||
|
|
||||||
|
r.post('/', withAuth(async ctx => {
|
||||||
|
const { text, done } = ctx.request.body
|
||||||
|
|
||||||
|
return Success(ctx, await new Item(String(text), Boolean(done)).save())
|
||||||
|
}))
|
||||||
|
|
||||||
|
r.delete('/', withAuth(async ctx => {
|
||||||
|
return Success(ctx, await Item.clear())
|
||||||
|
}))
|
||||||
|
|
||||||
|
r.get('/:id', withAuth(async ctx => {
|
||||||
|
const { id } = ctx.params
|
||||||
|
const item = await Item.findOne(id)
|
||||||
|
|
||||||
|
// 404
|
||||||
|
if (!item) return
|
||||||
|
|
||||||
|
return Success(ctx, item)
|
||||||
|
}))
|
||||||
|
|
||||||
|
r.delete('/:id', withAuth(async ctx => {
|
||||||
|
const { id } = ctx.params
|
||||||
|
const item = await Item.findOne(id)
|
||||||
|
|
||||||
|
// 404
|
||||||
|
if (!item) return
|
||||||
|
|
||||||
|
await item.remove()
|
||||||
|
return Success(ctx)
|
||||||
|
}))
|
||||||
|
|
||||||
|
r.patch('/:id', withAuth(async ctx => {
|
||||||
|
const { id } = ctx.params
|
||||||
|
const item = await Item.findOne(id)
|
||||||
|
|
||||||
|
// 404
|
||||||
|
if (!item) return
|
||||||
|
|
||||||
|
const { text, done } = ctx.request.body
|
||||||
|
if (text !== undefined) item.text = String(text)
|
||||||
|
if (done !== undefined) item.done = Boolean(done)
|
||||||
|
|
||||||
|
return Success(ctx, await item.save())
|
||||||
|
}))
|
||||||
|
|
||||||
|
export default r
|
@ -5,6 +5,7 @@ import Parser from 'koa-bodyparser'
|
|||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { createConnection } from 'typeorm'
|
import { createConnection } from 'typeorm'
|
||||||
|
|
||||||
|
import Item from './entities/item'
|
||||||
import Purchase from './entities/purchase'
|
import Purchase from './entities/purchase'
|
||||||
import User from './entities/user'
|
import User from './entities/user'
|
||||||
import { Config } from './lib/config'
|
import { Config } from './lib/config'
|
||||||
@ -15,7 +16,7 @@ import router from './routes'
|
|||||||
createConnection({
|
createConnection({
|
||||||
type: 'sqlite',
|
type: 'sqlite',
|
||||||
database: join(process.cwd(), 'db.sqlite'),
|
database: join(process.cwd(), 'db.sqlite'),
|
||||||
entities: [User, Purchase],
|
entities: [User, Purchase, Item],
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
|
|
||||||
|
114
www/pages/list.jsx
Normal file
114
www/pages/list.jsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
import { withAuthSync } from '../utils/auth'
|
||||||
|
import Layout from '../components/layout'
|
||||||
|
import { callAPI } from '../utils/api'
|
||||||
|
|
||||||
|
|
||||||
|
const Item = ({ id, text, done }) => {
|
||||||
|
|
||||||
|
const handleDone = async (e) => {
|
||||||
|
await callAPI(null, {
|
||||||
|
url: `/api/items/${id}`,
|
||||||
|
method: 'patch',
|
||||||
|
data: {
|
||||||
|
done: e.target.checked,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (e) => {
|
||||||
|
await callAPI(null, {
|
||||||
|
url: `/api/items/${id}`,
|
||||||
|
method: 'delete',
|
||||||
|
})
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
return <tr className="item">
|
||||||
|
<td className="item-done">
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-checkbox">
|
||||||
|
<input type="checkbox" checked={done} onChange={handleDone}/>
|
||||||
|
<i className="form-icon"/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{text}</td>
|
||||||
|
<td className="item-menu">
|
||||||
|
<div className="dropdown dropdown-right">
|
||||||
|
<a className="btn btn-link dropdown-toggle" tabIndex="0">
|
||||||
|
<i className="icon icon-more-vert"/>
|
||||||
|
</a>
|
||||||
|
<ul className="menu">
|
||||||
|
<li className="menu-item">
|
||||||
|
<a onClick={handleDelete}>
|
||||||
|
<i className="icon icon-delete"/> Delete
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{/* language=CSS */}
|
||||||
|
<style jsx>{`
|
||||||
|
.item-done,
|
||||||
|
.item-menu {
|
||||||
|
width: 2em;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
|
||||||
|
const List = ({ items }) => {
|
||||||
|
|
||||||
|
const input = useRef(undefined)
|
||||||
|
const [text, setText] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!input.current) return
|
||||||
|
input.current.focus()
|
||||||
|
}, [input])
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
await callAPI(null, {
|
||||||
|
url: `/api/items/`,
|
||||||
|
method: 'post',
|
||||||
|
data: { text },
|
||||||
|
})
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteAll = async () => {
|
||||||
|
await callAPI(null, {
|
||||||
|
url: `/api/items/`,
|
||||||
|
method: 'delete',
|
||||||
|
})
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Layout>
|
||||||
|
<form onSubmit={submit}>
|
||||||
|
<div className="input-group">
|
||||||
|
<input
|
||||||
|
type="text" className="form-input" placeholder="..." ref={input}
|
||||||
|
value={text} onChange={e => setText(e.target.value)}/>
|
||||||
|
<button type="submit" className="btn btn-primary input-group-btn">Add</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<br/>
|
||||||
|
<table className="table table-hover">
|
||||||
|
<tbody>
|
||||||
|
{items.map((item, i) => <Item key={i} {...item} />)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br/>
|
||||||
|
<button onClick={deleteAll} className="btn btn-error">Delete All</button>
|
||||||
|
</Layout>
|
||||||
|
}
|
||||||
|
|
||||||
|
List.getInitialProps = async ctx => ({
|
||||||
|
items: await callAPI(ctx, { url: `/api/items` }),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default withAuthSync(List)
|
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M346.5 240H272v-74.5c0-8.8-7.2-16-16-16s-16 7.2-16 16V240h-74.5c-8.8 0-16 6-16 16s7.5 16 16 16H240v74.5c0 9.5 7 16 16 16s16-7.2 16-16V272h74.5c8.8 0 16-7.2 16-16s-7.2-16-16-16z"/><path d="M256 76c48.1 0 93.3 18.7 127.3 52.7S436 207.9 436 256s-18.7 93.3-52.7 127.3S304.1 436 256 436c-48.1 0-93.3-18.7-127.3-52.7S76 304.1 76 256s18.7-93.3 52.7-127.3S207.9 76 256 76m0-28C141.1 48 48 141.1 48 256s93.1 208 208 208 208-93.1 208-208S370.9 48 256 48z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M368.5 240H272v-96.5c0-8.8-7.2-16-16-16s-16 7.2-16 16V240h-96.5c-8.8 0-16 7.2-16 16 0 4.4 1.8 8.4 4.7 11.3 2.9 2.9 6.9 4.7 11.3 4.7H240v96.5c0 4.4 1.8 8.4 4.7 11.3 2.9 2.9 6.9 4.7 11.3 4.7 8.8 0 16-7.2 16-16V272h96.5c8.8 0 16-7.2 16-16s-7.2-16-16-16z"/></svg>
|
Before Width: | Height: | Size: 524 B After Width: | Height: | Size: 330 B |
1
www/static/icons/ui/basket.svg
Normal file
1
www/static/icons/ui/basket.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M387.9 373.7h49.2l17.5-75.4h-66.7zM387.9 448h.5c18.7 0 33.4-12.5 38.3-29.5l6-25.9h-44.8V448zM265.4 392.5h103.7V448H265.4zM75 373.7h49v-75.4H57.5zM142.9 192h103.7v87.5H142.9zM265.4 192h103.7v87.5H265.4zM85.5 418.3c4.7 17 19.4 29.7 38.1 29.7h.5v-55.5H79.4l6.1 25.8zM142.9 392.5h103.7V448H142.9zM265.4 298.3h103.7v75.4H265.4zM142.9 298.3h103.7v75.4H142.9z"/><path d="M464 192h-47.9V96c0-17.6-14.4-32-32-32H127.9c-17.6 0-32 14.4-32 32v96H48c-10.3 0-17.9 9.6-15.6 19.6l19.7 67.9H124V106c0-7.7 6.3-14 14-14h236c7.7 0 14 6.3 14 14v173.5h72l19.6-67.9c2.3-10-5.3-19.6-15.6-19.6z"/></svg>
|
After Width: | Height: | Size: 649 B |
1
www/static/icons/ui/check.svg
Normal file
1
www/static/icons/ui/check.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M362.6 192.9L345 174.8c-.7-.8-1.8-1.2-2.8-1.2-1.1 0-2.1.4-2.8 1.2l-122 122.9-44.4-44.4c-.8-.8-1.8-1.2-2.8-1.2-1 0-2 .4-2.8 1.2l-17.8 17.8c-1.6 1.6-1.6 4.1 0 5.7l56 56c3.6 3.6 8 5.7 11.7 5.7 5.3 0 9.9-3.9 11.6-5.5h.1l133.7-134.4c1.4-1.7 1.4-4.2-.1-5.7z"/></svg>
|
After Width: | Height: | Size: 331 B |
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M184 448h48c4.4 0 8-3.6 8-8V72c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v368c0 4.4 3.6 8 8 8zM88 448h48c4.4 0 8-3.6 8-8V296c0-4.4-3.6-8-8-8H88c-4.4 0-8 3.6-8 8v144c0 4.4 3.6 8 8 8zM280.1 448h47.8c4.5 0 8.1-3.6 8.1-8.1V232.1c0-4.5-3.6-8.1-8.1-8.1h-47.8c-4.5 0-8.1 3.6-8.1 8.1v207.8c0 4.5 3.6 8.1 8.1 8.1zM368 136.1v303.8c0 4.5 3.6 8.1 8.1 8.1h47.8c4.5 0 8.1-3.6 8.1-8.1V136.1c0-4.5-3.6-8.1-8.1-8.1h-47.8c-4.5 0-8.1 3.6-8.1 8.1z"/></svg>
|
|
Before Width: | Height: | Size: 501 B |
1
www/static/icons/ui/trending.svg
Normal file
1
www/static/icons/ui/trending.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M464.9 128H344.1c-8.3 0-15.1 6.6-15.1 14.8s6.8 14.8 15.1 14.8h83.7l-138 142.2-85.9-84.1c-2.9-2.8-6.6-4.3-10.7-4.3-4 0-7.8 1.5-10.7 4.3L36.2 358.8c-1.9 1.9-4.2 5.2-4.2 10.7 0 4.1 1.4 7.5 4.2 10.2 2.9 2.8 6.6 4.3 10.7 4.3 4 0 7.8-1.5 10.7-4.3L193.2 247l85.9 84.1c2.9 2.8 6.6 4.3 10.7 4.3 4 0 7.8-1.5 10.7-4.3l149.4-151.9v81.7c0 8.1 6.8 14.8 15.1 14.8s15.1-6.6 15.1-14.8V142.8c-.1-8.2-6.9-14.8-15.2-14.8z"/></svg>
|
After Width: | Height: | Size: 481 B |
Loading…
Reference in New Issue
Block a user