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 { Route, Switch } from 'react-router'
import { Link } from 'react-router-dom'
import { Link, useHistory } from 'react-router-dom'
import { Dashboard } from './pages/Dashboard'
import { NewUser } from './pages/NewUser'
import { TransactionList } from './pages/TransactionList'
@ -8,29 +8,32 @@ import { AccountForm } from './forms/AccountForm'
import { Login } from './pages/Login'
import { AppHeader } from './layout/AppHeader'
import { AppFooter } from './layout/AppFooter'
import { TransactionForm } from './pages/TransactionForm'
import { Button } from 'antd'
export const CoreLayout = () => {
const { user } = useUserContext()
const history = useHistory()
return (
<div className="app">
<AppHeader>
<Link to="/">Home</Link>
<Link to="/select">Select Budget</Link>
<Link to="/accounts">Budget Details</Link>
<Link to="/accounts">Accounts</Link>
<Button onClick={() => history.push('?new=account')}>New</Button>
<Link to="/transactions">Transactions</Link>
<Link to="/new">New User</Link>
<Button onClick={() => history.push('?new=transaction')}>New</Button>
</AppHeader>
<main>
{!user ? (
<Switch>
<Route path="/sign-up" component={NewUser} />
<Route path="/" component={Login} />
<Route path="/login" component={Login} />
<div>loading...</div>
</Switch>
) : (
<Switch>
<Route path="/users/new" component={NewUser} />
<Route path="/transactions/new" component={TransactionForm} />
<Route path="/transactions" component={TransactionList} />
<Route path="/accounts/new" 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>
create: <T>(path: string, body: Partial<T>) => Promise<T>
destroy: (path: string) => Promise<string>
baseURL?: string
}
const AppContext = createContext<AppContextInterface | null>(null)
@ -52,7 +53,7 @@ export const AppContextProvider = ({ children, baseURL }: Props) => {
}
return (
<AppContext.Provider value={{ get, patch, post, create, destroy }}>
<AppContext.Provider value={{ get, patch, post, create, destroy, baseURL }}>
{children}
</AppContext.Provider>
)

View File

@ -3,7 +3,8 @@ import { message } from 'antd'
import { useHistory } from 'react-router'
import { User } from '../types'
import { useAppContext } from './AppContext'
import { logOut } from '../api'
import { useEffect } from 'react'
import { useRef } from 'react'
type Props = {
children: React.ReactNode
@ -18,14 +19,36 @@ type Context = {
const UserContext = createContext<Context | null>(null)
export const UserContextProvider = ({ children }: Props) => {
const api = useAppContext()
const history = useHistory()
const api = useRef(useAppContext())
const history = useRef(useHistory())
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) => {
try {
const { key } = await api.post<any, { key: string }>(
const { key } = await api.current.post<any, { key: string }>(
'/dj-rest-auth/login/',
{
username,
@ -34,24 +57,18 @@ export const UserContextProvider = ({ children }: Props) => {
)
if (!key) throw new Error('Problem logging in!')
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)
if (!user) message.error(`Couldn't find user`)
setUser(user)
message.success(`logged in as ${user?.name}`, 0.5)
} catch (err) {
message.error('Login Failed!')
}
}
const handleLogout = async () => {
await logOut()
await api.current.post('/dj-rest-auth/logout/', {})
setUser(null)
message.success('logged out!', 0.5)
history.push('/')
history.current.push('/')
}
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) => {
const stacks = useStacks('')
const stacks = useStacks()
const [name, setName] = useState<string>(account?.name || '')
const [details, setDetails] = useState<string>(account?.details || '')

View File

@ -1,88 +1,39 @@
import { useState, useEffect } from 'react'
import { Password, User } from '../types'
import { User } from '../types'
import { useUserContext } from '../contexts/UserContext'
import { message } from 'antd'
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 = () => {
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 [email, setEmail] = useState(user?.email)
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')
}
const handleFinish = (values: User) => {
console.log(values)
}
return (
<>
<h1>{user?.id ? 'Edit' : 'Create'} Profile</h1>
<form className="dank-form" onSubmit={handleSubmit}>
<label>
Name: <input value={name} onChange={(e) => setName(e.target.value)} />
</label>
<label>
Email:
<input
value={email}
type="email"
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<label>
Password:
<input
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>
<Form form={form} onFinish={handleFinish} initialValues={user}>
<h1>User Profile</h1>
<FormItem label="Username" name="username">
<Input />
</FormItem>
<FormItem label="First Name" name="first_name">
<Input />
</FormItem>
<FormItem label="Last Name" name="last_name">
<Input />
</FormItem>
<FormItem label="E-Mail" name="email">
<Input type="email" />
</FormItem>
<Button htmlType="submit">Save</Button>
</Form>
</>
)
}

View File

@ -1,4 +1,4 @@
import { Account } from '../../types'
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 { 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'
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 data = await appContext.current.get<T>(path)
if (!data) return
setData(data)
// @ts-ignore
setData(data.results)
}, [path])
useEffect(() => {

View File

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

View File

@ -2,9 +2,9 @@ import { ReactNode } from 'react'
import { Avatar, Dropdown, Menu } from 'antd'
import { Link, useHistory } from 'react-router-dom'
import { useUserContext } from '../../contexts/UserContext'
import { Button } from '../../elements/Button'
import './style.scss'
import { Button } from '../../elements/Button'
type Props = {
children: ReactNode
@ -27,30 +27,31 @@ export const AppHeader = ({ children }: Props) => {
<header>
<div className="header-left">
<Link to="/">
<h3>MVP Django React! 🤠</h3>
<h3>CashStacks! 💵</h3>
</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>
<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>
)
}

View File

@ -2,10 +2,23 @@
header {
display: flex;
padding: 0.5rem;
flex-direction: row;
width: 100%;
padding: 0.5rem;
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 {
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'
export const Dashboard = () => {
const stacks = useStacks('42')
const stacks = useStacks()
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 { useForm } from 'antd/lib/form/Form'
import FormItem from 'antd/lib/form/FormItem'
import axios from 'axios'
import { useAppContext } from '../../contexts/AppContext'
import { Button } from '../../elements/Button'
import { Form } from '../../elements/Form'
import { User } from '../../types'
type NewUserForm = Omit<User, 'id'> & {
type NewUserForm = {
username: string
email: string
password1: string
password2: string
}
export const NewUser = () => {
const api = useAppContext()
const { baseURL } = useAppContext()
const [form] = useForm<NewUserForm>()
const handleFinish = (user: NewUserForm) => {
@ -21,7 +23,7 @@ export const NewUser = () => {
return
}
api.post(`/dj-rest-auth/registration`, user)
axios.post(`${baseURL}/dj-rest-auth/registration/`, user)
}
return (
@ -29,11 +31,11 @@ export const NewUser = () => {
<FormItem label="username" name="username">
<Input></Input>
</FormItem>
<FormItem label="email" name="email">
{/* <FormItem label="email" name="email">
<Input type="email"></Input>
</FormItem>
</FormItem> */}
<FormItem label="password" name="password1">
<Input minLength={8}></Input>
<Input minLength={6}></Input>
</FormItem>
<FormItem label="confirm" name="password2">
<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 = {
id: string
name: string
id: number
username: string
email: string
// password: string
first_name: string
last_name: string
}
export type Account = {