almost mvp

This commit is contained in:
Elijah Lucian 2021-07-16 22:07:41 -06:00
parent 674e3fd253
commit 2ebe8bcd1f
17 changed files with 130 additions and 68 deletions

View File

@ -1,6 +1,6 @@
import { useUserContext } from './contexts/UserContext' import { useUserContext } from './contexts/UserContext'
import { Route, Switch } from 'react-router' import { Route, Switch } from 'react-router'
import { Link, useHistory } from 'react-router-dom' import { Link, Redirect, useHistory } from 'react-router-dom'
import { Dashboard } from './pages/Dashboard' import { Dashboard } from './pages/Dashboard'
import { NewUser } from './pages/NewUser' import { NewUser } from './pages/NewUser'
import { TransactionList } from './pages/TransactionList' import { TransactionList } from './pages/TransactionList'
@ -38,7 +38,8 @@ export const MainLayout = () => {
<Route path="/transactions" component={TransactionList} /> <Route path="/transactions" component={TransactionList} />
<Route path="/accounts/new" component={AccountForm} /> <Route path="/accounts/new" component={AccountForm} />
<Route path="/accounts" component={AccountList} /> <Route path="/accounts" component={AccountList} />
<Route path="/" component={Dashboard} /> <Route path="/dashboard" component={Dashboard} />
<Redirect to="/dashboard" />
</Switch> </Switch>
)} )}
</main> </main>

View File

