new form and form tweaks

main
Elijah Lucian 3 years ago
parent 2ebe8bcd1f
commit 4a1507eba4
  1. 4
      frontend/src/MainLayout.tsx
  2. 5
      frontend/src/app.scss
  3. 10
      frontend/src/contexts/AppContext.tsx
  4. 23
      frontend/src/elements/Form/style.scss
  5. 1
      frontend/src/elements/FormItem/index.tsx
  6. 5
      frontend/src/elements/FormItem/style.scss
  7. 4
      frontend/src/elements/InputNumber/index.tsx
  8. 33
      frontend/src/elements/InputNumber/style.scss
  9. 53
      frontend/src/forms/TransactionForm.tsx
  10. 2
      frontend/src/hooks/create/useCreateAccount.ts
  11. 2
      frontend/src/hooks/create/useCreateStack.ts
  12. 6
      frontend/src/hooks/create/useCreateTransaction.ts
  13. 2
      frontend/src/hooks/create/useCreateUser.ts
  14. 3
      frontend/src/hooks/getMany/useTransactions.ts
  15. 15
      frontend/src/layout/Modal/index.tsx
  16. 15
      frontend/src/layout/Modal/style.scss
  17. 10
      frontend/src/layout/Row/index.tsx
  18. 6
      frontend/src/layout/Row/style.scss
  19. 2
      frontend/src/pages/Dashboard/index.tsx
  20. 1
      frontend/src/pages/Dashboard/style.scss
  21. 1
      frontend/src/scss/variables.scss
  22. 4
      frontend/src/types/index.ts
  23. 3
      frontend/src/widgets/AccountSelect/index.tsx
  24. 57
      frontend/src/widgets/FundBar/index.tsx
  25. 6
      frontend/src/widgets/FundBar/style.scss

