mirror of
https://github.com/cupcakearmy/obolus.git
synced 2025-12-11 00:54:58 +00:00
client
This commit is contained in:
38
www/pages/index.jsx
Executable file
38
www/pages/index.jsx
Executable file
@@ -0,0 +1,38 @@
|
||||
import React from 'react'
|
||||
|
||||
import Layout from '../components/layout'
|
||||
import { callAPI } from '../utils/api'
|
||||
import { withAuthSync } from '../utils/auth'
|
||||
import Chart from '../components/chart'
|
||||
import Purchase from '../components/purchase'
|
||||
|
||||
|
||||
const deletePurchase = id => callAPI(null, {
|
||||
url: `/api/purchases/${id}`,
|
||||
method: 'delete',
|
||||
}).then(() => location.reload()).catch(() => alert('There was a problem deleting the purchase'))
|
||||
|
||||
|
||||
const Home = ({ purchases, stats, me }) => {
|
||||
|
||||
return <Layout>
|
||||
<h3 className={'text-center'}>summa</h3>
|
||||
|
||||
<Chart stats={stats}/>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<h4 className={'text-center'}>diarium</h4>
|
||||
<div className={'pl-2'}>
|
||||
{purchases.map((purchase, i) => <Purchase key={i} {...{ purchase, me }}/>)}
|
||||
</div>
|
||||
</Layout>
|
||||
}
|
||||
|
||||
Home.getInitialProps = async ctx => ({
|
||||
stats: await callAPI(ctx, { url: '/api/purchases/stats' }),
|
||||
purchases: await callAPI(ctx, { url: `/api/purchases/` }),
|
||||
me: await callAPI(ctx, { url: '/api/users/me' }),
|
||||
})
|
||||
|
||||
export default withAuthSync(Home)
|
||||
61
www/pages/login.jsx
Executable file
61
www/pages/login.jsx
Executable file
@@ -0,0 +1,61 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import Layout from '../components/layout'
|
||||
import { login } from '../utils/auth'
|
||||
import { callAPI } from '../utils/api'
|
||||
import { capitalize } from '../utils/misc'
|
||||
|
||||
|
||||
const Login = ({ users }) => {
|
||||
|
||||
const [user, setUser] = useState(users[0])
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
callAPI(null, {
|
||||
method: 'post',
|
||||
url: '/api/users/login',
|
||||
data: { user, password },
|
||||
})
|
||||
.then(login)
|
||||
.catch(() => setError(true))
|
||||
}
|
||||
|
||||
return <Layout>
|
||||
<form onSubmit={handleSubmit}>
|
||||
|
||||
<div className={`form-group ${error ? 'has-error' : ''}`}>
|
||||
<select className="form-select"
|
||||
name='username'
|
||||
placeholder="username"
|
||||
value={user}
|
||||
onChange={e => setUser(e.target.value)}
|
||||
>
|
||||
{users.map((username, i) => <option key={i} value={username}>{capitalize(username)}</option>)}
|
||||
</select>
|
||||
|
||||
<label className="form-label" htmlFor="input-example-1">Password</label>
|
||||
<input
|
||||
className="form-input"
|
||||
type='password'
|
||||
placeholder='password'
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
/>
|
||||
<br/>
|
||||
<button type='submit' className={'btn btn-primary'}>Login</button>
|
||||
{error && <React.Fragment><br/><p className="form-input-hint">Unauthorized</p></React.Fragment>}
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</Layout>
|
||||
}
|
||||
|
||||
Login.getInitialProps = async ctx => ({
|
||||
users: await callAPI(ctx, { url: `/api/users/names` }),
|
||||
})
|
||||
|
||||
export default Login
|
||||
65
www/pages/me.jsx
Executable file
65
www/pages/me.jsx
Executable file
@@ -0,0 +1,65 @@
|
||||
import React from 'react'
|
||||
|
||||
import Layout from '../components/layout'
|
||||
import { callAPI } from '../utils/api'
|
||||
import { capitalize, getAvatarOfFallback } from '../utils/misc'
|
||||
import { withAuthSync } from '../utils/auth'
|
||||
|
||||
|
||||
const avatars = ['anteater', 'bear', 'beaver', 'boar', 'buffalo-1', 'buffalo', 'cat', 'chicken', 'cow', 'crow', 'dog-1', 'dog', 'donkey', 'elephant', 'fox', 'giraffe', 'hedgehog', 'hen', 'hippopotamus', 'horse', 'kangaroo', 'koala', 'leopard', 'lion', 'marten', 'monkey-1', 'monkey', 'mouse', 'octopus', 'ostrich', 'owl', 'panda', 'parrot', 'penguin-1', 'penguin', 'pig', 'polar-bear', 'rabbit', 'racoon', 'rhinoceros', 'rooster', 'seagull', 'seal', 'sheep-1', 'sheep', 'sloth', 'snake', 'tiger', 'whale', 'zebra']
|
||||
|
||||
const selectAvatar = avatar => callAPI(null, {
|
||||
url: `/api/users/me/avatar`,
|
||||
method: 'post',
|
||||
data: { avatar },
|
||||
}).then(() => location.reload()).catch(() => alert('There was a problem deleting the purchase'))
|
||||
|
||||
const Me = me => {
|
||||
|
||||
const { name, debts, purchases, avatar } = me
|
||||
|
||||
return <Layout>
|
||||
<h1>{capitalize(name)}</h1>
|
||||
|
||||
<h5>Current Avatar</h5>
|
||||
<img alt="avatar-icon" src={`/static/icons/animals/${getAvatarOfFallback(avatar)}.svg`}
|
||||
className="avatar avatar-xl"/>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<h5>Select Avatar</h5>
|
||||
<div className={'selector'}>
|
||||
{avatars.map((avatar, i) => <img
|
||||
onClick={() => selectAvatar(avatar)}
|
||||
key={i} alt="avatar-icon"
|
||||
src={`/static/icons/animals/${avatar}.svg`}
|
||||
className="avatar avatar-md m-1"
|
||||
/>)}
|
||||
</div>
|
||||
|
||||
{/*<br/><br/>*/}
|
||||
{/*<h2>Purchases</h2>*/}
|
||||
{/*{purchases.map((purchase, i) => <Purchase key={i} {...{ purchase, me }} />)}*/}
|
||||
|
||||
{/*<br/><br/>*/}
|
||||
{/*<h2>Debts</h2>*/}
|
||||
{/*{debts.map((purchase, i) => <Purchase key={i} {...{ purchase, me }} />)}*/}
|
||||
|
||||
{/* language=CSS */}
|
||||
<style jsx>{`
|
||||
.selector img {
|
||||
cursor: pointer;
|
||||
transition: var(--animation);
|
||||
}
|
||||
|
||||
.selector img:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
`}</style>
|
||||
|
||||
</Layout>
|
||||
}
|
||||
|
||||
Me.getInitialProps = async ctx => await callAPI(ctx, { url: `/api/users/me` })
|
||||
|
||||
export default withAuthSync(Me)
|
||||
139
www/pages/new.jsx
Normal file
139
www/pages/new.jsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import React, { useRef, useState } from 'react'
|
||||
import Router from 'next/router'
|
||||
|
||||
import Layout from '../components/layout'
|
||||
import { withAuthSync } from '../utils/auth'
|
||||
import { callAPI } from '../utils/api'
|
||||
|
||||
|
||||
const Profile = props => {
|
||||
|
||||
const { users } = props
|
||||
|
||||
const check = useRef(undefined)
|
||||
const [photo, setPhoto] = useState(undefined)
|
||||
const [price, setPrice] = useState('')
|
||||
const [description, setDescription] = useState('')
|
||||
const [debtors, setDebtors] = useState(users.reduce((acc, users) => {
|
||||
acc[users.name] = true
|
||||
return acc
|
||||
}, {}))
|
||||
|
||||
const removeUpload = () => {
|
||||
setPhoto(false)
|
||||
}
|
||||
|
||||
const handlePhoto = e => {
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = e => {
|
||||
const converted = Buffer.from(e.target.result).toString('base64')
|
||||
check.current.src = `data:image/jpeg;base64,${converted}`
|
||||
setPhoto(converted)
|
||||
}
|
||||
reader.readAsArrayBuffer(e.target.files[0])
|
||||
}
|
||||
|
||||
const submit = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
console.log(e.files)
|
||||
const selectedDebtors = Object.entries(debtors)
|
||||
.filter(([name, selected]) => selected)
|
||||
.map(([name, selected]) => name)
|
||||
|
||||
await callAPI(null, {
|
||||
url: `/api/purchases`,
|
||||
method: 'post',
|
||||
data: {
|
||||
price: parseFloat(price),
|
||||
debtors: selectedDebtors,
|
||||
description,
|
||||
},
|
||||
})
|
||||
Router.push('/')
|
||||
}
|
||||
|
||||
return <Layout>
|
||||
<h1>Add New</h1>
|
||||
|
||||
<form onSubmit={submit}>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="form-label">Price</label>
|
||||
<input
|
||||
className="form-input"
|
||||
placeholder={'Price'}
|
||||
value={price}
|
||||
onChange={e => setPrice(e.target.value)}
|
||||
type={'number'} min={0} step={0.01}
|
||||
/>
|
||||
<br/>
|
||||
<input
|
||||
className="form-input"
|
||||
placeholder={'I haz bought...'}
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
<br/>
|
||||
{photo
|
||||
? <button onClick={removeUpload} type={'button'} className="btn btn-primary">Delete Photo</button>
|
||||
: <div className="fileUpload btn btn-primary">
|
||||
<span>Upload a Photo</span>
|
||||
<input
|
||||
type="file"
|
||||
onChange={handlePhoto}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
{users.map(({ name }, i) => <label key={i} className="form-checkbox form-inline">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={debtors[name]}
|
||||
onChange={e => setDebtors({ ...debtors, [name]: e.target.checked })}
|
||||
/>
|
||||
<i className="form-icon"/> {name}
|
||||
</label>)}
|
||||
</div>
|
||||
|
||||
<img ref={check} id="check" alt="file upload check" style={{ display: photo ? 'initial' : 'none' }}/>
|
||||
|
||||
<button type={'submit'} className="btn btn-primary">Save</button>
|
||||
</form>
|
||||
|
||||
{/* language=CSS */}
|
||||
<style jsx>{`
|
||||
|
||||
img#check {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fileUpload {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fileUpload input {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
filter: alpha(opacity=0);
|
||||
}
|
||||
`}</style>
|
||||
|
||||
</Layout>
|
||||
}
|
||||
|
||||
Profile.getInitialProps = async ctx => ({
|
||||
users: await callAPI(ctx, { url: `/api/users/` }),
|
||||
})
|
||||
|
||||
export default withAuthSync(Profile)
|
||||
Reference in New Issue
Block a user