adapting and shit
This commit is contained in:
parent
1af8209b99
commit
2b94223389
34
README.md
34
README.md
|
@ -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
|
||||
|
|
|
@ -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 (
|
||||
|
|
38
frontend/src/CoreLayout.tsx
Normal file
38
frontend/src/CoreLayout.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -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;
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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')
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -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>) {
|
||||
|
|
|
@ -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)
|
||||
|
|
13
frontend/src/elements/Button/index.tsx
Normal file
13
frontend/src/elements/Button/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
4
frontend/src/elements/Button/style.scss
Normal file
4
frontend/src/elements/Button/style.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
.dank-button {
|
||||
cursor: pointer;
|
||||
// shit here
|
||||
}
|
8
frontend/src/elements/Form/index.tsx
Normal file
8
frontend/src/elements/Form/index.tsx
Normal 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>
|
||||
}
|
14
frontend/src/elements/Toast/index.tsx
Normal file
14
frontend/src/elements/Toast/index.tsx
Normal 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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
.status {
|
||||
.dank-toast {
|
||||
position: absolute;
|
||||
left: 1ch;
|
||||
bottom: 4ch;
|
|
@ -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>
|
5
frontend/src/hooks/getMany/useStacks.ts
Normal file
5
frontend/src/hooks/getMany/useStacks.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { Stack } from '../../types'
|
||||
import { useGet } from '../util/useGet'
|
||||
|
||||
export const useStacks = (accountId: string) =>
|
||||
useGet<Stack[]>(`/accounts/${accountId}/stacks`)
|
|
@ -1,4 +1,4 @@
|
|||
import { useUserContext } from '../../../contexts/UserContext'
|
||||
import { useUserContext } from '../../contexts/UserContext'
|
||||
|
||||
import './style.scss'
|
||||
|
|
@ -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'
|
||||
|
19
frontend/src/pages/Dashboard/index.tsx
Normal file
19
frontend/src/pages/Dashboard/index.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -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 }
|
|
@ -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
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import '../../scss/transaction-list.scss'
|
||||
import './style.scss'
|
||||
|
||||
export const TransactionList = () => {
|
||||
return (
|
|
@ -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
|
|
@ -1,5 +1,5 @@
|
|||
import { Stack } from '../../types'
|
||||
import '../../scss/fund-bar.scss'
|
||||
import './style.scss'
|
||||
|
||||
type Props = {
|
||||
col: number
|
Loading…
Reference in New Issue
Block a user