motorin along

This commit is contained in:
Elijah Lucian 2021-07-15 23:06:36 -06:00
parent 8ff02f6149
commit 01a1876b3d
23 changed files with 236 additions and 250 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 } from 'react-router-dom' import { Link, 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'
@ -8,29 +8,32 @@ import { AccountForm } from './forms/AccountForm'
import { Login } from './pages/Login' import { Login } from './pages/Login'
import { AppHeader } from './layout/AppHeader' import { AppHeader } from './layout/AppHeader'
import { AppFooter } from './layout/AppFooter' import { AppFooter } from './layout/AppFooter'
import { TransactionForm } from './pages/TransactionForm'
import { Button } from 'antd'
export const CoreLayout = () => { export const CoreLayout = () => {
const { user } = useUserContext() const { user } = useUserContext()
const history = useHistory()
return ( return (
<div className="app"> <div className="app">
<AppHeader> <AppHeader>
<Link to="/">Home</Link> <Link to="/accounts">Accounts</Link>
<Link to="/select">Select Budget</Link> <Button onClick={() => history.push('?new=account')}>New</Button>
<Link to="/accounts">Budget Details</Link>
<Link to="/transactions">Transactions</Link> <Link to="/transactions">Transactions</Link>
<Link to="/new">New User</Link> <Button onClick={() => history.push('?new=transaction')}>New</Button>
</AppHeader> </AppHeader>
<main> <main>
{!user ? ( {!user ? (
<Switch> <Switch>
<Route path="/sign-up" component={NewUser} /> <Route path="/sign-up" component={NewUser} />
<Route path="/" component={Login} /> <Route path="/login" component={Login} />
<div>loading...</div>
</Switch> </Switch>
) : ( ) : (
<Switch> <Switch>
<Route path="/users/new" component={NewUser} /> <Route path="/transactions/new" component={TransactionForm} />
<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={AccountForm} /> <Route path="/accounts" component={AccountForm} />

View File

@ -12,6 +12,7 @@ type AppContextInterface = {
post: <T, R = T>(path: string, body: Partial<T>) => Promise<R> post: <T, R = T>(path: string, body: Partial<T>) => Promise<R>
create: <T>(path: string, body: Partial<T>) => Promise<T> create: <T>(path: string, body: Partial<T>) => Promise<T>
destroy: (path: string) => Promise<string> destroy: (path: string) => Promise<string>
baseURL?: string
} }
const AppContext = createContext<AppContextInterface | null>(null) const AppContext = createContext<AppContextInterface | null>(null)
@ -52,7 +53,7 @@ export const AppContextProvider = ({ children, baseURL }: Props) => {
} }
return ( return (
<AppContext.Provider value={{ get, patch, post, create, destroy }}> <AppContext.Provider value={{ get, patch, post, create, destroy, baseURL }}>
{children} {children}
</AppContext.Provider> </AppContext.Provider>
) )

View File

@ -3,7 +3,8 @@ import { message } from 'antd'
import { useHistory } from 'react-router' import { useHistory } from 'react-router'
import { User } from '../types' import { User } from '../types'
import { useAppContext } from './AppContext' import { useAppContext } from './AppContext'
import { logOut } from '../api' import { useEffect } from 'react'
import { useRef } from 'react'
type Props = { type Props = {
children: React.ReactNode children: React.ReactNode
@ -18,14 +19,36 @@ type Context = {
const UserContext = createContext<Context | null>(null) const UserContext = createContext<Context | null>(null)
export const UserContextProvider = ({ children }: Props) => { export const UserContextProvider = ({ children }: Props) => {
const api = useAppContext() const api = useRef(useAppContext())
const history = useHistory() const history = useRef(useHistory())
const [user, setUser] = useState<User | null>(null) const [user, setUser] = useState<User | null>(null)
useEffect(() => {
const get = async () => {
try {
const token = window.localStorage.getItem('cash-stacks-token')
if (!token) throw new Error()
// const valid = await api.current.post('/dj-rest-auth/token/verify/', {
// token,
// })
// console.log('token valid?', token)
const user = await api.current.get<User>(`/dj-rest-auth/user/`)
setUser(user)
} catch (err) {
console.log(`couldn't get user, token possibly expired`)
// window.localStorage.removeItem('cash-stacks-token')
history.current.push('/login')
}
}
get()
}, [])
const handleLogin = async (username: string, password: string) => { const handleLogin = async (username: string, password: string) => {
try { try {
const { key } = await api.post<any, { key: string }>( const { key } = await api.current.post<any, { key: string }>(
'/dj-rest-auth/login/', '/dj-rest-auth/login/',
{ {
username, username,
@ -34,24 +57,18 @@ export const UserContextProvider = ({ children }: Props) => {
) )
if (!key) throw new Error('Problem logging in!') if (!key) throw new Error('Problem logging in!')
window.localStorage.setItem('cash-stacks-token', key) window.localStorage.setItem('cash-stacks-token', key)
const user = await api.get<User>(`/dj-rest-auth/user/`) const user = await api.current.get<User>(`/dj-rest-auth/user/`)
console.log('user', user) console.log('user', user)
if (!user) message.error(`Couldn't find user`)
setUser(user) setUser(user)
message.success(`logged in as ${user?.name}`, 0.5)
} catch (err) { } catch (err) {
message.error('Login Failed!') message.error('Login Failed!')
} }
} }
const handleLogout = async () => { const handleLogout = async () => {
await logOut() await api.current.post('/dj-rest-auth/logout/', {})
setUser(null) setUser(null)
message.success('logged out!', 0.5) history.current.push('/')
history.push('/')
} }
return ( return (

View File

@ -0,0 +1,8 @@
import { FormItemProps } from 'antd'
import AntFormItem from 'antd/lib/form/FormItem'
type Props = FormItemProps
export const FormItem = (props: Props) => {
return <AntFormItem className="dank-form-item" {...props}></AntFormItem>
}

View File

@ -0,0 +1,7 @@
import { Input as AntInput, InputProps } from 'antd'
type Props = InputProps
export const Input = (props: Props) => {
return <AntInput className="dank-input" {...props}></AntInput>
}

View File

@ -0,0 +1,7 @@
import { InputNumber as AntInputNumber, InputNumberProps } from 'antd'
type Props = InputNumberProps
export const InputNumber = (props: Props) => {
return <AntInputNumber {...props} />
}

View File

@ -9,7 +9,7 @@ type Props = {
} }
export const AccountForm = ({ account }: Props) => { export const AccountForm = ({ account }: Props) => {
const stacks = useStacks('') const stacks = useStacks()
const [name, setName] = useState<string>(account?.name || '') const [name, setName] = useState<string>(account?.name || '')
const [details, setDetails] = useState<string>(account?.details || '') const [details, setDetails] = useState<string>(account?.details || '')

View File

@ -1,88 +1,39 @@
import { useState, useEffect } from 'react' import { User } from '../types'
import { Password, User } from '../types'
import { useUserContext } from '../contexts/UserContext' import { useUserContext } from '../contexts/UserContext'
import { message } from 'antd'
import { Button } from '../elements/Button' import { Button } from '../elements/Button'
import { Form } from '../elements/Form'
import { useForm } from 'antd/lib/form/Form'
import { FormItem } from '../elements/FormItem'
import { Input } from '../elements/Input'
export const UserForm = () => { export const UserForm = () => {
const { user } = useUserContext() const { user } = useUserContext()
if (!user) throw new Error('User should be gotten by this point')
console.log(user) const [form] = useForm<User>()
const [name, setName] = useState(user?.name) const handleFinish = (values: User) => {
const [email, setEmail] = useState(user?.email) console.log(values)
const [password, setPassword] = useState('')
const [passwordConfirm, setPasswordConfirm] = useState('')
const [valid, setValid] = useState(false)
useEffect(() => {
passwordConfirm.length > 4 && password === passwordConfirm && name && email
? setValid(true)
: setValid(false)
}, [password, passwordConfirm, name, email])
// @ts-ignore
const handleSubmit = async (e) => {
e.preventDefault()
if (!name || !email) {
message.error('Name and email required!')
return
}
const body: Omit<User, 'id'> & Password = {
name,
email,
password,
passwordConfirm,
}
try {
// 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')
}
} }
return ( return (
<> <>
<h1>{user?.id ? 'Edit' : 'Create'} Profile</h1> <Form form={form} onFinish={handleFinish} initialValues={user}>
<form className="dank-form" onSubmit={handleSubmit}> <h1>User Profile</h1>
<label> <FormItem label="Username" name="username">
Name: <input value={name} onChange={(e) => setName(e.target.value)} /> <Input />
</label> </FormItem>
<label> <FormItem label="First Name" name="first_name">
Email: <Input />
<input </FormItem>
value={email} <FormItem label="Last Name" name="last_name">
type="email" <Input />
onChange={(e) => setEmail(e.target.value)} </FormItem>
/> <FormItem label="E-Mail" name="email">
</label> <Input type="email" />
<label> </FormItem>
Password: <Button htmlType="submit">Save</Button>
<input </Form>
value={password}
type="password"
onChange={(e) => setPassword(e.target.value)}
/>
</label>
<label>
Confirm Password:
<input
type="password"
onChange={(e) => setPasswordConfirm(e.target.value)}
/>
</label>
<Button htmlType="submit" disabled={!valid}>
Save
</Button>
</form>
</> </>
) )
} }

View File

@ -1,4 +1,4 @@
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/`)

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 = (accountId: string) => useGet<Stack[]>(`/stacks`) export const useStacks = () => useGet<Stack[]>(`/stacks/`)

View File

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

View File

@ -8,8 +8,8 @@ export const useGet = <T>(path: string) => {
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
setData(data) setData(data.results)
}, [path]) }, [path])
useEffect(() => { useEffect(() => {

View File

@ -7,7 +7,7 @@ export const AppFooter = () => {
return ( return (
<footer> <footer>
<p>User: {user?.name}</p> <p>User: {user?.username}</p>
<p>|</p> <p>|</p>
<p>Budget:</p> <p>Budget:</p>
<p>|</p> <p>|</p>

View File

@ -2,9 +2,9 @@ import { ReactNode } from 'react'
import { Avatar, Dropdown, Menu } from 'antd' import { Avatar, Dropdown, Menu } from 'antd'
import { Link, useHistory } from 'react-router-dom' import { Link, useHistory } from 'react-router-dom'
import { useUserContext } from '../../contexts/UserContext' import { useUserContext } from '../../contexts/UserContext'
import { Button } from '../../elements/Button'
import './style.scss' import './style.scss'
import { Button } from '../../elements/Button'
type Props = { type Props = {
children: ReactNode children: ReactNode
@ -27,30 +27,31 @@ export const AppHeader = ({ children }: Props) => {
<header> <header>
<div className="header-left"> <div className="header-left">
<Link to="/"> <Link to="/">
<h3>MVP Django React! 🤠</h3> <h3>CashStacks! 💵</h3>
</Link> </Link>
{children}
</div>
<div>
{/* <Button onClick={() => history.push('/new/user')}>New User</Button> */}
</div>
<div className="header-right">
<p>Welcome, {user.name}!!</p>
<Dropdown
overlay={
<Menu style={{ display: 'flex', flexDirection: 'column' }}>
<Button onClick={() => history.push('/login')}>Login</Button>
<Button onClick={handleLogout}>Logout</Button>
<Button onClick={() => history.push('/forgot-password')}>
Forgot Password
</Button>
<Button onClick={() => history.push('/profile')}>Profile</Button>
</Menu>
}
>
<Avatar>{user.name[0]}</Avatar>
</Dropdown>
</div> </div>
<nav>{children}</nav>
{user && (
<div className="header-right">
<p>Welcome, {user?.username}!!</p>
{/* <Dropdown
overlay={
<Menu style={{ display: 'flex', flexDirection: 'column' }}>
<Button onClick={() => history.push('/login')}>Login</Button>
<Button onClick={handleLogout}>Logout</Button>
<Button onClick={() => history.push('/forgot-password')}>
Forgot Password
</Button>
<Button onClick={() => history.push('/profile')}>
Profile
</Button>
</Menu>
}
>
<Avatar>{user.username[0]}</Avatar>
</Dropdown> */}
</div>
)}
</header> </header>
) )
} }

View File

@ -2,10 +2,23 @@
header { header {
display: flex; display: flex;
padding: 0.5rem;
flex-direction: row; flex-direction: row;
width: 100%; padding: 0.5rem;
background: $color-dark; background: $color-dark;
justify-content: space-between;
align-items: center;
a {
text-decoration: none;
}
nav {
a {
margin: auto 0.5rem;
&:hover {
text-decoration: underline;
}
}
}
h3 { h3 {
color: $color-light; color: $color-light;

View File

@ -0,0 +1,9 @@
import { ModalProps } from 'antd'
import { ReactNode } from 'react'
import './style.scss'
type Props = ModalProps & { children: ReactNode }
export const Modal = ({ children, ...props }: Props) => {
return <div className="dank-modal">{children}</div>
}

View File

@ -0,0 +1,16 @@
@import '../../scss/variables.scss';
.dank-modal {
display: flex;
border: 1px solid $color-grey;
h3 {
text-align: center;
color: $color-light;
}
.cancel-button {
cursor: pointer;
text-align: center;
}
}

View File

@ -2,7 +2,7 @@ import { FundBar } from '../../widgets/FundBar'
import { useStacks } from '../../hooks/getMany/useStacks' import { useStacks } from '../../hooks/getMany/useStacks'
export const Dashboard = () => { export const Dashboard = () => {
const stacks = useStacks('42') const stacks = useStacks()
if (!stacks.data) return <div>loading...</div> if (!stacks.data) return <div>loading...</div>

View File

@ -1,62 +0,0 @@
import { InputNumber } from 'antd'
import { useState } from 'react'
import { Button } from '../../elements/Button'
import '../../scss/transaction-modal.scss'
type Props = {
stackId: string
}
export const TransactionForm = ({ stackId }: Props) => {
const [amount, setAmount] = useState(0)
const [stack, setStack] = useState(stackId)
const [details, setDetails] = useState('')
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!amount) return
const body = {
stack,
details,
amount: amount,
created_at: new Date().toISOString(),
}
console.log('creating tx', body)
}
return (
<div className="transaction-modal">
<form onSubmit={handleSubmit} className="form">
<div className="cancel-button">X</div>
<h3>Expense for {stack} account</h3>
<label>
Amount:
<InputNumber
autoFocus
value={amount}
onChange={(v) => setAmount(v)}
/>
</label>
<label>
Details:
<input
value={details}
onChange={(e) => setDetails(e.currentTarget.value)}
></input>
</label>
<label>
<select value={stack} onChange={(e) => setStack(e.target.value)}>
<option value={-1} disabled>
Select Account
</option>
</select>
<Button htmlType="button">cancel</Button>
<Button htmlType="submit">Submit</Button>
</label>
</form>
</div>
)
}

View File

@ -1,50 +0,0 @@
.transaction-modal {
color: #fff;
z-index: 100;
position: absolute;
width: 95vw;
height: 95vh;
border-radius: 1ch;
border: 1ch solid #000;
background: #000c;
display: flex;
pointer-events: visible;
h3 {
margin: 0;
text-align: center;
color: #fff;
text-shadow: 2px 2px #000, -1px -1px #444;
}
.cancel-button {
cursor: pointer;
background: #000a;
border: 1px solid #ccc;
width: 4ch;
padding: 1ch 1ch;
border-radius: 1.5ch;
text-align: center;
position: relative;
top: -9ch;
left: -2ch;
}
.form {
pointer-events: all;
box-sizing: content-box;
padding: 3ch 2ch;
background: #000a;
border: 1px solid #ccc;
border-radius: 1ch;
width: 50%;
margin: auto;
label {
display: flex;
margin: 1ch;
flex-direction: row;
justify-content: space-between;
}
}
}

View File

@ -1,18 +1,20 @@
import { Input, message } from 'antd' import { Input, message } from 'antd'
import { useForm } from 'antd/lib/form/Form' import { useForm } from 'antd/lib/form/Form'
import FormItem from 'antd/lib/form/FormItem' import FormItem from 'antd/lib/form/FormItem'
import axios from 'axios'
import { useAppContext } from '../../contexts/AppContext' import { useAppContext } from '../../contexts/AppContext'
import { Button } from '../../elements/Button' import { Button } from '../../elements/Button'
import { Form } from '../../elements/Form' import { Form } from '../../elements/Form'
import { User } from '../../types'
type NewUserForm = Omit<User, 'id'> & { type NewUserForm = {
username: string
email: string
password1: string password1: string
password2: string password2: string
} }
export const NewUser = () => { export const NewUser = () => {
const api = useAppContext() const { baseURL } = useAppContext()
const [form] = useForm<NewUserForm>() const [form] = useForm<NewUserForm>()
const handleFinish = (user: NewUserForm) => { const handleFinish = (user: NewUserForm) => {
@ -21,7 +23,7 @@ export const NewUser = () => {
return return
} }
api.post(`/dj-rest-auth/registration`, user) axios.post(`${baseURL}/dj-rest-auth/registration/`, user)
} }
return ( return (
@ -29,11 +31,11 @@ export const NewUser = () => {
<FormItem label="username" name="username"> <FormItem label="username" name="username">
<Input></Input> <Input></Input>
</FormItem> </FormItem>
<FormItem label="email" name="email"> {/* <FormItem label="email" name="email">
<Input type="email"></Input> <Input type="email"></Input>
</FormItem> </FormItem> */}
<FormItem label="password" name="password1"> <FormItem label="password" name="password1">
<Input minLength={8}></Input> <Input minLength={6}></Input>
</FormItem> </FormItem>
<FormItem label="confirm" name="password2"> <FormItem label="confirm" name="password2">
<Input></Input> <Input></Input>

View File

@ -0,0 +1,55 @@
import { useForm } from 'antd/lib/form/Form'
import { useState } from 'react'
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 { useStacks } from '../../hooks/getMany/useStacks'
import { Modal } from '../../layout/Modal'
import { Transaction } from '../../types'
type Props = {
stackId: string
}
export const TransactionForm = ({ stackId }: Props) => {
const [form] = useForm<Transaction>()
const [visible, setVisible] = useState(true)
const stacks = useStacks()
const handleFinish = (tx: Transaction) => {
console.log('creating tx ', tx)
}
const handleClose = () => {
setVisible(false)
}
return (
<Modal onCancel={handleClose} visible={visible}>
<Form onFinish={handleFinish} form={form} className="form">
<h3>Expense for {form.getFieldValue('stack')} account</h3>
<FormItem label="Amount" name="amount">
<InputNumber autoFocus />
</FormItem>
<FormItem label="Details" name="details">
<Input />
</FormItem>
<FormItem name="Stack" label="Stack">
<select>
{stacks.data?.map((stack) => (
<option value={stack.id}>{stack.name}</option>
))}
<option value={-1} disabled>
Select Account
</option>
</select>
</FormItem>
<Button htmlType="submit">Submit</Button>
</Form>
</Modal>
)
}

View File

@ -1,8 +1,16 @@
export type ApiResponse<T> = {
count: number
next: number | null
previous: number | null
results: T[]
}
export type User = { export type User = {
id: string id: number
name: string username: string
email: string email: string
// password: string first_name: string
last_name: string
} }
export type Account = { export type Account = {