From 8c40f302865b31bc4415790b14a24e3365eb0bd9 Mon Sep 17 00:00:00 2001 From: Elijah Lucian Date: Wed, 14 Apr 2021 20:04:37 -0600 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=B7=E2=80=8D=E2=99=80=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 5 + frontend/package.json | 2 + frontend/src/App.tsx | 2 +- frontend/src/api/index.ts | 32 +++- frontend/src/app/CoreLayout.tsx | 61 +++--- frontend/src/app/components/AccountForm.tsx | 92 +++++++++ frontend/src/app/components/AccountSelect.tsx | 37 ++++ frontend/src/app/components/FundBar.tsx | 40 ++++ frontend/src/app/components/Login.tsx | 71 +++++++ .../src/app/components/TransactionList.tsx | 18 ++ .../src/app/components/TransactionModal.tsx | 65 +++++++ frontend/src/app/components/UserForm.tsx | 90 +++++++++ frontend/src/app/layout/AppHeader.tsx | 4 +- frontend/src/app/pages/Dashboard.tsx | 20 +- frontend/src/contexts/UserContext.tsx | 43 +++-- frontend/src/scss/account-select.scss | 6 + frontend/src/scss/app.scss | 177 ++++++++++++++---- frontend/src/scss/app.scss.old | 50 +++++ frontend/src/scss/form.scss | 27 +++ frontend/src/scss/fund-bar.scss | 33 ++++ frontend/src/scss/status.scss | 25 +++ frontend/src/scss/transaction-list.scss | 22 +++ frontend/src/scss/transaction-modal.scss | 50 +++++ frontend/src/types/index.ts | 9 +- 24 files changed, 891 insertions(+), 90 deletions(-) create mode 100644 frontend/src/app/components/AccountForm.tsx create mode 100644 frontend/src/app/components/AccountSelect.tsx create mode 100644 frontend/src/app/components/FundBar.tsx create mode 100644 frontend/src/app/components/Login.tsx create mode 100644 frontend/src/app/components/TransactionList.tsx create mode 100644 frontend/src/app/components/TransactionModal.tsx create mode 100644 frontend/src/app/components/UserForm.tsx create mode 100644 frontend/src/scss/account-select.scss create mode 100644 frontend/src/scss/app.scss.old create mode 100644 frontend/src/scss/form.scss create mode 100644 frontend/src/scss/fund-bar.scss create mode 100644 frontend/src/scss/status.scss create mode 100644 frontend/src/scss/transaction-list.scss create mode 100644 frontend/src/scss/transaction-modal.scss diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7664943..e23078e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1245,6 +1245,11 @@ "resolved": "https://registry.npmjs.org/@dank-inc/data-buddy/-/data-buddy-0.1.4.tgz", "integrity": "sha512-+t/Ctm4Ik2Lh1H9pt8DIDLHCPnRNz38ZcNXu9U+VFZOrLut7wxRoKqdt6IFz6MGwVJtuveJe2dGPCme3N3hryg==" }, + "@dank-inc/use-get": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@dank-inc/use-get/-/use-get-0.3.1.tgz", + "integrity": "sha512-O7QREvSarFsARw+DKRip0bTuGRMjlKqQ31FdSq+8tgsBiUDXJQ1OEdumT18mK9DRG4VmVqrvQrxTKcH10cIwxw==" + }, "@eslint/eslintrc": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index a7ba01b..e80425d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@dank-inc/data-buddy": "^0.1.4", + "@dank-inc/use-get": "^0.3.1", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", @@ -25,6 +26,7 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", + "lint": "tsc", "eject": "react-scripts eject" }, "eslintConfig": { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 43203e2..c1a9ff5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,7 +7,7 @@ import { Api } from './api' import './scss/app.scss' const App = () => { - const api = new Api({ mock: true, baseURL: '/api' }) + const api = new Api({ baseURL: '/api' }) return ( diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 8f0e56a..ec1745a 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -1,5 +1,5 @@ import Axios, { AxiosInstance } from 'axios' -import { Stack, Transaction, User, uuid } from '../types' +import { Account, Password, Stack, Transaction, User, uuid } from '../types' import { JWT, setJWT, wipeJWT } from '../utils/jwt' import { DataBuddy } from '@dank-inc/data-buddy' import { users } from './data/users' @@ -53,21 +53,39 @@ export class Api { return data } - getAccounts = async () => { - this.axios.get('accounts') + updateUser = async (id: uuid, body: Partial) => { + if (this.mock) return this.users.update(id, body) + const { data } = await this.axios.patch(`users/${id}`, body) + return data } + + createUser = async (body: Omit & Password) => { + const { data } = await this.axios.post(`users`, body) + return data + } + + getAccounts = async () => { + const { data } = await this.axios.get('accounts') + return data + } + getAccount = async (id: uuid) => { - this.axios.get(`accounts/${id}`) + const data = await this.axios.get(`accounts/${id}`) + return data } updateAccount = async (id: uuid, body: Partial) => { const { data } = await this.axios.patch(`accounts/${id}`, body) return data } - createAccount = async () => {} + createAccount = async (body: Omit) => { + const { data } = await this.axios.post('accounts', body) + return data + } deleteAccount = async () => {} - getStacks = async () => { - this.axios.get('stacks') + getStacks = async (): Promise => { + const { data } = await this.axios.get('stacks') + return data } updateStack = async (id: uuid, body: Partial) => { const { data } = await this.axios.patch(`stacks/${id}`, body) diff --git a/frontend/src/app/CoreLayout.tsx b/frontend/src/app/CoreLayout.tsx index 9cce2a2..1af9e07 100644 --- a/frontend/src/app/CoreLayout.tsx +++ b/frontend/src/app/CoreLayout.tsx @@ -1,37 +1,42 @@ -import { Layout } from 'antd' import { useUserContext } from '../contexts/UserContext' -import { Login } from './pages/Login' -import { Route, Switch } from 'react-router' +import { Redirect, Route, Switch } from 'react-router' +import { Link } from 'react-router-dom' import { Dashboard } from './pages/Dashboard' -import { AppHeader } from './layout/AppHeader' -import { Profile } from './pages/Profile' -import { NewUser } from './forms/NewUser' -import { ForgotPassword } from './pages/ForgotPassword' +import { UserForm } from './components/UserForm' +import { TransactionList } from './components/TransactionList' +import { AccountForm } from './components/AccountForm' export const CoreLayout = () => { - const { user } = useUserContext() + const { user, accounts, selectedAccount } = useUserContext() - if (!user) - return ( - - - - - ) + if (!accounts?.length) - // header, sidebar, avatar? return ( - - - - - - - - - - - - +
+ + + + + + + + + + + {/* {showingModal && } */} + +
+

User: {user?.name}

+

|

+

Budget: {selectedAccount?.name}

+

|

+
+
) } diff --git a/frontend/src/app/components/AccountForm.tsx b/frontend/src/app/components/AccountForm.tsx new file mode 100644 index 0000000..760ec07 --- /dev/null +++ b/frontend/src/app/components/AccountForm.tsx @@ -0,0 +1,92 @@ +import React, { useState } from 'react' +import { Account } from '../../types' + +import '../../scss/form.scss' +import { useAppContext } from '../../contexts/AppContext' +import { message } from 'antd' +import { usePromise } from '@dank-inc/use-get' + +type Props = { + account?: Account +} + +export const AccountForm = ({ account }: Props) => { + const { api } = useAppContext() + + const stacks = usePromise(api.getStacks()) + + const [name, setName] = useState(account?.name || '') + const [details, setDetails] = useState(account?.details || '') + const [income, setIncome] = useState(account?.income || 0) + const [expenses, setExpenses] = useState(account?.expenses || 0) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (!name || !income || !expenses) message.error(`fill out all the fields!`) + + const body = { + name, + income, + expenses, + details, + } + + account?.id + ? await api.updateAccount(account.id, body) + : await api.createAccount(body) + + message.success('Yaaa') + } + + return ( + <> +

{account?.id ? 'Budget Details' : 'Create Budget'}

+
+

General

+ + +

Income & Expenses

+ + +

Budgets

+ {stacks.data?.map((stack) => ( +
+ + + api.updateStack(stack.id, { + amount: parseInt(e.target.value), + }) + } + > +
+ ))} + +
+ + ) +} diff --git a/frontend/src/app/components/AccountSelect.tsx b/frontend/src/app/components/AccountSelect.tsx new file mode 100644 index 0000000..417f944 --- /dev/null +++ b/frontend/src/app/components/AccountSelect.tsx @@ -0,0 +1,37 @@ +import { Link } from 'react-router-dom' +import { message } from 'antd' +import { useUserContext } from '../../contexts/UserContext' + +import '../../scss/account-select.scss' + +type Props = { + selectProfile: (id: string) => void +} + +export const AccountSelect = ({ selectProfile }: Props) => { + const { accounts } = useUserContext() + + const handleSelect = (id: string) => { + selectProfile(id) + message.success(`Selected Account: ${id}`) + } + + return ( + <> +

Select Account

+
+ {accounts?.length + ? accounts.map((account) => ( + + )) + : ''} + Create New Budget! +
+ + ) +} diff --git a/frontend/src/app/components/FundBar.tsx b/frontend/src/app/components/FundBar.tsx new file mode 100644 index 0000000..2d8b94a --- /dev/null +++ b/frontend/src/app/components/FundBar.tsx @@ -0,0 +1,40 @@ +import { Stack } from '../../types' +import '../../scss/fund-bar.scss' + +type Props = { + col: number + stack: Stack +} + +export const FundBar = ({ stack, col }: Props) => { + const amount = 0 + + const max = stack.amount + const current = max - amount + const percent = Math.max(current / max, 0) + const hue = percent * 120 + + return ( + <> +
console.log(`adding transaction to => ${stack.name}`)} + >
+
+

{stack.name}

+
+
+ ${Math.floor(current)} / ${max} +
+ + ) +} diff --git a/frontend/src/app/components/Login.tsx b/frontend/src/app/components/Login.tsx new file mode 100644 index 0000000..89428d9 --- /dev/null +++ b/frontend/src/app/components/Login.tsx @@ -0,0 +1,71 @@ +import React, { useState, useEffect } from 'react' +import Axios from 'axios' +import { Link } from 'react-router-dom' + +type Props = { + handleLogin: (v: string) => void +} + +export const Login = ({ handleLogin }: Props) => { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [valid, setValid] = useState(false) + const [error, setError] = useState('') + + useEffect(() => { + if (window.localStorage.userId) handleLogin(window.localStorage.userId) + }, [handleLogin]) + + useEffect(() => { + email && password ? setValid(true) : setValid(false) + }, [email, password]) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + 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 ( +
+
+ + + + {error &&

{error}

} + No Account? Sign Up! +
+
+ ) +} diff --git a/frontend/src/app/components/TransactionList.tsx b/frontend/src/app/components/TransactionList.tsx new file mode 100644 index 0000000..5ec179b --- /dev/null +++ b/frontend/src/app/components/TransactionList.tsx @@ -0,0 +1,18 @@ +import '../../scss/transaction-list.scss' + +export const TransactionList = () => { + return ( + <> +

Transactions

+
+ {[].map((account, i) => { + return ( +
+

{account}

+
+ ) + })} +
+ + ) +} diff --git a/frontend/src/app/components/TransactionModal.tsx b/frontend/src/app/components/TransactionModal.tsx new file mode 100644 index 0000000..75da348 --- /dev/null +++ b/frontend/src/app/components/TransactionModal.tsx @@ -0,0 +1,65 @@ +import { useState } from 'react' + +import '../../scss/transaction-modal.scss' +import { useAppContext } from '../../contexts/AppContext' +import { uuid } from '../../types' + +type Props = { + stackId: uuid +} + +export const TransactionModal = ({ stackId }: Props) => { + const { api } = useAppContext() + + const [amount, setAmount] = useState('') + const [stack, setStack] = useState(stackId) + const [details, setDetails] = useState('') + const [error, setError] = useState(null) + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + + if (parseFloat(amount)) { + api.createTransaction({ + stack, + details, + amount: parseFloat(amount), + created_at: new Date().toISOString(), + }) + } + } + + return ( +
+
+
X
+

Expense for {stack} account

+ + + +
+ {error &&
{error}
} +
+ ) +} diff --git a/frontend/src/app/components/UserForm.tsx b/frontend/src/app/components/UserForm.tsx new file mode 100644 index 0000000..f8fa97e --- /dev/null +++ b/frontend/src/app/components/UserForm.tsx @@ -0,0 +1,90 @@ +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() + + 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 & Password = { + name, + email, + password, + passwordConfirm, + } + + try { + user?.id + ? await api.updateUser(user.id, body) + : await api.createUser(body) + + if (!user?.id) history.push('/login') + } catch (err) { + message.error('Something went wrong') + } + } + + return ( + <> +

{user?.id ? 'Edit' : 'Create'} Profile

+
+ + + + + +
+ + ) +} diff --git a/frontend/src/app/layout/AppHeader.tsx b/frontend/src/app/layout/AppHeader.tsx index 8eb7b50..6ffb1ec 100644 --- a/frontend/src/app/layout/AppHeader.tsx +++ b/frontend/src/app/layout/AppHeader.tsx @@ -38,7 +38,7 @@ export const AppHeader = ({ user }: Props) => {
-

Welcome, {user.username}!!

+

Welcome, {user.name}!!

@@ -51,7 +51,7 @@ export const AppHeader = ({ user }: Props) => { } > - {user.username[0]} + {user.name[0]}
diff --git a/frontend/src/app/pages/Dashboard.tsx b/frontend/src/app/pages/Dashboard.tsx index 0aa2115..a067548 100644 --- a/frontend/src/app/pages/Dashboard.tsx +++ b/frontend/src/app/pages/Dashboard.tsx @@ -1,3 +1,21 @@ +import { FundBar } from '../components/FundBar' +import { usePromise } from '@dank-inc/use-get' +import { useAppContext } from '../../contexts/AppContext' + export const Dashboard = () => { - return

a dashboard

+ const { api } = useAppContext() + const stacks = usePromise(api.getStacks()) + + if (stacks.loading || !stacks.data) return
loading...
+ + return ( + <> +

Remaining Balances

+
+ {stacks.data.map((stack, i) => ( + + ))} +
+ + ) } diff --git a/frontend/src/contexts/UserContext.tsx b/frontend/src/contexts/UserContext.tsx index 06589bf..7548fbb 100644 --- a/frontend/src/contexts/UserContext.tsx +++ b/frontend/src/contexts/UserContext.tsx @@ -1,16 +1,7 @@ -import React, { - createContext, - useContext, - useState, - useEffect, - Dispatch, - SetStateAction, -} from 'react' +import React, { createContext, useContext, useState } from 'react' import { message } from 'antd' - import { useHistory } from 'react-router' - -import { User } from '../types' +import { Account, User, uuid } from '../types' import { useAppContext } from './AppContext' import { logOut } from '../api' @@ -20,9 +11,11 @@ type Props = { type Context = { user: User | null - setUser: Dispatch> + accounts: Account[] | null + selectedAccount: Account | null handleLogin: (username: string, password: string) => void handleLogout: () => void + handleSelectAccount: (id: uuid) => void } const UserContext = createContext(null) @@ -32,15 +25,19 @@ export const UserContextProvider = ({ children }: Props) => { const history = useHistory() const [user, setUser] = useState(null) + const [accounts, setAccounts] = useState(null) + const [selectedAccount, setSelectedAccount] = useState(null) const handleLogin = async (username: string, password: string) => { try { const { id } = await api.login(username, password) if (!id) throw new Error('Problem logging in!') - const user = await api.getUser(id) + setUser(await api.getUser(id)) - setUser(user) - message.success(`logged in as ${user?.username}`, 0.5) + const accounts = await api.getAccounts() + setAccounts(accounts) + + message.success(`logged in as ${user?.name}`, 0.5) } catch (err) { message.error('Login Failed!') } @@ -53,8 +50,22 @@ export const UserContextProvider = ({ children }: Props) => { history.push('/') } + const handleSelectAccount = (id: string) => { + const account = accounts?.find((account) => account.id === id) + if (account) setSelectedAccount(account) + } + return ( - + {children} ) diff --git a/frontend/src/scss/account-select.scss b/frontend/src/scss/account-select.scss new file mode 100644 index 0000000..f3f040a --- /dev/null +++ b/frontend/src/scss/account-select.scss @@ -0,0 +1,6 @@ +.account-select { + display: flex; + flex-direction: row; + justify-content: space-around; + width: 100%; +} diff --git a/frontend/src/scss/app.scss b/frontend/src/scss/app.scss index 19f70ae..ed8f10b 100644 --- a/frontend/src/scss/app.scss +++ b/frontend/src/scss/app.scss @@ -1,50 +1,161 @@ -.ant-layout-header.app-header { - display: flex; - flex-direction: row; - justify-content: space-between; - vertical-align: middle; - padding: 0 1rem; +h1, +h2, +h3, +h4, +h5 { + color: #222; + text-shadow: -1px -1px #444; + font-weight: 900; +} - a { - font-size: 1.2rem; - font-weight: bold; +.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 { + display: flex; + flex-direction: column; + align-items: center; + // justify-content: space-around; + font-size: calc(10px + 2vmin); + color: white; + + h1 { + margin-top: 2.5vmin; + margin-bottom: 1vmin; + } +} + +button { + cursor: pointer; + padding: 2ch; + background: #aaa; + border: 0; + border-radius: 1ch; + + &:hover { + background: #fff; } - div { - display: flex; - vertical-align: middle; - height: 100%; + &:active { + background: #222; + } + transition: all 0.1s ease-out; +} - * { - margin: auto 0.5rem; +a { + color: #61dafb; +} + +.error { + font-size: 1ch; + color: red; + padding: 0; + margin: 0; +} + +.funds, +.transactions { + display: grid; + grid-template-columns: repeat(3, 1fr); + // height: 50vh; +} + +nav { + padding-top: 2ch; + padding-bottom: 2ch; + width: 100%; + display: flex; + justify-content: space-around; + background: #111a; +} + +.totals { + z-index: 9; + .above { + margin-top: 5ch; + } + .bottom { + margin-bottom: 5ch; + } +} + +.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 { + color: #111a; +} + +.dank-form { + display: flex; + flex-direction: column; + background: #1115; + border: 1ch #1117 solid; + padding: 3ch; + border-radius: 1ch; + label { + margin: 0.5ch; + display: flex; + flex-direction: row; + justify-content: space-between; + + input { + margin-left: 2ch; } } - h3.ant-typography { - margin: auto 0 !important; + h3 { + color: #666; } - div.ant-typography { - font-size: 1.6rem; + button { + margin: 4ch 1ch; } } -.ant-layout.layout { - min-height: 100vh; - // background-color: #444; - // color: white; -} +footer { + position: fixed; + left: 0; + bottom: 0; + width: 100%; + background: #111a; + padding: 0 2ch; + font-size: 10pt; -.ant-avatar { display: flex; + flex-direction: row; - * { - margin: auto; - padding: 0; + p { + margin: 1ch; } } - -.ant-layout-content { - max-width: 900px; - margin: auto; -} diff --git a/frontend/src/scss/app.scss.old b/frontend/src/scss/app.scss.old new file mode 100644 index 0000000..19f70ae --- /dev/null +++ b/frontend/src/scss/app.scss.old @@ -0,0 +1,50 @@ +.ant-layout-header.app-header { + display: flex; + flex-direction: row; + justify-content: space-between; + vertical-align: middle; + padding: 0 1rem; + + a { + font-size: 1.2rem; + font-weight: bold; + } + + div { + display: flex; + vertical-align: middle; + height: 100%; + + * { + margin: auto 0.5rem; + } + } + + h3.ant-typography { + margin: auto 0 !important; + } + + div.ant-typography { + font-size: 1.6rem; + } +} + +.ant-layout.layout { + min-height: 100vh; + // background-color: #444; + // color: white; +} + +.ant-avatar { + display: flex; + + * { + margin: auto; + padding: 0; + } +} + +.ant-layout-content { + max-width: 900px; + margin: auto; +} diff --git a/frontend/src/scss/form.scss b/frontend/src/scss/form.scss new file mode 100644 index 0000000..f83a248 --- /dev/null +++ b/frontend/src/scss/form.scss @@ -0,0 +1,27 @@ +.dank-form { + display: flex; + flex-direction: column; + background: #1115; + border: 1ch #1117 solid; + padding: 3ch; + border-radius: 1ch; + color: #ccc; + label { + margin: 0.5ch; + display: flex; + flex-direction: row; + justify-content: space-between; + + input { + margin-left: 2ch; + } + } + + h3 { + color: #666; + } + + button { + margin: 4ch 1ch; + } +} diff --git a/frontend/src/scss/fund-bar.scss b/frontend/src/scss/fund-bar.scss new file mode 100644 index 0000000..86ccba0 --- /dev/null +++ b/frontend/src/scss/fund-bar.scss @@ -0,0 +1,33 @@ +.fundbar { + cursor: pointer; + border-radius: 1ch; + text-align: center; + margin: auto 2ch 0 2ch; + + &.totals { + pointer-events: none; + margin-top: 2ch; + } + + h3 { + margin: auto auto 0 auto; + } + + &.front { + pointer-events: none; + transition: all 0.2s ease-out; + border: 2px solid #222a; + display: flex; + flex-direction: column; + padding: 2ch; + color: #333; + text-shadow: 2px 2px #2223, -1px -1px #fffa; + } + + &.back { + background: #222; + + border: 4px solid #3334; + height: 50vh; + } +} diff --git a/frontend/src/scss/status.scss b/frontend/src/scss/status.scss new file mode 100644 index 0000000..01ccc9b --- /dev/null +++ b/frontend/src/scss/status.scss @@ -0,0 +1,25 @@ +.status { + position: absolute; + left: 1ch; + bottom: 4ch; + padding: 1ch 2ch; + border-radius: 1ch; + + transition: all 0.2s ease-in-out; + + &.error { + color: #ffffff; + background: #b40303; + opacity: 1; + } + + &.good { + color: #ffffff; + background: #009b46; + opacity: 1; + } + + &.off { + opacity: 0; + } +} diff --git a/frontend/src/scss/transaction-list.scss b/frontend/src/scss/transaction-list.scss new file mode 100644 index 0000000..072ddad --- /dev/null +++ b/frontend/src/scss/transaction-list.scss @@ -0,0 +1,22 @@ +.transactions { + .column { + h3 { + text-align: center; + text-decoration: underline; + } + font-size: 14pt; + margin: 2ch; + background: #fff1; + padding: 0 2ch 1ch; + border-radius: 1ch; + } + + .transaction-item { + display: flex; + flex-direction: row; + justify-content: space-between; + .details { + padding-left: 1ch; + } + } +} diff --git a/frontend/src/scss/transaction-modal.scss b/frontend/src/scss/transaction-modal.scss new file mode 100644 index 0000000..d6f87e2 --- /dev/null +++ b/frontend/src/scss/transaction-modal.scss @@ -0,0 +1,50 @@ +.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; + } + } +} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index af26177..26b1e04 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -12,8 +12,8 @@ export type Account = { users?: uuid[] name: string details: string - income: string - expenses: string + income: number + expenses: number } export type Stack = { @@ -32,3 +32,8 @@ export type Transaction = { amount: number // '30.44' created_at: string // '2021-04-15T00:02:45.096071Z' } + +export type Password = { + password: string + passwordConfirm: string +}