mirror of
https://github.com/cupcakearmy/obolus.git
synced 2024-12-21 23:56:32 +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 item from './item'
|
||||
import purchase from './purchase'
|
||||
import user from './user'
|
||||
|
||||
const r = new Router({
|
||||
prefix: '/api'
|
||||
prefix: '/api',
|
||||
})
|
||||
|
||||
r.use(user.routes(), user.allowedMethods())
|
||||
r.use(purchase.routes(), purchase.allowedMethods())
|
||||
r.use(item.routes(), purchase.allowedMethods())
|
||||
|
||||
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 { createConnection } from 'typeorm'
|
||||
|
||||
import Item from './entities/item'
|
||||
import Purchase from './entities/purchase'
|
||||
import User from './entities/user'
|
||||
import { Config } from './lib/config'
|
||||
@ -15,7 +16,7 @@ import router from './routes'
|
||||
createConnection({
|
||||
type: 'sqlite',
|
||||
database: join(process.cwd(), 'db.sqlite'),
|
||||
entities: [User, Purchase],
|
||||
entities: [User, Purchase, Item],
|
||||
synchronize: true,
|
||||
}).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