almost mvp
This commit is contained in:
parent
674e3fd253
commit
2ebe8bcd1f
|
@ -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>
|
||||||
|
|
|
@ -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!')
|
||||||
|
|
|
@ -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' })
|
||||||
|
|
|
@ -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' })
|
||||||
|
|
|
@ -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/`)
|
|
||||||
|
|
4
frontend/src/hooks/getOne/useAccount.ts
Normal file
4
frontend/src/hooks/getOne/useAccount.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { Account } from '../../types'
|
||||||
|
import { useGet } from '../util/useGet'
|
||||||
|
|
||||||
|
export const useAccount = (id: string) => useGet<Account>(`/accounts/${id}/`)
|
|
@ -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
|
||||||
|
if (config?.mode === 'list') {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
setData(data.results)
|
setData(data.results)
|
||||||
}, [path])
|
} else {
|
||||||
|
setData(data)
|
||||||
|
}
|
||||||
|
}, [path, config])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
get()
|
get()
|
||||||
|
|
10
frontend/src/layout/Page/index.tsx
Normal file
10
frontend/src/layout/Page/index.tsx
Normal 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>
|
||||||
|
}
|
3
frontend/src/layout/Page/style.scss
Normal file
3
frontend/src/layout/Page/style.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.dank-page {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
|
@ -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>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
{accounts.data?.map((account) => (
|
||||||
|
<div className="account-overview">
|
||||||
|
<h3>{account.name}</h3>
|
||||||
<div className="funds">
|
<div className="funds">
|
||||||
{stacks.data.map((stack, i) => (
|
{account.stacks.map((stack) => (
|
||||||
<FundBar key={stack.id} stack={stack} col={i + 1} />
|
<FundBar key={stack.id} stack={stack} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
|
))}
|
||||||
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
19
frontend/src/pages/Dashboard/style.scss
Normal file
19
frontend/src/pages/Dashboard/style.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user