new form and form tweaks
This commit is contained in:
parent
2ebe8bcd1f
commit
4a1507eba4
|
@ -30,7 +30,9 @@ export const MainLayout = () => {
|
|||
<Switch>
|
||||
<Route path="/sign-up" component={NewUser} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/">
|
||||
<div>loading...</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
) : (
|
||||
<Switch>
|
||||
|
|
|
@ -6,11 +6,6 @@
|
|||
|
||||
align-items: center;
|
||||
|
||||
h1 {
|
||||
margin-top: 2.5vmin;
|
||||
margin-bottom: 1vmin;
|
||||
}
|
||||
|
||||
main {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ type Props = {
|
|||
type AppContextInterface = {
|
||||
get: <T>(path: string) => Promise<T>
|
||||
patch: <T>(path: string, body: Partial<T>) => Promise<T>
|
||||
post: <T, R = T>(path: string, body: Partial<T>) => Promise<R>
|
||||
post: <T, R = T>(path: string, body: Partial<T>) => Promise<R | null>
|
||||
create: <T>(path: string, body: Partial<T>) => Promise<T>
|
||||
destroy: (path: string) => Promise<string>
|
||||
baseURL?: string
|
||||
|
@ -39,8 +39,12 @@ export const AppContextProvider = ({ children, baseURL }: Props) => {
|
|||
return res.data
|
||||
}
|
||||
async function post<T, R = T>(path: string, body: Partial<T>) {
|
||||
try {
|
||||
const res = await api.post<T, AxiosResponse<R>>(path, body)
|
||||
return res.data
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
async function create<T>(path: string, body: Partial<T>) {
|
||||
// unauthed POST
|
||||
|
|
|
@ -1,11 +1,30 @@
|
|||
@import '../../scss/variables.scss';
|
||||
|
||||
.dank-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 3ch;
|
||||
|
||||
input {
|
||||
border-radius: 0.25rem;
|
||||
width: 100%;
|
||||
font-size: 2rem;
|
||||
border: 1px solid $color-grey;
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid $color-primary;
|
||||
}
|
||||
}
|
||||
button {
|
||||
width: 40%;
|
||||
font-size: 2rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
label {
|
||||
margin: 0.5ch;
|
||||
display: flex;
|
||||
font-size: 1.5rem;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
|
@ -13,8 +32,4 @@
|
|||
margin-left: 2ch;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 4ch 1ch;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { FormItemProps } from 'antd'
|
||||
import AntFormItem from 'antd/lib/form/FormItem'
|
||||
import './style.scss'
|
||||
|
||||
type Props = FormItemProps
|
||||
|
||||
|
|
5
frontend/src/elements/FormItem/style.scss
Normal file
5
frontend/src/elements/FormItem/style.scss
Normal file
|
@ -0,0 +1,5 @@
|
|||
.dank-form-item {
|
||||
label {
|
||||
color: white;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
import { InputNumber as AntInputNumber, InputNumberProps } from 'antd'
|
||||
|
||||
import './style.scss'
|
||||
|
||||
type Props = InputNumberProps
|
||||
|
||||
export const InputNumber = (props: Props) => {
|
||||
return <AntInputNumber {...props} />
|
||||
return <AntInputNumber className="dank-input-number" {...props} />
|
||||
}
|
||||
|
|
33
frontend/src/elements/InputNumber/style.scss
Normal file
33
frontend/src/elements/InputNumber/style.scss
Normal file
|
@ -0,0 +1,33 @@
|
|||
.ant-input-number {
|
||||
display: grid;
|
||||
grid-template: 1fr / 1fr;
|
||||
|
||||
div {
|
||||
grid-area: 1/1/2/2;
|
||||
}
|
||||
|
||||
&-handler {
|
||||
&-wrap {
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-up,
|
||||
&-down {
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
margin: auto 0.5rem auto 0;
|
||||
svg {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-input-wrap {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
53
frontend/src/forms/TransactionForm.tsx
Normal file
53
frontend/src/forms/TransactionForm.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { useForm } from 'antd/lib/form/Form'
|
||||
import { Button } from '../elements/Button'
|
||||
import { Form } from '../elements/Form'
|
||||
import { FormItem } from '../elements/FormItem'
|
||||
import { Input } from '../elements/Input'
|
||||
import { InputNumber } from '../elements/InputNumber'
|
||||
import {
|
||||
useCreateTransaction,
|
||||
NewTransaction,
|
||||
} from '../hooks/create/useCreateTransaction'
|
||||
import { DankFormProps } from '../layout/Modal'
|
||||
import { Row } from '../layout/Row'
|
||||
|
||||
type Props = DankFormProps & {
|
||||
stackId: string
|
||||
}
|
||||
|
||||
export const TransactionForm = ({ stackId, onSave, onCancel }: Props) => {
|
||||
const [form] = useForm<NewTransaction>()
|
||||
const create = useCreateTransaction()
|
||||
|
||||
const handleFinish = async (tx: NewTransaction) => {
|
||||
console.log('transaction', tx)
|
||||
const res = await create(tx)
|
||||
|
||||
if (!res) {
|
||||
console.log("couldn't create transaction")
|
||||
return
|
||||
}
|
||||
|
||||
onSave?.()
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleFinish}
|
||||
initialValues={{ stack: stackId }}
|
||||
>
|
||||
<h1>New Transaction</h1>
|
||||
<FormItem label="Amount" name="amount">
|
||||
<InputNumber />
|
||||
</FormItem>
|
||||
<FormItem label="Details" name="amount">
|
||||
<Input />
|
||||
</FormItem>
|
||||
<Row>
|
||||
<Button onClick={onCancel}>Cancel</Button>
|
||||
<Button htmlType="submit">Save</Button>
|
||||
</Row>
|
||||
</Form>
|
||||
)
|
||||
}
|
|
@ -4,6 +4,6 @@ import { Account } from '../../types'
|
|||
export const useCreateAccount = () => {
|
||||
const api = useAppContext()
|
||||
|
||||
return (body: Omit<Account, 'id'>): Promise<Account> =>
|
||||
return (body: Omit<Account, 'id'>): Promise<Account | null> =>
|
||||
api.post<Account>('/accounts', body)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ import { Stack } from '../../types'
|
|||
export const useCreateStack = () => {
|
||||
const api = useAppContext()
|
||||
|
||||
return (body: Omit<Stack, 'id'>): Promise<Stack> =>
|
||||
return (body: Omit<Stack, 'id'>): Promise<Stack | null> =>
|
||||
api.post<Stack>('/stacks', body)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { useAppContext } from '../../contexts/AppContext'
|
||||
import { Transaction } from '../../types'
|
||||
|
||||
export type NewTransaction = Omit<Transaction, 'id'>
|
||||
|
||||
export const useCreateTransaction = () => {
|
||||
const api = useAppContext()
|
||||
|
||||
return (body: Omit<Transaction, 'id'>): Promise<Transaction> =>
|
||||
api.post<Transaction>('/transactions', body)
|
||||
return (body: NewTransaction): Promise<Transaction | null> =>
|
||||
api.post<Transaction>('/transactions/', body)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ import { User } from '../../types'
|
|||
export const useCreateUser = () => {
|
||||
const api = useAppContext()
|
||||
|
||||
return (body: Omit<User, 'id'>): Promise<User> =>
|
||||
return (body: Omit<User, 'id'>): Promise<User | null> =>
|
||||
api.post<User>('/users', body)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Transaction } from '../../types'
|
||||
import { useGet } from '../util/useGet'
|
||||
|
||||
export const useTransactions = () => useGet<Transaction[]>(`/transactions/`)
|
||||
export const useTransactions = () =>
|
||||
useGet<Transaction[]>(`/transactions/`, { mode: 'list' })
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import { ModalProps } from 'antd'
|
||||
import { ReactNode } from 'react'
|
||||
import { ReactElement, cloneElement } from 'react'
|
||||
import './style.scss'
|
||||
|
||||
type Props = ModalProps & { children: ReactNode }
|
||||
type Props = ModalProps & { children: ReactElement }
|
||||
|
||||
export const Modal = ({ children, ...props }: Props) => {
|
||||
return <div className="dank-modal">{children}</div>
|
||||
export type DankFormProps = {
|
||||
onSave?: () => void
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
export const Modal = ({ children, visible }: Props) => {
|
||||
const el = cloneElement(children, (props: DankFormProps) => ({ ...props }))
|
||||
|
||||
return visible ? <div className="dank-modal">{el}</div> : null
|
||||
}
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
@import '../../scss/variables.scss';
|
||||
|
||||
.dank-modal {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
width: 70%;
|
||||
height: 70%;
|
||||
background-color: transparentize(#222, 0.07);
|
||||
border-radius: 1rem;
|
||||
padding: 1rem;
|
||||
margin: auto;
|
||||
border: 1px solid $color-grey;
|
||||
|
||||
h3 {
|
||||
text-align: center;
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
color: $color-light;
|
||||
}
|
||||
|
||||
|
|
10
frontend/src/layout/Row/index.tsx
Normal file
10
frontend/src/layout/Row/index.tsx
Normal file
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
import './style.scss'
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const Row = ({ children }: Props) => {
|
||||
return <div className="dank-row">{children}</div>
|
||||
}
|
6
frontend/src/layout/Row/style.scss
Normal file
6
frontend/src/layout/Row/style.scss
Normal file
|
@ -0,0 +1,6 @@
|
|||
.dank-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin: 1rem 0;
|
||||
}
|
|
@ -11,7 +11,7 @@ export const Dashboard = () => {
|
|||
<Page>
|
||||
<h1>Remaining Balances</h1>
|
||||
{accounts.data?.map((account) => (
|
||||
<div className="account-overview">
|
||||
<div key={account.id} className="account-overview">
|
||||
<h3>{account.name}</h3>
|
||||
<div className="funds">
|
||||
{account.stacks.map((stack) => (
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
border: 1px solid $color-grey;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin: 1rem 0;
|
||||
padding: 1rem;
|
||||
background: $color-alt;
|
||||
|
|
|
@ -3,4 +3,5 @@ $color-alt: #c9d8b6;
|
|||
$color-light: #f1ecc3;
|
||||
$color-white: #fff;
|
||||
$color-grey: #515e63;
|
||||
$color-black: #111;
|
||||
$color-dark: #333;
|
||||
|
|
|
@ -28,7 +28,7 @@ export type Stack = {
|
|||
account: string //'38485982-87f3-4a11-a963-2202983809e3'
|
||||
name: string // 'House Fund'
|
||||
details: string //'buy furniture'
|
||||
amount: number // '200.00'
|
||||
amount: string // number // '200.00'
|
||||
transactions: Transaction[]
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ export type Transaction = {
|
|||
id: string
|
||||
stack: string // '0058cece-3ff3-4ee1-b71d-075a0bc73bc0'
|
||||
details: string // 'by ghetto couch off Kijiji'
|
||||
amount: number // '30.44'
|
||||
amount: string // number // '30.44'
|
||||
created_at: string // '2021-04-15T00:02:45.096071Z'
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Link } from 'react-router-dom'
|
||||
import { message } from 'antd'
|
||||
import './style.scss'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { useAccounts } from '../../hooks/getMany/useAccounts'
|
||||
|
||||
import './style.scss'
|
||||
|
||||
type Props = {
|
||||
selectProfile: (id: string) => void
|
||||
}
|
||||
|
|
|
@ -1,40 +1,47 @@
|
|||
import { Stack } from '../../types'
|
||||
import './style.scss'
|
||||
import _ from 'lodash'
|
||||
import { useState } from 'react'
|
||||
import { Modal } from '../../layout/Modal'
|
||||
import { TransactionForm } from '../../forms/TransactionForm'
|
||||
|
||||
type Props = {
|
||||
stack: Stack
|
||||
}
|
||||
|
||||
export const FundBar = ({ stack }: Props) => {
|
||||
const amount = _.sumBy(stack.transactions, 'amount')
|
||||
console.log('amount', stack.id, amount)
|
||||
const max = stack.amount
|
||||
const amount = _.sumBy(stack.transactions, (tx) => parseFloat(tx.amount))
|
||||
const max = parseFloat(stack.amount)
|
||||
const current = max - amount
|
||||
const percent = Math.max(current / max, 0)
|
||||
const hue = percent * 120
|
||||
const u = Math.max(current / max, 0)
|
||||
|
||||
const [newTx, setNewTx] = useState(false)
|
||||
|
||||
const addTransaction = () => {
|
||||
console.log(`adding transaction to => ${stack.name}`)
|
||||
setNewTx(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="fundbar">
|
||||
<div
|
||||
className={`back`}
|
||||
onClick={() => console.log(`adding transaction to => ${stack.name}`)}
|
||||
></div>
|
||||
<div
|
||||
className={`front`}
|
||||
style={{
|
||||
background: `hsl(${hue}, 100%, 50%)`,
|
||||
height: `${percent * 40 + 10}vmin`,
|
||||
}}
|
||||
>
|
||||
<div className="back" onClick={addTransaction}>
|
||||
<h3>{stack.name}</h3>
|
||||
</div>
|
||||
<div
|
||||
className={`totals`}
|
||||
style={{ color: `hsl(0, 0%, ${Math.abs(1 - percent) * 100}%)` }}
|
||||
>
|
||||
className="front"
|
||||
style={{
|
||||
background: `hsl(${u * 120}, 100%, 50%)`,
|
||||
height: `${u * 100}%`,
|
||||
}}
|
||||
></div>
|
||||
<div className="totals">
|
||||
${Math.floor(current)} / ${max}
|
||||
</div>
|
||||
</div>
|
||||
<Modal visible={newTx} onCancel={() => setNewTx(false)}>
|
||||
<TransactionForm stackId={stack.id} />
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
margin: 1rem;
|
||||
display: grid;
|
||||
grid-template: 1fr / 1fr;
|
||||
height: 20vh;
|
||||
|
||||
.totals {
|
||||
pointer-events: none;
|
||||
|
@ -18,17 +19,20 @@
|
|||
|
||||
.front {
|
||||
grid-area: 1/1/2/2;
|
||||
margin: auto auto 0;
|
||||
border-radius: 1ch;
|
||||
pointer-events: none;
|
||||
transition: all 0.2s ease-out;
|
||||
border: 2px solid $color-dark;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2ch;
|
||||
width: 100%;
|
||||
min-height: 1rem;
|
||||
text-shadow: 2px 2px #2223, -1px -1px #fffa;
|
||||
}
|
||||
|
||||
.back {
|
||||
padding: 0.25rem;
|
||||
grid-area: 1/1/2/2;
|
||||
background: #222;
|
||||
border-radius: 1ch;
|
||||
|
|
Loading…
Reference in New Issue
Block a user