@ -30,7 +30,9 @@ export const MainLayout = () => {
<Switch> <Switch>
<Route path="/sign-up" component={NewUser} /> <Route path="/sign-up" component={NewUser} />
<Route path="/login" component={Login} /> <Route path="/login" component={Login} />
<div>loading...</div> <Route path="/">
<div>loading...</div>
</Route>
</Switch> </Switch>
) : ( ) : (
<Switch> <Switch>

@ -6,11 +6,6 @@
align-items: center; align-items: center;
h1 {
margin-top: 2.5vmin;
margin-bottom: 1vmin;
}
main { main {
height: 100%; height: 100%;
} }

@ -9,7 +9,7 @@ type Props = {
type AppContextInterface = { type AppContextInterface = {
get: <T>(path: string) => Promise<T> get: <T>(path: string) => Promise<T>
patch: <T>(path: string, body: Partial<T>) => 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> create: <T>(path: string, body: Partial<T>) => Promise<T>
destroy: (path: string) => Promise<string> destroy: (path: string) => Promise<string>
baseURL?: string baseURL?: string
@ -39,8 +39,12 @@ export const AppContextProvider = ({ children, baseURL }: Props) => {
return res.data return res.data
} }
async function post<T, R = T>(path: string, body: Partial<T>) { async function post<T, R = T>(path: string, body: Partial<T>) {
const res = await api.post<T, AxiosResponse<R>>(path, body) try {
return res.data 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>) { async function create<T>(path: string, body: Partial<T>) {
// unauthed POST // unauthed POST

@ -1,11 +1,30 @@
@import '../../scss/variables.scss';
.dank-form { .dank-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 3ch; 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 { label {
margin: 0.5ch; margin: 0.5ch;
display: flex; display: flex;
font-size: 1.5rem;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
@ -13,8 +32,4 @@
margin-left: 2ch; margin-left: 2ch;
} }
} }
button {
margin: 4ch 1ch;
}
} }

@ -1,5 +1,6 @@
import { FormItemProps } from 'antd' import { FormItemProps } from 'antd'
import AntFormItem from 'antd/lib/form/FormItem' import AntFormItem from 'antd/lib/form/FormItem'
import './style.scss'
type Props = FormItemProps type Props = FormItemProps

@ -0,0 +1,5 @@
.dank-form-item {
label {
color: white;
}
}

@ -1,7 +1,9 @@
import { InputNumber as AntInputNumber, InputNumberProps } from 'antd' import { InputNumber as AntInputNumber, InputNumberProps } from 'antd'
import './style.scss'
type Props = InputNumberProps type Props = InputNumberProps
export const InputNumber = (props: Props) => { export const InputNumber = (props: Props) => {
return <AntInputNumber {...props} /> return <AntInputNumber className="dank-input-number" {...props} />
} }

@ -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;
}
}

@ -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 = () => { export const useCreateAccount = () => {
const api = useAppContext() const api = useAppContext()
return (body: Omit<Account, 'id'>): Promise<Account> => return (body: Omit<Account, 'id'>): Promise<Account | null> =>
api.post<Account>('/accounts', body) api.post<Account>('/accounts', body)
} }

@ -4,6 +4,6 @@ import { Stack } from '../../types'
export const useCreateStack = () => { export const useCreateStack = () => {
const api = useAppContext() const api = useAppContext()
return (body: Omit<Stack, 'id'>): Promise<Stack> => return (body: Omit<Stack, 'id'>): Promise<Stack | null> =>
api.post<Stack>('/stacks', body) api.post<Stack>('/stacks', body)
} }

@ -1,9 +1,11 @@
import { useAppContext } from '../../contexts/AppContext' import { useAppContext } from '../../contexts/AppContext'
import { Transaction } from '../../types' import { Transaction } from '../../types'
export type NewTransaction = Omit<Transaction, 'id'>
export const useCreateTransaction = () => { export const useCreateTransaction = () => {
const api = useAppContext() const api = useAppContext()
return (body: Omit<Transaction, 'id'>): Promise<Transaction> => return (body: NewTransaction): Promise<Transaction | null> =>
api.post<Transaction>('/transactions', body) api.post<Transaction>('/transactions/', body)
} }

@ -4,6 +4,6 @@ import { User } from '../../types'
export const useCreateUser = () => { export const useCreateUser = () => {
const api = useAppContext() const api = useAppContext()
return (body: Omit<User, 'id'>): Promise<User> => return (body: Omit<User, 'id'>): Promise<User | null> =>
api.post<User>('/users', body) api.post<User>('/users', body)
} }

@ -1,4 +1,5 @@
import { Transaction } from '../../types' import { Transaction } from '../../types'
import { useGet } from '../util/useGet' 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 { ModalProps } from 'antd'
import { ReactNode } from 'react' import { ReactElement, cloneElement } from 'react'
import './style.scss' import './style.scss'
type Props = ModalProps & { children: ReactNode } type Props = ModalProps & { children: ReactElement }
export const Modal = ({ children, ...props }: Props) => { export type DankFormProps = {
return <div className="dank-modal">{children}</div> 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'; @import '../../scss/variables.scss';
.dank-modal { .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; border: 1px solid $color-grey;
h3 { h1,
text-align: center; h2,
h3,
h4,
h5 {
color: $color-light; color: $color-light;
} }

@ -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>
}

@ -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> <Page>
<h1>Remaining Balances</h1> <h1>Remaining Balances</h1>
{accounts.data?.map((account) => ( {accounts.data?.map((account) => (
<div className="account-overview"> <div key={account.id} className="account-overview">
<h3>{account.name}</h3> <h3>{account.name}</h3>
<div className="funds"> <div className="funds">
{account.stacks.map((stack) => ( {account.stacks.map((stack) => (

@ -5,7 +5,6 @@
border: 1px solid $color-grey; border: 1px solid $color-grey;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
text-align: center;
margin: 1rem 0; margin: 1rem 0;
padding: 1rem; padding: 1rem;
background: $color-alt; background: $color-alt;

@ -3,4 +3,5 @@ $color-alt: #c9d8b6;
$color-light: #f1ecc3; $color-light: #f1ecc3;
$color-white: #fff; $color-white: #fff;
$color-grey: #515e63; $color-grey: #515e63;
$color-black: #111;
$color-dark: #333; $color-dark: #333;

@ -28,7 +28,7 @@ export type Stack = {
account: string //'38485982-87f3-4a11-a963-2202983809e3' account: string //'38485982-87f3-4a11-a963-2202983809e3'
name: string // 'House Fund' name: string // 'House Fund'
details: string //'buy furniture' details: string //'buy furniture'
amount: number // '200.00' amount: string // number // '200.00'
transactions: Transaction[] transactions: Transaction[]
} }
@ -36,7 +36,7 @@ export type Transaction = {
id: string id: string
stack: string // '0058cece-3ff3-4ee1-b71d-075a0bc73bc0' stack: string // '0058cece-3ff3-4ee1-b71d-075a0bc73bc0'
details: string // 'by ghetto couch off Kijiji' 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' created_at: string // '2021-04-15T00:02:45.096071Z'
} }

@ -1,9 +1,10 @@
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { message } from 'antd' import { message } from 'antd'
import './style.scss'
import { Button } from '../../elements/Button' import { Button } from '../../elements/Button'
import { useAccounts } from '../../hooks/getMany/useAccounts' import { useAccounts } from '../../hooks/getMany/useAccounts'
import './style.scss'
type Props = { type Props = {
selectProfile: (id: string) => void selectProfile: (id: string) => void
} }

@ -1,40 +1,47 @@
import { Stack } from '../../types' import { Stack } from '../../types'
import './style.scss' import './style.scss'
import _ from 'lodash' import _ from 'lodash'
import { useState } from 'react'
import { Modal } from '../../layout/Modal'
import { TransactionForm } from '../../forms/TransactionForm'
type Props = { type Props = {
stack: Stack stack: Stack
} }
export const FundBar = ({ stack }: Props) => { export const FundBar = ({ stack }: Props) => {
const amount = _.sumBy(stack.transactions, 'amount') const amount = _.sumBy(stack.transactions, (tx) => parseFloat(tx.amount))
console.log('amount', stack.id, amount) const max = parseFloat(stack.amount)
const max = stack.amount
const current = max - amount const current = max - amount
const percent = Math.max(current / max, 0) const u = Math.max(current / max, 0)
const hue = percent * 120
const [newTx, setNewTx] = useState(false)
const addTransaction = () => {
console.log(`adding transaction to => ${stack.name}`)
setNewTx(true)
}
return ( return (
<div className="fundbar"> <>
<div <div className="fundbar">
className={`back`} <div className="back" onClick={addTransaction}>
onClick={() => console.log(`adding transaction to => ${stack.name}`)} <h3>{stack.name}</h3>
></div> </div>
<div <div
className={`front`} className="front"
style={{ style={{
background: `hsl(${hue}, 100%, 50%)`, background: `hsl(${u * 120}, 100%, 50%)`,
height: `${percent * 40 + 10}vmin`, height: `${u * 100}%`,
}} }}
> ></div>
<h3>{stack.name}</h3> <div className="totals">
</div> ${Math.floor(current)} / ${max}
<div </div>
className={`totals`}
style={{ color: `hsl(0, 0%, ${Math.abs(1 - percent) * 100}%)` }}
>
${Math.floor(current)} / ${max}
</div> </div>
</div> <Modal visible={newTx} onCancel={() => setNewTx(false)}>
<TransactionForm stackId={stack.id} />
</Modal>
</>
) )
} }

@ -6,6 +6,7 @@
margin: 1rem; margin: 1rem;
display: grid; display: grid;
grid-template: 1fr / 1fr; grid-template: 1fr / 1fr;
height: 20vh;
.totals { .totals {
pointer-events: none; pointer-events: none;
@ -18,17 +19,20 @@
.front { .front {
grid-area: 1/1/2/2; grid-area: 1/1/2/2;
margin: auto auto 0;
border-radius: 1ch; border-radius: 1ch;
pointer-events: none; pointer-events: none;
transition: all 0.2s ease-out; transition: all 0.2s ease-out;
border: 2px solid $color-dark; border: 2px solid $color-dark;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 2ch; width: 100%;
min-height: 1rem;
text-shadow: 2px 2px #2223, -1px -1px #fffa; text-shadow: 2px 2px #2223, -1px -1px #fffa;
} }
.back { .back {
padding: 0.25rem;
grid-area: 1/1/2/2; grid-area: 1/1/2/2;
background: #222; background: #222;
border-radius: 1ch; border-radius: 1ch;

Loading…
Cancel
Save