@ -5,6 +5,7 @@ import { User } from '../types'
import { useAppContext } from './AppContext' import { useAppContext } from './AppContext'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useRef } from 'react' import { useRef } from 'react'
import axios from 'axios'
type Props = { type Props = {
children: React.ReactNode children: React.ReactNode
@ -48,17 +49,18 @@ export const UserContextProvider = ({ children }: Props) => {
const handleLogin = async (username: string, password: string) => { const handleLogin = async (username: string, password: string) => {
try { try {
const { key } = await api.current.post<any, { key: string }>( const res = await axios.post<{ key: string }>(
'/dj-rest-auth/login/', 'http://localhost:8000/dj-rest-auth/login/',
{ {
username, username,
password, password,
}, },
) )
if (!key) throw new Error('Problem logging in!') if (!res.data.key) throw new Error('Problem logging in!')
window.localStorage.setItem('cash-stacks-token', key)
window.localStorage.setItem('cash-stacks-token', res.data.key)
const user = await api.current.get<User>(`/dj-rest-auth/user/`) const user = await api.current.get<User>(`/dj-rest-auth/user/`)
console.log('user', user)
setUser(user) setUser(user)
} catch (err) { } catch (err) {
message.error('Login Failed!') message.error('Login Failed!')

View File

@ -1,4 +1,5 @@
import { Account } from '../../types' import { Account } from '../../types'
import { useGet } from '../util/useGet' import { useGet } from '../util/useGet'
export const useAccounts = () => useGet<Account[]>(`/accounts/`) export const useAccounts = () =>
useGet<Account[]>(`/accounts/`, { mode: 'list' })

View File

@ -1,4 +1,4 @@
import { Stack } from '../../types' import { Stack } from '../../types'
import { useGet } from '../util/useGet' import { useGet } from '../util/useGet'
export const useStacks = () => useGet<Stack[]>(`/stacks/`) export const useStacks = () => useGet<Stack[]>(`/stacks/`, { mode: 'list' })

View File

@ -1,5 +1,4 @@
import { Transaction } from '../../types' import { Transaction } from '../../types'
import { useGet } from '../util/useGet' import { useGet } from '../util/useGet'
export const useTransactions = (accountId: string) => export const useTransactions = () => useGet<Transaction[]>(`/transactions/`)
useGet<Transaction[]>(`/transactions/`)

View File

@ -0,0 +1,4 @@
import { Account } from '../../types'
import { useGet } from '../util/useGet'
export const useAccount = (id: string) => useGet<Account>(`/accounts/${id}/`)

View File

@ -1,16 +1,24 @@
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import { useAppContext } from '../../contexts/AppContext' import { useAppContext } from '../../contexts/AppContext'
export const useGet = <T>(path: string) => { type Config = {
mode: 'list'
}
export const useGet = <T>(path: string, config?: Config) => {
const appContext = useRef(useAppContext()) const appContext = useRef(useAppContext())
const [data, setData] = useState<T | null>(null) const [data, setData] = useState<T | null>(null)
const get = useCallback(async () => { const get = useCallback(async () => {
const data = await appContext.current.get<T>(path) const data = await appContext.current.get<T>(path)
if (!data) return if (!data) return
// @ts-ignore if (config?.mode === 'list') {
setData(data.results) // @ts-ignore
}, [path]) setData(data.results)
} else {
setData(data)
}
}, [path, config])
useEffect(() => { useEffect(() => {
get() get()

View File

@ -0,0 +1,10 @@
import React from 'react'
import './style.scss'
type Props = {
children: React.ReactNode
}
export const Page = ({ children }: Props) => {
return <div className="dank-page">{children}</div>
}

View File

@ -0,0 +1,3 @@
.dank-page {
padding: 1rem;
}

View File

@ -1,7 +1,13 @@
import { useAccounts } from '../../hooks/getMany/useAccounts' import { useAccounts } from '../../hooks/getMany/useAccounts'
import { Page } from '../../layout/Page'
export const AccountList = () => { export const AccountList = () => {
const accounts = useAccounts() const accounts = useAccounts()
return <div>Account Count: {accounts.data?.length}</div> return (
<Page>
<h1>Account List</h1>
<p>Count: {accounts.data?.length}</p>
</Page>
)
} }

View File

@ -1,19 +1,25 @@
import { FundBar } from '../../widgets/FundBar' import { FundBar } from '../../widgets/FundBar'
import { useStacks } from '../../hooks/getMany/useStacks' import { Page } from '../../layout/Page'
import './style.scss'
import { useAccounts } from '../../hooks/getMany/useAccounts'
export const Dashboard = () => { export const Dashboard = () => {
const stacks = useStacks() const accounts = useAccounts()
if (!accounts.data) return <div>loading...</div>
if (!stacks.data) return <div>loading...</div>
return ( return (
<> <Page>
<h1>Remaining Balances</h1> <h1>Remaining Balances</h1>
<div className="funds"> {accounts.data?.map((account) => (
{stacks.data.map((stack, i) => ( <div className="account-overview">
<FundBar key={stack.id} stack={stack} col={i + 1} /> <h3>{account.name}</h3>
))} <div className="funds">
</div> {account.stacks.map((stack) => (
</> <FundBar key={stack.id} stack={stack} />
))}
</div>
</div>
))}
</Page>
) )
} }

View File

@ -0,0 +1,19 @@
@import '../../scss/variables.scss';
.account-overview {
display: flex;
border: 1px solid $color-grey;
flex-direction: column;
align-items: center;
text-align: center;
margin: 1rem 0;
padding: 1rem;
background: $color-alt;
.funds {
display: flex;
justify-content: center;
flex-direction: row;
flex-wrap: wrap;
}
}

View File

@ -1,18 +1,26 @@
import { useTransactions } from '../../hooks/getMany/useTransactions'
import { Page } from '../../layout/Page'
import './style.scss' import './style.scss'
export const TransactionList = () => { export const TransactionList = () => {
const transactions = useTransactions()
return ( return (
<> <Page>
<h1>Transactions</h1> <h1>Transactions</h1>
<div className="transactions"> <div className="transactions">
{[].map((account, i) => { {transactions.data?.map((tx, i) => {
return ( return (
<div className="column" key={`${account}-txs-${i}`}> <div className="dank-transaction" key={`${tx.id}-txs-${i}`}>
<h3>{account}</h3> <p>
{tx.details}: ${tx.amount}
</p>
<p>on: {tx.created_at}</p>
</div> </div>
) )
})} })}
</div> </div>
</> </Page>
) )
} }

View File

@ -1,21 +1,10 @@
.transactions { @import '../../scss/variables.scss';
.column {
h3 {
text-align: center;
text-decoration: underline;
}
margin: 2ch;
background: #fff1;
padding: 0 2ch 1ch;
border-radius: 1ch;
}
.transaction-item { .transactions {
display: flex; .dank-transaction {
flex-direction: row; border: 1px solid $color-dark;
justify-content: space-between; margin: 1rem 0;
.details { background: #fff1;
padding-left: 1ch; padding: 1rem;
}
} }
} }

View File

@ -20,6 +20,7 @@ export type Account = {
details: string details: string
income: number income: number
expenses: number expenses: number
stacks: Stack[]
} }
export type Stack = { export type Stack = {

View File

@ -1,27 +1,27 @@
import { Stack } from '../../types' import { Stack } from '../../types'
import './style.scss' import './style.scss'
import _ from 'lodash'
type Props = { type Props = {
col: number
stack: Stack stack: Stack
} }
export const FundBar = ({ stack, col }: Props) => { export const FundBar = ({ stack }: Props) => {
const amount = 0 const amount = _.sumBy(stack.transactions, 'amount')
console.log('amount', stack.id, amount)
const max = stack.amount const max = stack.amount
const current = max - amount const current = max - amount
const percent = Math.max(current / max, 0) const percent = Math.max(current / max, 0)
const hue = percent * 120 const hue = percent * 120
return ( return (
<> <div className="fundbar">
<div <div
className={`fundbar back col${col}`} className={`back`}
onClick={() => console.log(`adding transaction to => ${stack.name}`)} onClick={() => console.log(`adding transaction to => ${stack.name}`)}
></div> ></div>
<div <div
className={`fundbar front col${col}`} className={`front`}
style={{ style={{
background: `hsl(${hue}, 100%, 50%)`, background: `hsl(${hue}, 100%, 50%)`,
height: `${percent * 40 + 10}vmin`, height: `${percent * 40 + 10}vmin`,
@ -30,11 +30,11 @@ export const FundBar = ({ stack, col }: Props) => {
<h3>{stack.name}</h3> <h3>{stack.name}</h3>
</div> </div>
<div <div
className={`fundbar totals col${col}`} className={`totals`}
style={{ color: `hsl(0, 0%, ${Math.abs(1 - percent) * 100}%)` }} style={{ color: `hsl(0, 0%, ${Math.abs(1 - percent) * 100}%)` }}
> >
${Math.floor(current)} / ${max} ${Math.floor(current)} / ${max}
</div> </div>
</> </div>
) )
} }

View File

@ -1,32 +1,37 @@
@import '../../scss/variables.scss';
.fundbar { .fundbar {
cursor: pointer; cursor: pointer;
border-radius: 1ch;
text-align: center; text-align: center;
margin: auto 2ch 0 2ch; margin: 1rem;
display: grid;
grid-template: 1fr / 1fr;
&.totals { .totals {
pointer-events: none; pointer-events: none;
margin-top: 2ch; margin-top: 1rem;
} }
h3 { h3 {
margin: auto auto 0 auto; margin: auto auto 0 auto;
} }
&.front { .front {
grid-area: 1/1/2/2;
border-radius: 1ch;
pointer-events: none; pointer-events: none;
transition: all 0.2s ease-out; transition: all 0.2s ease-out;
border: 2px solid #222a; border: 2px solid $color-dark;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 2ch; padding: 2ch;
text-shadow: 2px 2px #2223, -1px -1px #fffa; text-shadow: 2px 2px #2223, -1px -1px #fffa;
} }
&.back { .back {
grid-area: 1/1/2/2;
background: #222; background: #222;
border-radius: 1ch;
border: 4px solid #3334; border: 4px solid #3334;
height: 50vh;
} }
} }