🐱😒💋

This commit is contained in:
Elijah Lucian 2021-04-14 21:24:00 -06:00
parent 486955a861
commit c73bcf8a57
18 changed files with 7121 additions and 5539 deletions

10512
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@
"@types/react-dom": "^17.0.0", "@types/react-dom": "^17.0.0",
"antd": "^4.14.0", "antd": "^4.14.0",
"axios": "^0.21.1", "axios": "^0.21.1",
"node-sass": "^5.0.0", "node-sass": "^4.0.0",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",

View File

@ -7,7 +7,7 @@ import { Api } from './api'
import './scss/app.scss' import './scss/app.scss'
const App = () => { const App = () => {
const api = new Api({ baseURL: '/api' }) const api = new Api({ mock: true, baseURL: '/api' })
return ( return (
<BrowserRouter> <BrowserRouter>

View File

@ -0,0 +1,49 @@
import { DataBuddy } from '@dank-inc/data-buddy'
import { Account, Stack, Transaction, User } from '../../types'
export const users = new DataBuddy<User>([
{
id: 'mock-user',
name: 'TestUser42',
email: 'testuser@email.com',
},
])
export const accounts = new DataBuddy<Account>([
{
id: 'home',
name: 'Home Expenses',
details: 'ya',
users: ['42'],
income: 1000,
expenses: 500,
},
])
export const stacks = new DataBuddy<Stack>([
{
id: 'ccrap',
name: 'crap',
account: 'asdf',
amount: 200,
details: 'for all my crap!',
transactions: [],
},
{
id: 'shit',
name: 'shit',
account: 'home',
amount: 500,
details: 'for all my shit!',
transactions: [],
},
{
id: 'poo',
name: 'poo',
account: 'home',
amount: 800,
details: 'for all my poo!',
transactions: [],
},
])
export const transactions = new DataBuddy<Transaction>([])

View File

@ -1,14 +0,0 @@
import { DataBuddy } from '@dank-inc/data-buddy'
import { Account, Stack, Transaction, User } from '../../types'
export const users = new DataBuddy<User>([
{
id: '42',
name: 'TestUser42',
email: 'testuser@email.com',
},
])
export const accounts = new DataBuddy<Account>([])
export const stacks = new DataBuddy<Stack>([])
export const transactions = new DataBuddy<Transaction>([])

View File

@ -2,7 +2,7 @@ import Axios, { AxiosInstance } from 'axios'
import { Account, Password, Stack, Transaction, User, uuid } from '../types' import { Account, Password, Stack, Transaction, User, uuid } from '../types'
import { JWT, setJWT, wipeJWT } from '../utils/jwt' import { JWT, setJWT, wipeJWT } from '../utils/jwt'
import { DataBuddy } from '@dank-inc/data-buddy' import { DataBuddy } from '@dank-inc/data-buddy'
import { users } from './data/users' import { users, accounts, stacks, transactions } from './data'
export type ApiParams = { export type ApiParams = {
baseURL?: string baseURL?: string
@ -14,7 +14,7 @@ export interface Api {
users: DataBuddy<User> users: DataBuddy<User>
accounts: DataBuddy<Account> accounts: DataBuddy<Account>
stacks: DataBuddy<Stack> stacks: DataBuddy<Stack>
Transactions: DataBuddy<Transaction> transactions: DataBuddy<Transaction>
axios: AxiosInstance axios: AxiosInstance
} }
@ -22,19 +22,25 @@ export class Api {
constructor({ mock, baseURL }: ApiParams) { constructor({ mock, baseURL }: ApiParams) {
this.mock = mock this.mock = mock
this.users = users this.users = users
this.accounts = accounts
this.stacks = stacks
this.transactions = transactions
this.axios = Axios.create({ baseURL }) this.axios = Axios.create({ baseURL })
} }
login = async (username: string, password: string): Promise<JWT> => { login = async (name: string, password: string): Promise<JWT> => {
if (this.mock) if (this.mock) {
return { const jwt = {
id: 'mock-id', id: 'mock-user',
token: 'token-token-token', token: 'token-token-token',
exp: +new Date(), exp: +new Date(),
} }
setJWT(jwt)
return jwt
}
const { data } = await this.axios.post<JWT>(`/api/dj-rest-auth/login/`, { const { data } = await this.axios.post<JWT>(`/api/dj-rest-auth/login/`, {
username, name,
password, password,
}) })
@ -65,11 +71,13 @@ export class Api {
} }
getAccounts = async () => { getAccounts = async () => {
if (this.mock) return this.accounts.get()
const { data } = await this.axios.get<Account[]>('accounts') const { data } = await this.axios.get<Account[]>('accounts')
return data return data
} }
getAccount = async (id: uuid) => { getAccount = async (id: uuid) => {
if (this.mock) return this.accounts.getOne(id)
const data = await this.axios.get<Account>(`accounts/${id}`) const data = await this.axios.get<Account>(`accounts/${id}`)
return data return data
} }
@ -84,20 +92,25 @@ export class Api {
deleteAccount = async () => {} deleteAccount = async () => {}
getStacks = async (): Promise<Stack[]> => { getStacks = async (): Promise<Stack[]> => {
if (this.mock) return this.stacks.get()
const { data } = await this.axios.get('stacks') const { data } = await this.axios.get('stacks')
return data return data
} }
updateStack = async (id: uuid, body: Partial<Stack>) => { updateStack = async (id: uuid, body: Partial<Stack>) => {
const { data } = await this.axios.patch<Stack>(`stacks/${id}`, body) const { data } = await this.axios.patch<Stack>(`stacks/${id}`, body)
return data return data
} }
createStack = async () => {} createStack = async () => {}
deleteStack = async () => {} deleteStack = async () => {}
getTransactions = async () => { getTransactions = async () => {
if (this.mock) return this.transactions.get()
const { data } = await this.axios.get('transactions') const { data } = await this.axios.get('transactions')
return data return data
} }
updateTransaction = async (id: uuid, body: Partial<Transaction>) => { updateTransaction = async (id: uuid, body: Partial<Transaction>) => {
const { data } = await this.axios.patch<Transaction>( const { data } = await this.axios.patch<Transaction>(
`transactions/${id}`, `transactions/${id}`,
@ -105,6 +118,7 @@ export class Api {
) )
return data return data
} }
createTransaction = async (body: Omit<Transaction, 'id'>) => { createTransaction = async (body: Omit<Transaction, 'id'>) => {
const { data } = await this.axios.post<Transaction>('transactions', body) const { data } = await this.axios.post<Transaction>('transactions', body)
return data return data

View File

@ -1,18 +1,19 @@
import { useUserContext } from '../contexts/UserContext' import { useUserContext } from '../contexts/UserContext'
import { Redirect, Route, Switch } from 'react-router' import { Route, Switch } from 'react-router'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Dashboard } from './pages/Dashboard' import { Dashboard } from './pages/Dashboard'
import { UserForm } from './components/UserForm' import { UserForm } from './components/UserForm'
import { TransactionList } from './components/TransactionList' import { TransactionList } from './components/TransactionList'
import { AccountForm } from './components/AccountForm' import { AccountForm } from './components/AccountForm'
import { Login } from './components/Login'
export const CoreLayout = () => { export const CoreLayout = () => {
const { user, accounts, selectedAccount } = useUserContext() const { user, selectedAccount } = useUserContext()
if (!accounts?.length) <Redirect to="/account/new" /> if (!user) return <Login />
return ( return (
<div className="app" id="appElement"> <div className="app">
<nav> <nav>
<Link to="/">Home</Link> <Link to="/">Home</Link>
<Link to="/select">Select Budget</Link> <Link to="/select">Select Budget</Link>

View File

@ -72,7 +72,7 @@ export const AccountForm = ({ account }: Props) => {
</label> </label>
<h3>Budgets</h3> <h3>Budgets</h3>
{stacks.data?.map((stack) => ( {stacks.data?.map((stack) => (
<div className="form-item"> <div key={stack.details} className="form-item">
<label>{stack.name}</label> <label>{stack.name}</label>
<input <input
type="number" type="number"

View File

@ -1,71 +1,40 @@
import React, { useState, useEffect } from 'react' import { Button, Form, Input } from 'antd'
import Axios from 'axios' import FormItem from 'antd/lib/form/FormItem'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { User } from '../../types'
type Props = { import '../../scss/login.scss'
handleLogin: (v: string) => void import { useUserContext } from '../../contexts/UserContext'
}
export const Login = ({ handleLogin }: Props) => { type FormValues = Pick<User, 'name'> & { password: string }
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [valid, setValid] = useState(false)
const [error, setError] = useState('')
useEffect(() => { export const Login = () => {
if (window.localStorage.userId) handleLogin(window.localStorage.userId) const userContext = useUserContext()
}, [handleLogin])
useEffect(() => { const [form] = Form.useForm<FormValues>()
email && password ? setValid(true) : setValid(false)
}, [email, password])
const handleSubmit = async (e: React.FormEvent) => { const handleFinish = ({ name, password }: FormValues) => {
e.preventDefault() userContext.handleLogin(name, password)
if (process.env.NODE_ENV === 'development') {
handleLogin(email)
return
}
if (!email || !password) {
setError('please fill out both fields')
return
}
try {
const { data } = await Axios.post('/api/login', { email, password })
handleLogin(data.id)
window.localStorage.userId = data.id
} catch (err) {
setError(err.message)
}
} }
return ( return (
<div className="login"> <div className="login">
<form onSubmit={handleSubmit}> <h1>Log In</h1>
<label> <Form onFinish={handleFinish} form={form}>
Type Yo Email: <FormItem label="Username" name="username">
<input <Input />
autoFocus </FormItem>
onChange={e => setEmail(e.target.value)} <FormItem label="Password" name="password">
value={email} <Input type="password" />
></input> </FormItem>
</label>
<label> <div className="form-footer">
Type Yo Password:
<input
type="password"
onChange={e => setPassword(e.target.value)}
value={password}
></input>
</label>
<label>
<button disabled={!valid} type="submit">
Submit
</button>
</label>
{error && <p>{error}</p>}
<Link to="/sign-up">No Account? Sign Up!</Link> <Link to="/sign-up">No Account? Sign Up!</Link>
</form> <Button type="primary" htmlType="submit">
Log In!
</Button>
</div>
</Form>
</div> </div>
) )
} }

View File

@ -12,6 +12,8 @@ export const UserForm = () => {
const { api } = useAppContext() const { api } = useAppContext()
const { user } = useUserContext() const { user } = useUserContext()
console.log(user)
const [name, setName] = useState(user?.name) const [name, setName] = useState(user?.name)
const [email, setEmail] = useState(user?.email) const [email, setEmail] = useState(user?.email)
const [password, setPassword] = useState('') const [password, setPassword] = useState('')

View File

@ -13,7 +13,7 @@ export const Dashboard = () => {
<h1>Remaining Balances</h1> <h1>Remaining Balances</h1>
<div className="funds"> <div className="funds">
{stacks.data.map((stack, i) => ( {stacks.data.map((stack, i) => (
<FundBar stack={stack} col={i + 1} /> <FundBar key={stack.id} stack={stack} col={i + 1} />
))} ))}
</div> </div>
</> </>

View File

@ -9,7 +9,6 @@ type Credentials = {
export const Login = () => { export const Login = () => {
const { handleLogin } = useUserContext() const { handleLogin } = useUserContext()
const [form] = useForm<Credentials>() const [form] = useForm<Credentials>()
const handleSubmit = ({ username, password }: Credentials) => { const handleSubmit = ({ username, password }: Credentials) => {

View File

@ -1,3 +0,0 @@
export const Profile = () => {
return <p>Look, A user profile!</p>
}

View File

@ -13,7 +13,7 @@ type Context = {
user: User | null user: User | null
accounts: Account[] | null accounts: Account[] | null
selectedAccount: Account | null selectedAccount: Account | null
handleLogin: (username: string, password: string) => void handleLogin: (name: string, password: string) => void
handleLogout: () => void handleLogout: () => void
handleSelectAccount: (id: uuid) => void handleSelectAccount: (id: uuid) => void
} }
@ -28,11 +28,14 @@ export const UserContextProvider = ({ children }: Props) => {
const [accounts, setAccounts] = useState<Account[] | null>(null) const [accounts, setAccounts] = useState<Account[] | null>(null)
const [selectedAccount, setSelectedAccount] = useState<Account | null>(null) const [selectedAccount, setSelectedAccount] = useState<Account | null>(null)
const handleLogin = async (username: string, password: string) => { const handleLogin = async (name: string, password: string) => {
try { try {
const { id } = await api.login(username, password) const { id } = await api.login(name, password)
if (!id) throw new Error('Problem logging in!') if (!id) throw new Error('Problem logging in!')
setUser(await api.getUser(id)) const user = await api.getUser(id)
if (!user) message.error(`Couldn't find user`)
setUser(user)
const accounts = await api.getAccounts() const accounts = await api.getAccounts()
setAccounts(accounts) setAccounts(accounts)

View File

@ -4,6 +4,35 @@
box-sizing: border-box; box-sizing: border-box;
} }
#root {
display: flex;
flex-direction: column;
height: 100vh;
}
.col1 {
grid-column: 1/1;
grid-row: 1/1;
}
.col2 {
grid-column: 2/2;
grid-row: 1/1;
}
.col3 {
grid-column: 3/3;
grid-row: 1/1;
}
h1,
h2,
h3,
h4,
h5 {
color: #222;
text-shadow: -1px -1px #444;
font-weight: 900;
}
body { body {
margin: 0; margin: 0;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;

View File

@ -1,26 +1,3 @@
h1,
h2,
h3,
h4,
h5 {
color: #222;
text-shadow: -1px -1px #444;
font-weight: 900;
}
.col1 {
grid-column: 1/1;
grid-row: 1/1;
}
.col2 {
grid-column: 2/2;
grid-row: 1/1;
}
.col3 {
grid-column: 3/3;
grid-row: 1/1;
}
.app { .app {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -89,29 +66,6 @@ nav {
} }
} }
.login {
color: #ccc;
form {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 100%;
text-align: center;
label {
input {
margin-left: 2ch;
}
}
}
display: flex;
flex-direction: column;
width: 70vmin;
height: 70vmin;
margin: auto;
background: #222;
}
.todo { .todo {
color: #111a; color: #111a;
} }

View File

@ -0,0 +1,33 @@
.login {
display: flex;
box-shadow: 5px 5px #111, 2px 2px #111;
flex-direction: column;
margin: auto;
background: #222;
border-radius: 0.2rem;
padding: 2rem 2rem;
h1 {
margin: 0 auto;
}
form {
display: flex;
flex-direction: column;
justify-content: space-between;
text-align: center;
.ant-row {
margin: 1rem 2rem;
}
.form-footer {
margin-top: 1rem;
button {
width: 50%;
margin: auto 0 auto 1rem;
}
}
}
}

File diff suppressed because it is too large Load Diff