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>
<Route path="/sign-up" component={NewUser} />
<Route path="/login" component={Login} />
<div>loading...</div>
<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>) {
const res = await api.post<T, AxiosResponse<R>>(path, body)
return res.data
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

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

@ -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 = () => {
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;
}

@ -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>
<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`,
}}
>
<h3>{stack.name}</h3>
</div>
<div
className={`totals`}
style={{ color: `hsl(0, 0%, ${Math.abs(1 - percent) * 100}%)` }}
>
${Math.floor(current)} / ${max}
<>
<div className="fundbar">
<div className="back" onClick={addTransaction}>
<h3>{stack.name}</h3>
</div>
<div
className="front"
style={{
background: `hsl(${u * 120}, 100%, 50%)`,
height: `${u * 100}%`,
}}
></div>
<div className="totals">
${Math.floor(current)} / ${max}
</div>
</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…
Cancel
Save