adapting and shit

This commit is contained in:
Elijah Lucian 2021-07-13 17:47:28 -06:00
parent 1af8209b99
commit 2b94223389
35 changed files with 163 additions and 160 deletions

View File

@ -1,31 +1,13 @@
# mvp-django-react
# Cash Dash Stacks
An MVP boilerplate for a django / react app.
- A simple way to track expenses
- cash budgeting in digital mode
# Setup
# Ideas
```
python manage.py migrate
python manage.py createsuperuser --email admin@example.com --username admin --password whatever
```
- put items in your wishlist and track savings to buy big-ticket items
- view spending trends
# Boilerplate Features
# Todo
- Authentication
- User Table (username, email, password)
# Extras
- dockerized
# Frontend
- [x] User context
- [x] auth api
- [ ] user crud api (new user flow)
# Backend
- user CRUD
- login / logout / forgot password
- serve static index (prod)
https://github.com/dank-inc/cash-stacks/projects

View File

@ -1,10 +1,9 @@
import { BrowserRouter } from 'react-router-dom'
import { UserContextProvider } from './contexts/UserContext'
import { CoreLayout } from './app/CoreLayout'
import { CoreLayout } from './CoreLayout'
import { AppContextProvider } from './contexts/AppContext'
import { Api } from './api'
import './scss/app.scss'
import './app.scss'
const App = () => {
return (

View File

@ -0,0 +1,38 @@
import { useUserContext } from './contexts/UserContext'
import { Route, Switch } from 'react-router'
import { Link } from 'react-router-dom'
import { Dashboard } from './pages/Dashboard'
import { NewUser } from './pages/NewUser'
import { TransactionList } from './pages/TransactionList'
import { AccountForm } from './forms/AccountForm'
import { Login } from './pages/Login'
import { AppHeader } from './layout/AppHeader'
import { AppFooter } from './layout/AppFooter'
export const CoreLayout = () => {
const { user } = useUserContext()
if (!user) return <Login />
return (
<div className="app">
<AppHeader>
<Link to="/">Home</Link>
<Link to="/select">Select Budget</Link>
<Link to="/accounts">Budget Details</Link>
<Link to="/transactions">Transactions</Link>
<Link to="/new">New User</Link>
</AppHeader>
<Switch>
<Route path="/users/new" component={NewUser} />
<Route path="/transactions" component={TransactionList} />
<Route path="/accounts/new" component={AccountForm} />
<Route path="/accounts" component={AccountForm} />
<Route path="/" component={Dashboard} />
</Switch>
<AppFooter />
</div>
)
}

View File

@ -1,8 +1,10 @@
// @include './form.scss';
.app {
display: flex;
flex-direction: column;
display: grid;
grid-template: 1fr / auto 1fr auto;
align-items: center;
// justify-content: space-around;
font-size: calc(10px + 2vmin);
color: white;
@ -12,10 +14,6 @@
}
}
button {
cursor: pointer;
}
.funds,
.transactions {
display: grid;

View File

@ -1,39 +0,0 @@
import { useUserContext } from '../contexts/UserContext'
import { Route, Switch } from 'react-router'
import { Link } from 'react-router-dom'
import { Dashboard } from './pages/Dashboard'
import { UserForm } from './components/UserForm'
import { TransactionList } from './components/TransactionList'
import { AccountForm } from './components/AccountForm'
import { Login } from './components/Login'
import { AppHeader } from './layout/AppHeader'
import { AppFooter } from './layout/AppFooter'
export const CoreLayout = () => {
const { user, selectedAccount } = useUserContext()
if (!user) return <Login />
return (
<div className="app">
<AppHeader>
<Link to="/">Home</Link>
<Link to="/select">Select Budget</Link>
<Link to="/account">Budget Details</Link>
<Link to="/details">Transactions</Link>
<Link to="/user">Profile</Link>
</AppHeader>
<Switch>
<Route path="/user" component={UserForm} />
<Route path="/details" component={TransactionList} />
<Route path="/account/new" component={AccountForm} />
<Route path="/account" component={AccountForm} />
<Route path="/" component={Dashboard} />
</Switch>
<AppFooter />
{/* {showingModal && <TransactionModal account={account} />} */}
</div>
)
}

View File

@ -1,15 +1,10 @@
import { useState, useEffect } from 'react'
import { Password, User } from '../../types'
import '../../scss/form.scss'
import { useUserContext } from '../../contexts/UserContext'
import { useAppContext } from '../../contexts/AppContext'
import { useHistory } from 'react-router'
import { message } from 'antd'
export const UserForm = () => {
const history = useHistory()
const { api } = useAppContext()
const { user } = useUserContext()
console.log(user)
@ -43,11 +38,11 @@ export const UserForm = () => {
}
try {
user?.id
? await api.updateUser(user.id, body)
: await api.createUser(body)
if (!user?.id) history.push('/login')
// user?.id
// ? await api.updateUser(user.id, body)
// : await api.createUser(body)
console.log('User form wtf')
// if (!user?.id) history.push('/login')
} catch (err) {
message.error('Something went wrong')
}

View File

@ -1,21 +0,0 @@
import { FundBar } from '../components/FundBar'
import { usePromise } from '@dank-inc/use-get'
import { useAppContext } from '../../contexts/AppContext'
export const Dashboard = () => {
const { api } = useAppContext()
const stacks = usePromise(api.getStacks())
if (stacks.loading || !stacks.data) return <div>loading...</div>
return (
<>
<h1>Remaining Balances</h1>
<div className="funds">
{stacks.data.map((stack, i) => (
<FundBar key={stack.id} stack={stack} col={i + 1} />
))}
</div>
</>
)
}

View File

@ -1,4 +1,4 @@
import Axios from 'axios'
import Axios, { AxiosResponse } from 'axios'
import React, { createContext, useContext } from 'react'
type Props = {
@ -9,7 +9,7 @@ type Props = {
type Context = {
get: <T>(path: string) => Promise<T>
patch: <T>(path: string, body: Partial<T>) => Promise<T>
post: <T>(path: string, body: Partial<T>) => Promise<T>
post: <T, R = T>(path: string, body: Partial<T>) => Promise<R>
create: <T>(path: string, body: Partial<T>) => Promise<T>
destroy: (path: string) => Promise<string>
}
@ -27,8 +27,8 @@ export const AppContextProvider = ({ children, baseURL }: Props) => {
const res = await axios.patch<T>(path, body)
return res.data
}
async function post<T>(path: string, body: Partial<T>) {
const res = await axios.post<T>(path, body)
async function post<T, R = T>(path: string, body: Partial<T>) {
const res = await axios.post<T, AxiosResponse<R>>(path, body)
return res.data
}
async function create<T>(path: string, body: Partial<T>) {

View File

@ -21,7 +21,7 @@ type Context = {
const UserContext = createContext<Context | null>(null)
export const UserContextProvider = ({ children }: Props) => {
const { api } = useAppContext()
const api = useAppContext()
const history = useHistory()
const [user, setUser] = useState<User | null>(null)
@ -30,14 +30,17 @@ export const UserContextProvider = ({ children }: Props) => {
const handleLogin = async (name: string, password: string) => {
try {
const { id } = await api.login(name, password)
const { id } = await api.post<any, { id: string }>('/login', {
name,
password,
})
if (!id) throw new Error('Problem logging in!')
const user = await api.getUser(id)
const user = await api.get<User>(`/users/${id}`)
if (!user) message.error(`Couldn't find user`)
setUser(user)
const accounts = await api.getAccounts()
const accounts = await api.get<Account[]>('/accounts')
setAccounts(accounts)
message.success(`logged in as ${user?.name}`, 0.5)

View File

@ -0,0 +1,13 @@
import { Button as AntButton, ButtonProps } from 'antd'
import React from 'react'
import './style.scss'
type Props = ButtonProps
export const Button = ({ children, htmlType }: Props) => {
return (
<AntButton className="dank-button" htmlType={htmlType}>
{children}
</AntButton>
)
}

View File

@ -0,0 +1,4 @@
.dank-button {
cursor: pointer;
// shit here
}

View File

@ -0,0 +1,8 @@
import { Form as AntForm, FormProps } from 'antd'
import './style.scss'
type Props = FormProps
export const Form = ({ children }: Props) => {
return <AntForm className="dank-form">{children}</AntForm>
}

View File

@ -0,0 +1,14 @@
import React from 'react'
import './style.scss'
type Props = {
title: string
type: 'success' | 'error'
children: React.ReactNode
}
export const Toast = (props: Props) => {
// show me a toast
return null
}

View File

@ -1,4 +1,4 @@
.status {
.dank-toast {
position: absolute;
left: 1ch;
bottom: 4ch;

View File

@ -1,19 +1,15 @@
import React, { useState } from 'react'
import { Account } from '../../types'
import { Account } from '../types'
import '../../scss/form.scss'
import { useAppContext } from '../../contexts/AppContext'
import { message } from 'antd'
import { usePromise } from '@dank-inc/use-get'
import { useStacks } from '../hooks/getMany/useStacks'
type Props = {
account?: Account
}
export const AccountForm = ({ account }: Props) => {
const { api } = useAppContext()
const stacks = usePromise(api.getStacks())
const stacks = useStacks('')
const [name, setName] = useState<string>(account?.name || '')
const [details, setDetails] = useState<string>(account?.details || '')
@ -31,9 +27,9 @@ export const AccountForm = ({ account }: Props) => {
details,
}
account?.id
? await api.updateAccount(account.id, body)
: await api.createAccount(body)
// account?.id
// ? await api.updateAccount(account.id, body)
// : await api.createAccount(body)
message.success('Yaaa')
}
@ -74,15 +70,7 @@ export const AccountForm = ({ account }: Props) => {
{stacks.data?.map((stack) => (
<div key={stack.details} className="form-item">
<label>{stack.name}</label>
<input
type="number"
value={stack.amount}
onChange={(e) =>
api.updateStack(stack.id, {
amount: parseInt(e.target.value),
})
}
></input>
<input type="number" value={stack.amount}></input>
</div>
))}
<button type="submit">{account?.id ? 'Save' : 'Create'}</button>

View File

@ -0,0 +1,5 @@
import { Stack } from '../../types'
import { useGet } from '../util/useGet'
export const useStacks = (accountId: string) =>
useGet<Stack[]>(`/accounts/${accountId}/stacks`)

View File

@ -1,4 +1,4 @@
import { useUserContext } from '../../../contexts/UserContext'
import { useUserContext } from '../../contexts/UserContext'
import './style.scss'

View File

@ -2,7 +2,7 @@ import { ReactNode } from 'react'
import { Avatar, Button, Dropdown, Menu } from 'antd'
import { Header } from 'antd/lib/layout/layout'
import { Link, useHistory } from 'react-router-dom'
import { useUserContext } from '../../../contexts/UserContext'
import { useUserContext } from '../../contexts/UserContext'
import './style.scss'

View File

@ -0,0 +1,19 @@
import { FundBar } from '../../widgets/FundBar'
import { useStacks } from '../../hooks/getMany/useStacks'
export const Dashboard = () => {
const stacks = useStacks('')
if (!stacks.data) return <div>loading...</div>
return (
<>
<h1>Remaining Balances</h1>
<div className="funds">
{stacks.data.map((stack, i) => (
<FundBar key={stack.id} stack={stack} col={i + 1} />
))}
</div>
</>
)
}

View File

@ -3,7 +3,7 @@ import FormItem from 'antd/lib/form/FormItem'
import { Link } from 'react-router-dom'
import { User } from '../../types'
import '../../scss/login.scss'
import './style.scss'
import { useUserContext } from '../../contexts/UserContext'
type FormValues = Pick<User, 'name'> & { password: string }

View File

@ -1,6 +1,7 @@
import { Button, Form, Input } from 'antd'
import { useForm } from 'antd/lib/form/Form'
import { useUserContext } from '../../contexts/UserContext'
import './style.scss'
type Credentials = {
username: string

View File

@ -1,32 +1,29 @@
import { InputNumber } from 'antd'
import { useState } from 'react'
import '../../scss/transaction-modal.scss'
import { useAppContext } from '../../contexts/AppContext'
import { uuid } from '../../types'
type Props = {
stackId: uuid
stackId: string
}
export const TransactionModal = ({ stackId }: Props) => {
const { api } = useAppContext()
const [amount, setAmount] = useState('')
export const TransactionForm = ({ stackId }: Props) => {
const [amount, setAmount] = useState(0)
const [stack, setStack] = useState(stackId)
const [details, setDetails] = useState('')
const [error, setError] = useState<string | null>(null)
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (parseFloat(amount)) {
api.createTransaction({
stack,
details,
amount: parseFloat(amount),
created_at: new Date().toISOString(),
})
if (!amount) return
const body = {
stack,
details,
amount: amount,
created_at: new Date().toISOString(),
}
console.log('creating tx', body)
}
return (
@ -36,11 +33,11 @@ export const TransactionModal = ({ stackId }: Props) => {
<h3>Expense for {stack} account</h3>
<label>
Amount:
<input
<InputNumber
autoFocus
value={amount}
onChange={(e) => setAmount(e.currentTarget.value)}
></input>
onChange={(v) => setAmount(v)}
/>
</label>
<label>
Details:
@ -59,7 +56,6 @@ export const TransactionModal = ({ stackId }: Props) => {
<button type="submit">Submit</button>
</label>
</form>
{error && <div className="error">{error}</div>}
</div>
)
}

View File

@ -1,4 +1,4 @@
import '../../scss/transaction-list.scss'
import './style.scss'
export const TransactionList = () => {
return (

View File

@ -2,7 +2,7 @@ import { Link } from 'react-router-dom'
import { message } from 'antd'
import { useUserContext } from '../../contexts/UserContext'
import '../../scss/account-select.scss'
import './style.scss'
type Props = {
selectProfile: (id: string) => void

View File

@ -1,5 +1,5 @@
import { Stack } from '../../types'
import '../../scss/fund-bar.scss'
import './style.scss'
type Props = {
col: number