diff --git a/.gitignore b/.gitignore index 3f7bf98..d09d672 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules /build /public/build .env +/prisma/dev.db diff --git a/README.md b/README.md index ac67d06..2ba8ac8 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,23 @@ Personal Finance Manager is an application with limited financing functionality, used for tracking your income and expenses and managing your money efficiently. +## Steps To Run The Application + +- Create .env file inside project directory +- Add DATABASE_URL="file:./dev.db" to .env +- In the terminal run npx prisma db push to created database file in prisma/dev.db +- In the terminal run yarn seed to add mock data to the database +- Finally run yarn dev to run the project + +## Libraries And Packages That I Used + +- [Prisma](https://www.prisma.io/) as ORM +- [PropTypes](https://www.npmjs.com/package/prop-types) for validating props +- [React DatePicker](https://reactdatepicker.com/) for handling range date input +- [React Icons](https://react-icons.github.io/react-icons/) for icons +- [React Paginate](https://www.npmjs.com/package/react-paginate) for pagination +- [React Select](https://react-select.com/home) for multiple selecting + ## The Assignment The assignment you're about to tackle is a simple application, you'll be building from the current template built with **Create Remix App**. @@ -21,11 +38,11 @@ The application uses a simple data representation due to its limited functionali Following best practices is especially important because it helps us to ensure that the code is clean, maintainable, and efficient. Some examples of best practices in coding include: - - Creating a clear and intuitive navigation structure. - - Using clear and descriptive names for variables, functions, and other elements of code. - - Using descriptive and meaningful variable and function names. - - Structuring code in a logical and organized way. - - while using version controll, keep you're commits simple, small, and clean. +- Creating a clear and intuitive navigation structure. +- Using clear and descriptive names for variables, functions, and other elements of code. +- Using descriptive and meaningful variable and function names. +- Structuring code in a logical and organized way. +- while using version control, keep you're commits simple, small, and clean. ## Design @@ -39,17 +56,17 @@ to actions, but keep in mind, it's just a mock up, and real-world applications n ![image](https://user-images.githubusercontent.com/41629832/154848837-d53bf2fd-e721-4f78-8858-22d07cb36c43.png) - ## Stories ### Sidebar & Topbar - As a user, I want to see a sidebar in the left of the screen so I can easily navigate through the application. Each link representing a page in the application, when I'm on a specific page, I want it's link to be `white` - + When clicking on a link other than currently active link, user will be navigated to corresponding page. Sidebar contains two navigation links: + 1. Overview 2. Transaction History @@ -83,7 +100,7 @@ to actions, but keep in mind, it's just a mock up, and real-world applications n 4. `type` input: multiple radio buttons, one can be selected at a time, determining the transactions type. 5. `note` input: I want to be able to write up to 350 characters as a note attached to the transaction. - When `Dismiss` button clicked, the popup will be hidden and the state will be discarded. -- When `Add Transaction` button clicked: +- When `Add Transaction` button clicked: - If one or more fields do not have acceptable values, each input will have an error message below it, showing a proper error message. - If the form is valid, then a transaction will be saved to the transactions list. @@ -100,4 +117,4 @@ to actions, but keep in mind, it's just a mock up, and real-world applications n - As a user, I want to be able to see a Date input to select a `to` date to filter transactions until that date. - in `to` date input, future dates must be disabled. - When clicking on `Clear` button on the filter bar, all filters except search bar will be resetted to their initial values. -- As a user, I want to see pagination on the screen so I can change pages of which transactions is shown. +- As a user, I want to see pagination on the screen so I can change pages of which transactions is shown. diff --git a/app/components/AddTransactionModal.jsx b/app/components/AddTransactionModal.jsx new file mode 100644 index 0000000..d758524 --- /dev/null +++ b/app/components/AddTransactionModal.jsx @@ -0,0 +1,177 @@ +import { Form, useActionData, useTransition } from "@remix-run/react" +import propTypes from "prop-types" +import { useState } from "react" +import { BiDollar } from "react-icons/bi" +import { GrClose } from "react-icons/gr" +import { options } from "~/utils/categories" +import { + validateAmount, + validateCategory, + validateDate, + validateNote, + validateType, +} from "~/utils/validations" +import Error from "./Error" + +// get the options from the utils folder +const categoryOptions = options + +export default function AddTransactionModal({ onModalOpenClick }) { + const [type, setType] = useState("income") + const [note, setNote] = useState("") + + const transition = useTransition() + const actionData = useActionData() + + // disable the submit button while submitting + const submitButton = + transition.state === "submitting" ? ( + + ) : ( + + ) + + const handleFormSubmit = (e) => { + const category = validateCategory(e.target.category.value) + const date = validateDate(e.target.date.value) + const amount = validateAmount(e.target.amount.value) + const type = validateType(e.target.type.value) + const note = validateNote(e.target.note.value) + if (!category && !date && !amount && !type && !note) { + onModalOpenClick() + } + } + return ( +
+
+
+
Add Transaction
+ +
+
+
+
+ + + {actionData?.fieldErrors?.category ? ( + + ) : null} +
+
+ + + {actionData?.fieldErrors?.date ? ( + + ) : null} +
+
+ + +
+ + +
+ {actionData?.fieldErrors?.amount ? ( + + ) : null} +
+
+ +
+
+ setType(e.target.value)} + checked={type === "income"} + /> + +
+
+ setType(e.target.value)} + checked={type === "expense"} + /> + +
+
+ {actionData?.fieldErrors?.type ? ( + + ) : null} +
+
+ + +

{`${note.length} / 350`}

+ {actionData?.fieldErrors?.note ? ( + + ) : null} +
+
+ + {submitButton} +
+
+
+
+
+ ) +} + +AddTransactionModal.propTypes = { + onModalOpenClick: propTypes.func.isRequired, +} diff --git a/app/components/Card.jsx b/app/components/Card.jsx new file mode 100644 index 0000000..c746fb5 --- /dev/null +++ b/app/components/Card.jsx @@ -0,0 +1,18 @@ +import propTypes from "prop-types" +import { formatAmount } from "~/utils/formatAmount" + +export default function Card({ title, amount, color }) { + return ( +
+

{title}

+ +

${formatAmount(amount)}

+
+ ) +} + +Card.propTypes = { + title: propTypes.string.isRequired, + amount: propTypes.number.isRequired, + color: propTypes.string.isRequired, +} diff --git a/app/components/Cards.jsx b/app/components/Cards.jsx new file mode 100644 index 0000000..94c98f6 --- /dev/null +++ b/app/components/Cards.jsx @@ -0,0 +1,15 @@ +import { useLoaderData } from "@remix-run/react" +import { balance, expense, income } from "~/utils/calculateBalance" +import Card from "./Card" + +export default function Cards() { + const { transactions } = useLoaderData() + + return ( +
+ + + +
+ ) +} diff --git a/app/components/Document.jsx b/app/components/Document.jsx new file mode 100644 index 0000000..916df7e --- /dev/null +++ b/app/components/Document.jsx @@ -0,0 +1,29 @@ +import { + Links, + LiveReload, + Meta, + Scripts, + ScrollRestoration, +} from "@remix-run/react" + +import propTypes from "prop-types" +export default function Document({ children }) { + return ( + + + + + + + {children} + + + + + + ) +} + +Document.propTypes = { + children: propTypes.node.isRequired, +} diff --git a/app/components/Error.jsx b/app/components/Error.jsx new file mode 100644 index 0000000..f891fcf --- /dev/null +++ b/app/components/Error.jsx @@ -0,0 +1,8 @@ +import propTypes from "prop-types" +export default function Error({ error }) { + return

{error}

+} + +Error.propTypes = { + error: propTypes.string.isRequired, +} diff --git a/app/components/Filter.jsx b/app/components/Filter.jsx new file mode 100644 index 0000000..3216c92 --- /dev/null +++ b/app/components/Filter.jsx @@ -0,0 +1,74 @@ +import { FiFilter } from "react-icons/fi" +import Select from "react-select" +import makeAnimated from "react-select/animated" +import DatePicker from "react-datepicker" +import propTypes from "prop-types" +import { options } from "~/utils/categories" + +const categories = [...options["income"], ...options["expense"]] + +export default function Filter({ + selectedCategories, + onCategoryChange, + startDate, + onStartDateChange, + endDate, + onEndDateChange, + onClearClick, +}) { + return ( +
+
+ + +
+ +
+ ) +} + +SearchBar.propTypes = { + search: propTypes.string.isRequired, + onSearchChange: propTypes.func.isRequired, + onClearClick: propTypes.func.isRequired, +} diff --git a/app/components/Topbar.jsx b/app/components/Topbar.jsx new file mode 100644 index 0000000..b86b42f --- /dev/null +++ b/app/components/Topbar.jsx @@ -0,0 +1,13 @@ +import propTypes from "prop-types" + +export default function Topbar({ title }) { + return ( +
+

{title}

+
+ ) +} + +Topbar.propTypes = { + title: propTypes.string.isRequired, +} diff --git a/app/components/Transaction.jsx b/app/components/Transaction.jsx new file mode 100644 index 0000000..360ee6f --- /dev/null +++ b/app/components/Transaction.jsx @@ -0,0 +1,50 @@ +import propTypes from "prop-types" +import { FaMoneyBillAlt } from "react-icons/fa" +import { AiOutlineGift } from "react-icons/ai" +import { GiReceiveMoney, GiHealthNormal, GiClothes } from "react-icons/gi" +import { GrTechnology } from "react-icons/gr" +import { IoFastFoodOutline } from "react-icons/io5" +import { MdAttachMoney, MdOutlineSportsSoccer } from "react-icons/md" +import { formatAmount } from "~/utils/formatAmount" +import { formatDate } from "~/utils/formatDate" + +const icons = { + salary: , + loan: , + gift: , + tech: , + food: , + bills: , + sports: , + health: , + clothes: , +} +export default function Transaction({ category, note, date, amount, type }) { + const formattedAmount = formatAmount(amount) + const formattedDate = formatDate(date) + const amountElement = + type === "income" ? ( +

+${formattedAmount}

+ ) : ( +

-${formattedAmount}

+ ) + return ( +
+
+ {icons[category]} +

{note}

+
+
+

{formattedDate}

+ {amountElement} +
+
+ ) +} +Transaction.propTypes = { + category: propTypes.string.isRequired, + note: propTypes.string.isRequired, + date: propTypes.string.isRequired, + amount: propTypes.string.isRequired, + type: propTypes.string.isRequired, +} diff --git a/app/components/TransactionsList.jsx b/app/components/TransactionsList.jsx new file mode 100644 index 0000000..fc3beb3 --- /dev/null +++ b/app/components/TransactionsList.jsx @@ -0,0 +1,61 @@ +import Transaction from "./Transaction" +import Paginate from "react-paginate" +import { GrFormNext, GrFormPrevious } from "react-icons/gr" +import { useEffect, useState } from "react" +import propTypes from "prop-types" +import { paginate } from "~/utils/pagination" +export default function TransactionsList({ transactions }) { + const [currentPage, setCurrentPage] = useState(1) + + const { list: paginatedTransaction, pageCount } = paginate( + transactions, + 10, + currentPage + ) + + const transactionsArray = paginatedTransaction.map((transaction) => ( + + )) + + const handlePageChange = ({ selected }) => { + setCurrentPage(selected + 1) + } + + useEffect(() => { + setCurrentPage(1) + }, [transactions]) + + return ( +
+
{transactionsArray}
+ + {transactions.length > 10 ? ( + } + nextLabel={} + initialPage={currentPage - 1} + renderOnZeroPageCount={null} + className="pagination" + pageClassName="page" + activeClassName="active" + previousClassName="previous" + nextClassName="next" + disabledClassName="disabled" + onPageChange={handlePageChange} + /> + ) : null} +
+ ) +} + +TransactionsList.propTypes = { + transactions: propTypes.array.isRequired, +} diff --git a/app/hooks/useDebounce.jsx b/app/hooks/useDebounce.jsx new file mode 100644 index 0000000..67b5174 --- /dev/null +++ b/app/hooks/useDebounce.jsx @@ -0,0 +1,16 @@ +import { useEffect, useState } from "react" + +const useDebounce = (value, delay) => { + const [debouncedValue, setDebouncedValue] = useState(value) + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value) + }, delay) + return () => { + clearTimeout(handler) + } + }, [value, delay]) + return debouncedValue +} + +export default useDebounce diff --git a/app/root.jsx b/app/root.jsx index aacec3a..3497326 100644 --- a/app/root.jsx +++ b/app/root.jsx @@ -1,14 +1,10 @@ -import { - Links, - LiveReload, - Meta, - Outlet, - Scripts, - ScrollRestoration, -} from "@remix-run/react" +import { Outlet, useCatch } from "@remix-run/react" +import propTypes from "prop-types" import rootStyles from "~/styles/root.css" - +import Layout from "~/components/Layout" +import datePickerStyles from "react-datepicker/dist/react-datepicker.css" +import Document from "~/components/Document" /** * @returns {import("@remix-run/node").LinkDescriptor[]} */ @@ -17,6 +13,10 @@ export const links = () => [ rel: "stylesheet", href: rootStyles, }, + { + rel: "stylesheet", + href: datePickerStyles, + }, ] /** @@ -24,23 +24,54 @@ export const links = () => [ */ export const meta = () => ({ charset: "utf-8", - title: "New Remix App", + title: "Finance Manager", viewport: "width=device-width,initial-scale=1", + description: "A finance manager app that helps you manage your finances", + keywords: "finance, manager, app, money, budget", + author: "Ayub Abdullah", }) export default function App() { return ( - - - - - - + + - - - - - + + + ) +} + +export function ErrorBoundary({ error }) { + const { message, name } = error + return ( + + +
+

{name}

+

{message}

+
+
+
) } + +export function CatchBoundary() { + const caught = useCatch() + return ( + + +
+

{caught.status}

+

+ {caught.statusText || "Something went wrong"} +

+

{caught.data || "Something went wrong"}

+
+
+
+ ) +} + +ErrorBoundary.propTypes = { + error: propTypes.object.isRequired, +} diff --git a/app/routes/index.jsx b/app/routes/index.jsx index eb12141..f64c567 100644 --- a/app/routes/index.jsx +++ b/app/routes/index.jsx @@ -1,8 +1,86 @@ +import { json, redirect } from "@remix-run/node" +import { useState } from "react" +import AddTransactionModal from "~/components/AddTransactionModal" +import Cards from "~/components/Cards" +import LatestTransactions from "~/components/LatestTransactions" +import Topbar from "~/components/Topbar" +import { db } from "~/utils/db.server" +import { + validateAmount, + validateCategory, + validateDate, + validateNote, + validateType, +} from "~/utils/validations" + +export const loader = async () => { + const transactions = await db.transactions.findMany({ + orderBy: { createdAt: "desc" }, + }) + return json({ transactions }) +} + +export const action = async ({ request }) => { + const formData = await request.formData() + const { category, date, amount, type, note } = Object.fromEntries(formData) + + const fieldErrors = { + category: validateCategory(category), + date: validateDate(date), + amount: validateAmount(amount), + type: validateType(type), + note: validateNote(note), + } + + const fields = { + category, + date, + amount, + type, + note, + } + + if (Object.values(fieldErrors).some(Boolean)) { + return json( + { + fieldErrors, + fields, + }, + { status: 400 } + ) + } + + await db.transactions.create({ + data: { + category, + date: new Date(date), + amount: Number(amount), + type, + note, + }, + }) + + return redirect("/") +} + +export const meta = () => ({ + title: "Finance Manager | Overview", +}) export default function Index() { + const [isModalOpen, setIsModalOpen] = useState(false) + + const handleOpenModalClick = () => { + setIsModalOpen((prev) => !prev) + } + return ( -
-

Finance Manager

-

Let's get this done!

+
+ {isModalOpen ? ( + + ) : null} + + +
- ); + ) } diff --git a/app/routes/transactions.jsx b/app/routes/transactions.jsx new file mode 100644 index 0000000..fb53de0 --- /dev/null +++ b/app/routes/transactions.jsx @@ -0,0 +1,93 @@ +import { json } from "@remix-run/node" +import { useLoaderData } from "@remix-run/react" +import { useState } from "react" + +import Filter from "~/components/Filter" +import SearchBar from "~/components/SearchBar" +import Topbar from "~/components/Topbar" +import TransactionsList from "~/components/TransactionsList" +import useDebounce from "~/hooks/useDebounce" +import { db } from "~/utils/db.server" +import { filter } from "~/utils/filter" +import { getDateObj } from "~/utils/formatDate" +import { search } from "~/utils/search" + +export const loader = async () => { + const transactions = await db.transactions.findMany({ + orderBy: { date: "asc" }, + }) + return json({ transactions }) +} + +export const meta = () => ({ + title: "Finance Manager | Transactions History", +}) + +export default function Transactions() { + const { transactions } = useLoaderData() + + const [selectedCategories, setSelectedCategories] = useState([]) + const [startDate, setStartDate] = useState( + getDateObj(transactions[0]?.date || new Date()) + ) + const [endDate, setEndDate] = useState(new Date()) + + const [searchTerm, setSearchTerm] = useState("") + + const debouncedSearchTerm = useDebounce(searchTerm, 500) + + const searchedTransactions = search(transactions, debouncedSearchTerm) + + const filteredTransactions = filter( + searchedTransactions, + selectedCategories, + startDate, + endDate + ) + + const handleCategoryChange = (selected) => { + setSelectedCategories(selected) + } + + const handleStartDateChange = (date) => { + setStartDate(date) + } + + const handleEndDateChange = (date) => { + setEndDate(date) + } + + const handleClearClick = () => { + setSelectedCategories([]) + setStartDate(getDateObj(transactions[0]?.date || new Date())) + setEndDate(new Date()) + } + + const handleSearchChange = (e) => { + setSearchTerm(e.target.value) + } + + const handleSearchClearClick = () => { + setSearchTerm("") + } + return ( +
+ + + + +
+ ) +} diff --git a/app/styles/root.css b/app/styles/root.css index 6692dad..278ec06 100644 --- a/app/styles/root.css +++ b/app/styles/root.css @@ -1,16 +1,542 @@ * { box-sizing: border-box; - font-family: inherit; + font-family: inter; margin: 0; padding: 0; + font-size: 10px; } -.index-page { - font-family: system-ui, sans-serif; +body { + background-color: #e8e8e8; +} + +a { + text-decoration: none; +} + +.error-container { width: 100%; height: 100vh; display: flex; flex-direction: column; + justify-content: center; + align-items: center; + row-gap: 10px; + padding: 3rem; +} + +.catch-boundary .status { + font-size: 5rem; +} +.catch-boundary .status-text { + font-size: 3rem; +} +.catch-boundary .message { + font-size: 2rem; +} + +.error-boundary .title { + font-size: 5rem; +} +.error-boundary .message { + font-size: 2rem; +} + +.layout { + display: grid; + grid-template-columns: repeat(5, 1fr); +} +.layout .header { + grid-column: 1/2; +} +.layout .children { + grid-column: 2/6; +} + +.header { + position: fixed; + top: 0; + left: 0; + display: flex; + align-items: center; + flex-direction: column; + background: linear-gradient(180deg, #30475e 0%, #222831 100%); + height: 100%; + width: 20%; +} +.header .logo { + display: flex; + align-items: center; + font-size: 2.2rem; + font-weight: 600; + color: #f05454; + margin: 15% 0; +} +.header .logo .icon { + width: 3rem; + height: 3rem; + margin-right: 0.5rem; +} + +.navbar { + margin-top: 40%; + list-style: none; + display: flex; + flex-direction: column; + row-gap: 3rem; +} + +.nav-item { + color: #d4d4d8; + font-size: 2.2rem; + font-weight: 400; +} + +.active { + color: #fff; +} + +.topbar { + padding: 1.8rem; + background-color: #f7f4f3; + box-shadow: 0px 1px 8px rgba(34, 40, 49, 0.05); + height: 10%; +} +.topbar .title { + font-size: 2.2rem; + font-weight: 500; +} + +.transaction { + display: flex; + justify-content: space-between; + padding: 0.8rem 2rem; + background-color: rgb(248, 250, 252); + border-radius: 7px; + margin-bottom: 1rem; + color: rgb(51, 65, 85); +} +.transaction .wrapper { + display: flex; + align-items: center; + column-gap: 1.8rem; +} +.transaction .icon { + width: 3.2rem; + height: 3.2rem; + padding: 0.5rem; + border-radius: 50%; +} +.transaction .icon.green { + background-color: rgba(5, 150, 105, 0.2); + color: rgb(5, 150, 105); +} +.transaction .icon.blue { + background-color: rgba(2, 132, 199, 0.2); + color: rgb(2, 132, 199); +} +.transaction .note { + width: 40rem; + font-size: 1.6rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.transaction .date { + font-size: 1.6rem; +} +.transaction .amount { + padding: 0.6rem 1.4rem; + border-radius: 6px; + font-size: 1.5rem; + font-weight: 600; text-align: center; +} +.transaction .amount.green { + background-color: rgba(14, 165, 233, 0.15); + color: rgb(14, 165, 233); +} +.transaction .amount.red { + color: rgb(239, 42, 76); + background-color: rgba(239, 42, 76, 0.15); +} + +.cards { + display: flex; + column-gap: 3rem; + width: 80%; + height: 25%; + padding: 2.5rem; +} + +.card { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.5rem; + width: 32%; + height: 11rem; + border-radius: 7px; + color: #fff; + padding: 2rem; +} +.card.blue { + background: linear-gradient(90deg, #7dd3fc 0%, #bae6fd 102.82%); +} +.card.gray { + background: linear-gradient(90deg, #d4d4d8 0%, #e4e4e7 102.82%); +} +.card.red { + background: linear-gradient(90deg, #fda4af 0%, #fecdd3 102.82%); +} +.card .title { + font-size: 1.8rem; + font-weight: 600; +} +.card .title.blue { + color: #1a74c7; +} +.card .title.gray { + color: #71717a; +} +.card .title.red { + color: #ef2a4c; +} +.card .details-button { + border-radius: 12px; + font-size: 1.2rem; + border: none; + color: #fff; + justify-self: end; + padding: 0.2em 1rem; +} +.card .details-button.blue { + background-color: rgba(56, 189, 248, 0.75); +} +.card .details-button.gray { + background-color: rgba(113, 113, 122, 0.75); +} +.card .details-button.red { + background-color: rgba(251, 113, 133, 0.75); +} +.card .amount { + font-size: 3rem; + font-weight: 600; +} + +.latest-transactions { + display: flex; + flex-direction: column; + row-gap: 1rem; + width: 80%; + padding: 0 2.5rem; +} +.latest-transactions .wrapper { + display: flex; + justify-content: space-between; + align-items: center; +} +.latest-transactions .title { + font-size: 2.2rem; +} +.latest-transactions .add-transaction { + align-self: flex-end; + padding: 0.8rem 1.6rem; + border-radius: 50px; + background-color: rgba(28, 101, 140, 0.15); + font-size: 1.6rem; + color: rgb(28, 101, 140); + border: none; +} + +.modal { + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 100; +} +.modal .modal-content { + position: absolute; + top: 45%; + left: 50%; + transform: translate(-50%, -45%); + background-color: rgb(248, 250, 252); + padding: 1.8rem 2.5rem; + border-radius: 1.5rem; + width: 60%; + height: 65%; +} +.modal .modal-content .modal-header { + display: flex; + justify-content: space-between; + align-items: center; +} +.modal .modal-content .modal-header .title { + font-size: 1.6rem; + font-weight: 400; + color: rgb(0, 0, 0); +} +.modal .modal-content .modal-header .close { + cursor: pointer; + border: none; + background-color: transparent; +} +.modal .modal-content .modal-header .close .icon { + width: 1.6rem; + height: 1.6rem; + color: rgb(17, 24, 39); +} +.modal .modal-content .modal-body { + margin-top: 2rem; + height: 90%; +} +.modal .modal-content .modal-body .modal-form { + height: 100%; + display: grid; + grid-template-columns: repeat(3, 1fr); + column-gap: 3rem; + row-gap: 0.5rem; +} +.modal .modal-content .modal-body .modal-form .form-group { + display: flex; + flex-direction: column; + row-gap: 1rem; +} +.modal .modal-content .modal-body .modal-form .form-group input, +.modal .modal-content .modal-body .modal-form .form-group textarea { + height: 100%; + padding: 1rem; + resize: none; +} +.modal .modal-content .modal-body .modal-form .form-group.textarea { + grid-column: span 2; +} +.modal .modal-content .modal-body .modal-form .form-group .date, +.modal .modal-content .modal-body .modal-form .form-group .amount, +.modal .modal-content .modal-body .modal-form .form-group .category { + height: 3.8rem; + width: 100%; +} +.modal .modal-content .modal-body .modal-form .form-group .amount { + display: flex; + align-items: center; + border-radius: 7px; + border: 1px solid rgb(148, 163, 184); +} +.modal .modal-content .modal-body .modal-form .form-group .amount .amount-icon { + width: 2rem; + height: 2rem; + color: rgb(148, 163, 184); +} +.modal .modal-content .modal-body .modal-form .form-group .amount .amount-input { + width: 100%; + border: none; + outline: none; +} +.modal .modal-content .modal-body .modal-form .form-group .radio-container { + display: flex; + align-items: center; + column-gap: 1rem; + margin-top: 2rem; +} +.modal .modal-content .modal-body .modal-form .form-group .radio-input { + display: none; +} +.modal .modal-content .modal-body .modal-form .form-group .radio-input:checked + .radio-label { + border: 1px solid rgb(148, 163, 184); +} +.modal .modal-content .modal-body .modal-form .form-group .radio-label { + padding: 0.8rem 1.5rem; + border: 1px solid rgba(148, 163, 184, 0.3); + border-radius: 0.7rem; + cursor: pointer; +} +.modal .modal-content .modal-body .modal-form .form-button-group { + grid-column: span 3; + justify-self: end; + align-self: flex-end; + display: flex; + align-items: center; + column-gap: 0.5rem; +} +.modal .modal-content .modal-body .modal-form .form-button-group .button { + padding: 0.5rem 1rem; + border-radius: 5rem; + font-size: 1.2rem; + font-weight: 500; + border: none; +} +.modal .modal-content .modal-body .modal-form .form-button-group .button.primary { + background-color: rgba(28, 101, 140, 0.15); + color: rgb(28, 101, 140); +} +.modal .modal-content .modal-body .modal-form .form-button-group .button.cancel { + background-color: rgb(248, 250, 252); + color: rgb(0, 0, 0); + border: 1px solid rgb(0, 0, 0); +} + +.form-validation-error { + color: red; + font-size: 1rem; +} + +.overview-page { + min-height: 100vh; +} + +.search-bar { + display: grid; + grid-template-columns: repeat(6, 1fr); + column-gap: 1rem; + width: 95%; + margin: 2rem auto; + height: 4.8rem; + border-radius: 0.7rem; + padding-left: 0.8rem; + background-color: rgb(255, 255, 255); +} +.search-bar .wrapper { + grid-column: 1/6; + display: flex; + align-items: center; +} +.search-bar .icon { + width: 2rem; + height: 2rem; + color: rgb(126, 133, 148); +} +.search-bar .input { + width: 100%; + height: 100%; + padding: 0 0.8rem; + border: none; + outline: none; + background-color: transparent; + font-size: 1.4rem; + line-height: 1.7rem; + color: rgb(126, 133, 148); +} +.search-bar .clear-button { + width: 9.5rem; + grid-column: 6/7; + justify-self: end; + background-color: rgba(28, 101, 140, 0.75); + border: none; + border-radius: 0 0.7rem 0.7rem 0; + padding: 0.8rem 2rem; + font-size: 2rem; + line-height: 2.5rem; + color: rgb(255, 255, 255); +} + +.filter { + display: flex; + justify-content: space-between; + align-items: center; + margin: 0 auto; + padding-left: 2.2rem; + width: 95%; + height: 6.8rem; + background-color: rgb(248, 250, 252); + border-radius: 0.7rem; +} +.filter .wrapper { + display: flex; + align-items: center; + column-gap: 1rem; +} +.filter .icon { + width: 1.8rem; + height: 1.8rem; + color: rgb(126, 133, 148); + margin-right: 2.6rem; +} +.filter .dropdown { + width: 30rem; +} +.filter .date { + width: 24rem; + height: 4rem; + border-radius: 0.7rem; + background-color: rgb(255, 255, 255); + border: none; + outline: none; + padding: 0.6rem 0.8rem; + box-shadow: inset 0px 0px 2px rgba(0, 0, 0, 0.25); + font-size: 1.6rem; + line-height: 1.9rem; + color: rgb(17, 24, 39); +} +.filter .clear-button { + width: 9.5rem; + height: 100%; + background-color: rgba(28, 101, 140, 0.75); + border: none; + border-radius: 0 0.7rem 0.7rem 0; + padding: 0.8rem 2rem; + font-size: 2rem; + line-height: 2.5rem; + color: rgb(255, 255, 255); +} + +.transactions-list { + display: flex; + flex-direction: column; + width: 80%; + padding: 2.5rem; +} +.transactions-list .pagination { + align-self: flex-end; + display: flex; + list-style: none; + column-gap: 0.5rem; +} +.transactions-list .pagination .page { + width: 3rem; + height: 3rem; + display: flex; + justify-content: center; + align-items: center; + background-color: rgb(255, 255, 255); + border-radius: 1.6rem; + cursor: pointer; +} +.transactions-list .pagination .page a { + font-size: 1.6rem; + line-height: 1.9rem; + color: rgb(28, 101, 140); +} +.transactions-list .pagination .previous, +.transactions-list .pagination .next { + width: 3rem; + height: 3rem; + display: flex; justify-content: center; + align-items: center; + cursor: pointer; +} +.transactions-list .pagination .previous svg, +.transactions-list .pagination .next svg { + width: 2rem; + height: 2rem; + color: rgb(28, 101, 140); +} +.transactions-list .pagination .active { + background-color: rgba(28, 101, 140, 0.75); +} +.transactions-list .pagination .active a { + color: rgb(255, 255, 255); +} +.transactions-list .pagination .disabled { + cursor: not-allowed; +} +.transactions-list .pagination .disabled svg { + color: rgb(149, 159, 180); +} + +#transactions-page { + min-height: 100vh; } diff --git a/app/utils/calculateBalance.js b/app/utils/calculateBalance.js new file mode 100644 index 0000000..8daf75c --- /dev/null +++ b/app/utils/calculateBalance.js @@ -0,0 +1,19 @@ +import { getDateObj } from "./formatDate" + +const today = new Date() +const thisMonth = new Date(today.getFullYear(), today.getMonth(), 1) + +export const income = (transactions) => + transactions + .filter((transaction) => transaction.type === "income") + .filter((transaction) => getDateObj(transaction.date) >= thisMonth) + .reduce((sum, transaction) => sum + parseInt(transaction.amount, 10), 0) + +export const expense = (transactions) => + transactions + .filter((transaction) => transaction.type === "expense") + .filter((transaction) => getDateObj(transaction.date) >= thisMonth) + .reduce((sum, transaction) => sum + parseInt(transaction.amount, 10), 0) + +export const balance = (transactions) => + income(transactions) - expense(transactions) diff --git a/app/utils/categories.js b/app/utils/categories.js new file mode 100644 index 0000000..7c6c6e3 --- /dev/null +++ b/app/utils/categories.js @@ -0,0 +1,42 @@ +export const options = { + income: [ + { + value: "salary", + label: "Salary", + }, + { + value: "loan", + label: "Loan", + }, + { + value: "gift", + label: "Gift", + }, + ], + expense: [ + { + value: "tech", + label: "Tech", + }, + { + value: "food", + label: "Food", + }, + { + value: "bills", + label: "Bills", + }, + { + value: "sports", + label: "Sports", + }, + { + value: "health", + label: "Health", + }, + { + value: "clothes", + label: "Clothes", + }, + ], +} diff --git a/app/utils/db.server.ts b/app/utils/db.server.ts new file mode 100644 index 0000000..1f861a4 --- /dev/null +++ b/app/utils/db.server.ts @@ -0,0 +1,21 @@ +import { PrismaClient } from "@prisma/client"; + +let db: PrismaClient; + +declare global { + var __db: PrismaClient | undefined; +} + +// this is needed because in development we don't want to restart +// the server with every change, but we want to make sure we don't +// create a new connection to the DB with every change either. +if (process.env.NODE_ENV === "production") { + db = new PrismaClient(); +} else { + if (!global.__db) { + global.__db = new PrismaClient(); + } + db = global.__db; +} + +export { db }; \ No newline at end of file diff --git a/app/utils/filter.js b/app/utils/filter.js new file mode 100644 index 0000000..7c246bd --- /dev/null +++ b/app/utils/filter.js @@ -0,0 +1,14 @@ +import { getDateObj } from "./formatDate" + +export const filter = (list, selectedCategories, startDate, endDate) => + list.filter((transaction) => { + const isCategorySelected = selectedCategories.length + ? selectedCategories.some( + (category) => category.value === transaction.category + ) + : true + const isDateInRange = + getDateObj(transaction.date) >= startDate && + getDateObj(transaction.date) <= endDate + return isCategorySelected && isDateInRange + }) diff --git a/app/utils/formatAmount.js b/app/utils/formatAmount.js new file mode 100644 index 0000000..f8f35af --- /dev/null +++ b/app/utils/formatAmount.js @@ -0,0 +1,15 @@ +export const formatAmount = (amount) => { + const parsedAmount = parseInt(amount, 10) + const suffixes = [ + { value: 1000000000, suffix: "B" }, + { value: 1000000, suffix: "M" }, + { value: 1000, suffix: "K" }, + ] + for (const suffix of suffixes) { + if (parsedAmount >= suffix.value) { + return `${(parsedAmount / suffix.value).toFixed(1)}${suffix.suffix}` + } + } + + return parsedAmount +} diff --git a/app/utils/formatDate.js b/app/utils/formatDate.js new file mode 100644 index 0000000..448922d --- /dev/null +++ b/app/utils/formatDate.js @@ -0,0 +1,24 @@ +export const formatDate = (date) => { + const today = new Date() + const yesterday = new Date( + today.getFullYear(), + today.getMonth(), + today.getDate() - 1 + ) + + // I add plus 1 to getMonth because it starts from 0 + const dateObj = new Date(date) + if (dateObj.toDateString() === today.toDateString()) { + return "Today" + } else if (dateObj.toDateString() === yesterday.toDateString()) { + return "Yesterday" + } else { + return `${dateObj.getDate()}/${ + dateObj.getMonth() + 1 + }/${dateObj.getFullYear()}` + } +} + +export const getDateObj = (date) => { + return new Date(date) +} diff --git a/app/utils/getLatestTransactions.js b/app/utils/getLatestTransactions.js new file mode 100644 index 0000000..8e06fdf --- /dev/null +++ b/app/utils/getLatestTransactions.js @@ -0,0 +1,57 @@ +import { getDateObj } from "./formatDate" + +export let title = "" +export const getLatestTransactions = (transactions) => { + // Get current date + const currentDate = new Date() + + // Get dates for last seven days, last month, and this year + const lastWeek = new Date( + currentDate.getFullYear(), + currentDate.getMonth(), + currentDate.getDate() - 7 + ) + const lastMonth = new Date( + currentDate.getFullYear(), + currentDate.getMonth() - 1, + currentDate.getDate() + ) + const thisYear = new Date(currentDate.getFullYear(), 0, 1) + + // Initialize an array to hold all transactions + let allTransactions = [] + + //Check for transactions this week + if ( + transactions.some((transaction) => getDateObj(transaction.date) >= lastWeek) + ) { + allTransactions = transactions.filter( + (transaction) => getDateObj(transaction.date) >= lastWeek + ) + title = "This Week" + } + // Check for transactions last month + else if ( + transactions.some( + (transaction) => getDateObj(transaction.date) >= lastMonth + ) + ) { + allTransactions = transactions.filter( + (transaction) => getDateObj(transaction.date) >= lastMonth + ) + title = "Last Month" + } + + // Check for transactions this year + else if ( + transactions.some((transaction) => getDateObj(transaction.date) >= thisYear) + ) { + allTransactions = transactions.filter( + (transaction) => getDateObj(transaction.date) >= thisYear + ) + title = "This Year" + } + + // Return the last 10 transactions + return allTransactions.slice(Math.max(allTransactions.length - 10, 0)) +} diff --git a/app/utils/pagination.js b/app/utils/pagination.js new file mode 100644 index 0000000..a480b76 --- /dev/null +++ b/app/utils/pagination.js @@ -0,0 +1,13 @@ +export const paginate = (list, pageSize, currentPage) => { + const pageCount = Math.ceil(list.length / pageSize) + + const startIndex = (currentPage - 1) * pageSize + const endIndex = startIndex + pageSize + + const paginatedList = list.slice(startIndex, endIndex) + + return { + list: paginatedList, + pageCount, + } +} diff --git a/app/utils/route.js b/app/utils/route.js new file mode 100644 index 0000000..4a2d939 --- /dev/null +++ b/app/utils/route.js @@ -0,0 +1,4 @@ +export const routes = [ + { path: "/", title: "Overview" }, + { path: "/transactions", title: "Transaction History" }, +] diff --git a/app/utils/search.js b/app/utils/search.js new file mode 100644 index 0000000..1c42c4e --- /dev/null +++ b/app/utils/search.js @@ -0,0 +1,8 @@ +export const search = (list, term) => + list.filter((item) => { + const note = item.note.toLowerCase() + const amount = item.amount.toString() + const isNoteMatch = note.includes(term.toLowerCase()) + const isAmountMatch = amount.includes(term) + return isNoteMatch || isAmountMatch + }) diff --git a/app/utils/validations.js b/app/utils/validations.js new file mode 100644 index 0000000..41dcd19 --- /dev/null +++ b/app/utils/validations.js @@ -0,0 +1,42 @@ +import { options } from "./categories" +import { getDateObj } from "./formatDate" + +const categoryOptions = [...options["income"], ...options["expense"]].map( + (option) => option.value +) + +export const validateCategory = (category) => { + if (!category) return "Category is required" + if (!categoryOptions.includes(category)) return "Category is invalid" + + return null +} + +export const validateDate = (date) => { + if (!date) return "Date is required" + if (getDateObj(date) > new Date()) return "Date cannot be in the future" + + return null +} + +export const validateAmount = (amount) => { + if (!amount) return "Amount is required" + if (parseInt(amount, 10) < 0) return "Amount cannot be negative" + + return null +} + +export const validateType = (type) => { + if (!type) return "Type is required" + if (!["income", "expense"].includes(type)) return "Type is invalid" + + return null +} + +export const validateNote = (note) => { + if (!note) return "Note is required" + if (note.length > 350 || note.length < 0) + return "Note must be between 0 and 350 characters" + + return null +} diff --git a/jsconfig.json b/jsconfig.json index bf4ce14..ac501c5 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,5 +1,5 @@ { - "include": ["app/**/*.js", "app/**/*.jsx"], + "include": ["app/**/*.js", "app/**/*.jsx", "app/utils/db.server.ts"], "compilerOptions": { "lib": ["DOM", "DOM.Iterable", "ES2019"], "isolatedModules": true, diff --git a/package.json b/package.json index 1b2c0f3..c7ccfbb 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,11 @@ "sass:dev": "sass -q --no-source-map --watch styles/:app/styles", "sass:build": "sass styles/:app/styles", "build": "yarn sass:build && remix build", - "start": "remix-serve build" + "start": "remix-serve build", + "seed": "node prisma/seed.js" }, "dependencies": { + "@prisma/client": "^4.11.0", "@remix-run/node": "^1.9.0", "@remix-run/react": "^1.9.0", "@remix-run/serve": "^1.9.0", @@ -17,16 +19,22 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.31.11", "isbot": "^3.6.5", + "prop-types": "^15.8.1", "react": "^18.2.0", + "react-datepicker": "^4.10.0", "react-dom": "^18.2.0", + "react-icons": "^4.8.0", + "react-paginate": "^8.1.4", + "react-select": "^5.7.0", "sass": "^1.57.1" }, "devDependencies": { "@remix-run/dev": "^1.9.0", "@remix-run/eslint-config": "^1.9.0", - "eslint": "^8.31.0" + "eslint": "^8.31.0", + "prisma": "^4.11.0" }, "engines": { "node": ">=14" } -} +} \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..1e7d862 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,22 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") +} + +model Transactions { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + category String + date DateTime + amount Decimal + type String + note String +} diff --git a/prisma/seed.js b/prisma/seed.js new file mode 100644 index 0000000..eed5743 --- /dev/null +++ b/prisma/seed.js @@ -0,0 +1,80 @@ +const { PrismaClient } = require("@prisma/client") +const db = new PrismaClient() + +async function seed() { + await Promise.all( + getTransactions().map((transaction) => { + return db.transactions.create({ data: transaction }) + }) + ) +} + +seed() + +function getTransactions() { + return [ + { + category: "salary", + date: new Date(), + amount: 1000, + type: "income", + note: "Salary for the month of February", + }, + { + category: "loan", + date: new Date(), + amount: 5000, + type: "income", + note: "Loan from the bank", + }, + { + category: "gift", + date: new Date(), + amount: 200, + type: "income", + note: "Gift from my parents", + }, + { + category: "tech", + date: new Date(), + amount: 1500, + type: "expense", + note: "New laptop", + }, + { + category: "food", + date: new Date(), + amount: 100, + type: "expense", + note: "Groceries", + }, + { + category: "bills", + date: new Date(), + amount: 500, + type: "expense", + note: "Electricity bill", + }, + { + category: "sports", + date: new Date(), + amount: 100, + type: "expense", + note: "New shoes", + }, + { + category: "health", + date: new Date(), + amount: 200, + type: "expense", + note: "New medicine", + }, + { + category: "clothes", + date: new Date(), + amount: 100, + type: "expense", + note: "New shirt", + }, + ] +} diff --git a/styles/_global.scss b/styles/_global.scss index a9ead6f..a91c0ef 100644 --- a/styles/_global.scss +++ b/styles/_global.scss @@ -1,6 +1,43 @@ * { box-sizing: border-box; - font-family: inherit; + font-family: inter; margin: 0; padding: 0; -} \ No newline at end of file + font-size: 10px; +} +body { + background-color: #e8e8e8; +} +a { + text-decoration: none; +} +.error-container { + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + row-gap: 10px; + padding: 3rem; +} +.catch-boundary { + .status { + font-size: 5rem; + } + .status-text { + font-size: 3rem; + } + .message { + font-size: 2rem; + } +} + +.error-boundary { + .title { + font-size: 5rem; + } + .message { + font-size: 2rem; + } +} diff --git a/styles/components/_addTransactionModal.scss b/styles/components/_addTransactionModal.scss new file mode 100644 index 0000000..4279354 --- /dev/null +++ b/styles/components/_addTransactionModal.scss @@ -0,0 +1,129 @@ +.modal { + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 100; + .modal-content { + position: absolute; + top: 45%; + left: 50%; + transform: translate(-50%, -45%); + background-color: rgba(248, 250, 252, 1); + padding: 1.8rem 2.5rem; + border-radius: 1.5rem; + width: 60%; + height: 65%; + .modal-header { + display: flex; + justify-content: space-between; + align-items: center; + .title { + font-size: 1.6rem; + font-weight: 400; + color: rgba(0, 0, 0, 1); + } + .close { + cursor: pointer; + border: none; + background-color: transparent; + .icon { + width: 1.6rem; + height: 1.6rem; + color: rgba(17, 24, 39, 1); + } + } + } + .modal-body { + margin-top: 2rem; + height: 90%; + .modal-form { + height: 100%; + display: grid; + grid-template-columns: repeat(3, 1fr); + column-gap: 3rem; + row-gap: 0.5rem; + .form-group { + display: flex; + flex-direction: column; + row-gap: 1rem; + input, + textarea { + height: 100%; + padding: 1rem; + resize: none; + } + &.textarea { + grid-column: span 2; + } + .date, + .amount, + .category { + height: 3.8rem; + width: 100%; + } + .amount { + display: flex; + align-items: center; + border-radius: 7px; + border: 1px solid rgba(148, 163, 184, 1); + .amount-icon { + width: 2rem; + height: 2rem; + color: rgba(148, 163, 184, 1); + } + .amount-input { + width: 100%; + border: none; + outline: none; + } + } + .radio-container { + display: flex; + align-items: center; + column-gap: 1rem; + margin-top: 2rem; + } + .radio-input { + display: none; + &:checked + .radio-label { + border: 1px solid rgba(148, 163, 184, 1); + } + } + .radio-label { + padding: 0.8rem 1.5rem; + border: 1px solid rgba(148, 163, 184, 0.3); + border-radius: 0.7rem; + cursor: pointer; + } + } + .form-button-group { + grid-column: span 3; + justify-self: end; + align-self: flex-end; + display: flex; + align-items: center; + column-gap: 0.5rem; + .button { + padding: 0.5rem 1rem; + border-radius: 5rem; + font-size: 1.2rem; + font-weight: 500; + border: none; + &.primary { + background-color: rgba(28, 101, 140, 0.15); + color: rgba(28, 101, 140, 1); + } + &.cancel { + background-color: rgba(248, 250, 252, 1); + color: rgba(0, 0, 0, 1); + border: 1px solid rgba(0, 0, 0, 1); + } + } + } + } + } + } +} diff --git a/styles/components/_card.scss b/styles/components/_card.scss new file mode 100644 index 0000000..29ae45e --- /dev/null +++ b/styles/components/_card.scss @@ -0,0 +1,54 @@ +.card { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.5rem; + width: 32%; + height: 11rem; + border-radius: 7px; + + color: #fff; + padding: 2rem; + &.blue { + background: linear-gradient(90deg, #7dd3fc 0%, #bae6fd 102.82%); + } + &.gray { + background: linear-gradient(90deg, #d4d4d8 0%, #e4e4e7 102.82%); + } + &.red { + background: linear-gradient(90deg, #fda4af 0%, #fecdd3 102.82%); + } + .title { + font-size: 1.8rem; + font-weight: 600; + &.blue { + color: #1a74c7; + } + &.gray { + color: #71717a; + } + &.red { + color: #ef2a4c; + } + } + .details-button { + border-radius: 12px; + font-size: 1.2rem; + border: none; + color: #fff; + justify-self: end; + padding: 0.2em 1rem; + &.blue { + background-color: rgba(56, 189, 248, 0.75); + } + &.gray { + background-color: rgba(113, 113, 122, 0.75); + } + &.red { + background-color: rgba(251, 113, 133, 0.75); + } + } + .amount { + font-size: 3rem; + font-weight: 600; + } +} diff --git a/styles/components/_cards.scss b/styles/components/_cards.scss new file mode 100644 index 0000000..5c32031 --- /dev/null +++ b/styles/components/_cards.scss @@ -0,0 +1,7 @@ +.cards { + display: flex; + column-gap: 3rem; + width: 80%; + height: 25%; + padding: 2.5rem; +} diff --git a/styles/components/_error.scss b/styles/components/_error.scss new file mode 100644 index 0000000..5bd1a74 --- /dev/null +++ b/styles/components/_error.scss @@ -0,0 +1,4 @@ +.form-validation-error { + color: red; + font-size: 1rem; +} diff --git a/styles/components/_filter.scss b/styles/components/_filter.scss new file mode 100644 index 0000000..deb7533 --- /dev/null +++ b/styles/components/_filter.scss @@ -0,0 +1,49 @@ +.filter { + display: flex; + justify-content: space-between; + align-items: center; + margin: 0 auto; + padding-left: 2.2rem; + width: 95%; + height: 6.8rem; + background-color: rgba(248, 250, 252, 1); + border-radius: 0.7rem; + .wrapper { + display: flex; + align-items: center; + column-gap: 1rem; + } + .icon { + width: 1.8rem; + height: 1.8rem; + color: rgba(126, 133, 148, 1); + margin-right: 2.6rem; + } + .dropdown { + width: 30rem; + } + .date { + width: 24rem; + height: 4rem; + border-radius: 0.7rem; + background-color: rgba(255, 255, 255, 1); + border: none; + outline: none; + padding: 0.6rem 0.8rem; + box-shadow: inset 0px 0px 2px rgba(0, 0, 0, 0.25); + font-size: 1.6rem; + line-height: 1.9rem; + color: rgba(17, 24, 39, 1); + } + .clear-button{ + width: 9.5rem; + height: 100%; + background-color: rgba(28, 101, 140, 0.75); + border: none; + border-radius: 0 0.7rem 0.7rem 0; + padding: 0.8rem 2rem; + font-size: 2rem; + line-height: 2.5rem; + color: rgba(255, 255, 255, 1); + } +} diff --git a/styles/components/_header.scss b/styles/components/_header.scss new file mode 100644 index 0000000..19ca876 --- /dev/null +++ b/styles/components/_header.scss @@ -0,0 +1,24 @@ +.header { + position: fixed; + top: 0; + left: 0; + display: flex; + align-items: center; + flex-direction: column; + background: linear-gradient(180deg, #30475e 0%, #222831 100%); + height: 100%; + width: 20%; + .logo { + display: flex; + align-items: center; + font-size: 2.2rem; + font-weight: 600; + color: #f05454; + margin: 15% 0; + .icon { + width: 3rem; + height: 3rem; + margin-right: 0.5rem; + } + } +} diff --git a/styles/components/_latestTransactions.scss b/styles/components/_latestTransactions.scss new file mode 100644 index 0000000..8d59410 --- /dev/null +++ b/styles/components/_latestTransactions.scss @@ -0,0 +1,24 @@ +.latest-transactions { + display: flex; + flex-direction: column; + row-gap: 1rem; + width: 80%; + padding: 0 2.5rem; + .wrapper { + display: flex; + justify-content: space-between; + align-items: center; + } + .title { + font-size: 2.2rem; + } + .add-transaction { + align-self: flex-end; + padding: 0.8rem 1.6rem; + border-radius: 50px; + background-color: rgba(28, 101, 140, 0.15); + font-size: 1.6rem; + color: rgba(28, 101, 140, 1); + border: none; + } +} diff --git a/styles/components/_layout.scss b/styles/components/_layout.scss new file mode 100644 index 0000000..77be677 --- /dev/null +++ b/styles/components/_layout.scss @@ -0,0 +1,10 @@ +.layout{ + display: grid; + grid-template-columns: repeat(5, 1fr); + .header{ + grid-column: 1 / 2; + } + .children{ + grid-column: 2 / 6; + } +} \ No newline at end of file diff --git a/styles/components/_navItem.scss b/styles/components/_navItem.scss new file mode 100644 index 0000000..b653a5d --- /dev/null +++ b/styles/components/_navItem.scss @@ -0,0 +1,9 @@ +.nav-item { + color: #d4d4d8; + font-size: 2.2rem; + font-weight: 400; +} + +.active { + color: #fff; +} diff --git a/styles/components/_navbar.scss b/styles/components/_navbar.scss new file mode 100644 index 0000000..336dea2 --- /dev/null +++ b/styles/components/_navbar.scss @@ -0,0 +1,7 @@ +.navbar { + margin-top: 40%; + list-style: none; + display: flex; + flex-direction: column; + row-gap: 3rem; +} diff --git a/styles/components/_searchBar.scss b/styles/components/_searchBar.scss new file mode 100644 index 0000000..36d43c9 --- /dev/null +++ b/styles/components/_searchBar.scss @@ -0,0 +1,44 @@ +.search-bar { + display: grid; + grid-template-columns: repeat(6, 1fr); + column-gap: 1rem; + width: 95%; + margin: 2rem auto; + height: 4.8rem; + border-radius: 0.7rem; + padding-left: 0.8rem; + background-color: rgba(255, 255, 255, 1); + .wrapper { + grid-column: 1/6; + display: flex; + align-items: center; + } + .icon { + width: 2rem; + height: 2rem; + color: rgba(126, 133, 148, 1); + } + .input { + width: 100%; + height: 100%; + padding: 0 0.8rem; + border: none; + outline: none; + background-color: transparent; + font-size: 1.4rem; + line-height: 1.7rem; + color: rgba(126, 133, 148, 1); + } + .clear-button { + width: 9.5rem; + grid-column: 6/7; + justify-self: end; + background-color: rgba(28, 101, 140, 0.75); + border: none; + border-radius: 0 0.7rem 0.7rem 0; + padding: 0.8rem 2rem; + font-size: 2rem; + line-height: 2.5rem; + color: rgba(255, 255, 255, 1); + } +} diff --git a/styles/components/_topbar.scss b/styles/components/_topbar.scss new file mode 100644 index 0000000..f01132d --- /dev/null +++ b/styles/components/_topbar.scss @@ -0,0 +1,10 @@ +.topbar { + padding: 1.8rem; + background-color: #f7f4f3; + box-shadow: 0px 1px 8px rgba(34, 40, 49, 0.05); + height: 10%; + .title { + font-size: 2.2rem; + font-weight: 500; + } +} diff --git a/styles/components/_transaction.scss b/styles/components/_transaction.scss new file mode 100644 index 0000000..854057a --- /dev/null +++ b/styles/components/_transaction.scss @@ -0,0 +1,53 @@ +.transaction { + display: flex; + justify-content: space-between; + padding: 0.8rem 2rem; + background-color: rgba(248, 250, 252, 1); + border-radius: 7px; + margin-bottom: 1rem; + color: rgba(51, 65, 85, 1); + .wrapper { + display: flex; + align-items: center; + column-gap: 1.8rem; + } + .icon { + width: 3.2rem; + height: 3.2rem; + padding: 0.5rem; + border-radius: 50%; + &.green { + background-color: rgba(5, 150, 105, 0.2); + color: rgba(5, 150, 105, 1); + } + &.blue { + background-color: rgba(2, 132, 199, 0.2); + color: rgba(2, 132, 199, 1); + } + } + .note { + width: 40rem; + font-size: 1.6rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .date { + font-size: 1.6rem; + } + .amount { + padding: 0.6rem 1.4rem; + border-radius: 6px; + font-size: 1.5rem; + font-weight: 600; + text-align: center; + &.green { + background-color: rgba(14, 165, 233, 0.15); + color: rgba(14, 165, 233, 1); + } + &.red { + color: rgba(239, 42, 76, 1); + background-color: rgba(239, 42, 76, 0.15); + } + } +} diff --git a/styles/components/_transactionsList.scss b/styles/components/_transactionsList.scss new file mode 100644 index 0000000..5bf9999 --- /dev/null +++ b/styles/components/_transactionsList.scss @@ -0,0 +1,53 @@ +.transactions-list { + display: flex; + flex-direction: column; + width: 80%; + padding: 2.5rem; + .pagination { + align-self: flex-end; + display: flex; + list-style: none; + column-gap: 0.5rem; + .page { + width: 3rem; + height: 3rem; + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(255, 255, 255, 1); + border-radius: 1.6rem; + cursor: pointer; + a { + font-size: 1.6rem; + line-height: 1.9rem; + color: rgba(28, 101, 140, 1); + } + } + .previous, + .next { + width: 3rem; + height: 3rem; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + svg { + width: 2rem; + height: 2rem; + color: rgba(28, 101, 140, 1); + } + } + .active { + background-color: rgba(28, 101, 140, 0.75); + a { + color: rgba(255, 255, 255, 1); + } + } + .disabled { + cursor: not-allowed; + svg { + color: rgba(149, 159, 180, 1); + } + } + } +} diff --git a/styles/pages/_index.scss b/styles/pages/_index.scss index 2a88350..ec7b75c 100644 --- a/styles/pages/_index.scss +++ b/styles/pages/_index.scss @@ -1,9 +1,9 @@ -.index-page { - font-family: system-ui, sans-serif; - width: 100%; - height: 100vh; - display: flex; - flex-direction: column; - text-align: center; - justify-content: center; -} \ No newline at end of file +@import "../components/cards"; +@import "../components/card"; +@import "../components/latestTransactions"; +@import "../components/addTransactionModal"; +@import "../components/error"; + +.overview-page { + min-height: 100vh; +} diff --git a/styles/pages/_transactionHistory.scss b/styles/pages/_transactionHistory.scss new file mode 100644 index 0000000..30826ec --- /dev/null +++ b/styles/pages/_transactionHistory.scss @@ -0,0 +1,7 @@ +@import "../components/searchBar"; +@import "../components/filter"; +@import "../components/transactionsList"; + +#transactions-page { + min-height: 100vh; +} diff --git a/styles/root.scss b/styles/root.scss index 29f9b28..49250de 100644 --- a/styles/root.scss +++ b/styles/root.scss @@ -1,2 +1,11 @@ @import "global"; -@import "pages/index"; \ No newline at end of file + +@import "components/layout"; +@import "components/header"; +@import "components/navbar"; +@import "components/navItem"; +@import "components/topbar"; +@import "components/transaction"; + +@import "pages/index"; +@import "pages/transactionHistory"; diff --git a/yarn.lock b/yarn.lock index 09d6533..635011a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,7 +10,7 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.18.6": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== @@ -154,7 +154,7 @@ dependencies: "@babel/types" "^7.20.7" -"@babel/helper-module-imports@^7.18.6": +"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== @@ -998,6 +998,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.12.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" + integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/template@^7.18.10", "@babel/template@^7.20.7": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" @@ -1032,6 +1039,94 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@emotion/babel-plugin@^11.10.6": + version "11.10.6" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz#a68ee4b019d661d6f37dec4b8903255766925ead" + integrity sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.0" + "@emotion/memoize" "^0.8.0" + "@emotion/serialize" "^1.1.1" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.1.3" + +"@emotion/cache@^11.10.5", "@emotion/cache@^11.4.0": + version "11.10.5" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.5.tgz#c142da9351f94e47527ed458f7bbbbe40bb13c12" + integrity sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA== + dependencies: + "@emotion/memoize" "^0.8.0" + "@emotion/sheet" "^1.2.1" + "@emotion/utils" "^1.2.0" + "@emotion/weak-memoize" "^0.3.0" + stylis "4.1.3" + +"@emotion/hash@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.0.tgz#c5153d50401ee3c027a57a177bc269b16d889cb7" + integrity sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ== + +"@emotion/memoize@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" + integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== + +"@emotion/react@^11.8.1": + version "11.10.6" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.6.tgz#dbe5e650ab0f3b1d2e592e6ab1e006e75fd9ac11" + integrity sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.10.6" + "@emotion/cache" "^11.10.5" + "@emotion/serialize" "^1.1.1" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" + "@emotion/utils" "^1.2.0" + "@emotion/weak-memoize" "^0.3.0" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.1.tgz#0595701b1902feded8a96d293b26be3f5c1a5cf0" + integrity sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA== + dependencies: + "@emotion/hash" "^0.9.0" + "@emotion/memoize" "^0.8.0" + "@emotion/unitless" "^0.8.0" + "@emotion/utils" "^1.2.0" + csstype "^3.0.2" + +"@emotion/sheet@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.1.tgz#0767e0305230e894897cadb6c8df2c51e61a6c2c" + integrity sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA== + +"@emotion/unitless@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db" + integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw== + +"@emotion/use-insertion-effect-with-fallbacks@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz#ffadaec35dbb7885bd54de3fa267ab2f860294df" + integrity sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A== + +"@emotion/utils@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561" + integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw== + +"@emotion/weak-memoize@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" + integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== + "@esbuild-plugins/node-modules-polyfill@^0.1.4": version "0.1.4" resolved "https://registry.yarnpkg.com/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.1.4.tgz#eb2f55da11967b2986c913f1a7957d1c868849c0" @@ -1165,6 +1260,18 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@floating-ui/core@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.2.tgz#66f62cf1b7de2ed23a09c101808536e68caffaec" + integrity sha512-FaO9KVLFnxknZaGWGmNtjD2CVFuc0u4yeGEofoyXO2wgRA7fLtkngT6UB0vtWQWuhH3iMTZZ/Y89CMeyGfn8pA== + +"@floating-ui/dom@^1.0.1": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.2.3.tgz#8dc6fbf799fbb5c29f705b54bdd51f3ab0ee03a2" + integrity sha512-lK9cZUrHSJLMVAdCvDqs6Ug8gr0wmqksYiaoj/bxj2gweRQkSuhg2/V6Jswz2KiQ0RAULbqw1oQDJIMpQ5GfGA== + dependencies: + "@floating-ui/core" "^1.2.2" + "@gar/promisify@^1.0.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -1292,6 +1399,28 @@ tiny-glob "^0.2.9" tslib "^2.4.0" +"@popperjs/core@^2.9.2": + version "2.11.6" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" + integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== + +"@prisma/client@^4.11.0": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.11.0.tgz#41d5664dea4172c954190a432f70b86d3e2e629b" + integrity sha512-0INHYkQIqgAjrt7NzhYpeDQi8x3Nvylc2uDngKyFDDj1tTRQ4uV1HnVmd1sQEraeVAN63SOK0dgCKQHlvjL0KA== + dependencies: + "@prisma/engines-version" "4.11.0-57.8fde8fef4033376662cad983758335009d522acb" + +"@prisma/engines-version@4.11.0-57.8fde8fef4033376662cad983758335009d522acb": + version "4.11.0-57.8fde8fef4033376662cad983758335009d522acb" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.11.0-57.8fde8fef4033376662cad983758335009d522acb.tgz#74af5ff56170c78e93ce46c56510160f58cd3c01" + integrity sha512-3Vd8Qq06d5xD8Ch5WauWcUUrsVPdMC6Ge8ILji8RFfyhUpqon6qSyGM0apvr1O8n8qH8cKkEFqRPsYjuz5r83g== + +"@prisma/engines@4.11.0": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.11.0.tgz#c99749bfe20f58e8f4d2b5e04fee0785eba440e1" + integrity sha512-0AEBi2HXGV02cf6ASsBPhfsVIbVSDC9nbQed4iiY5eHttW9ZtMxHThuKZE1pnESbr8HRdgmFSa/Kn4OSNYuibg== + "@remix-run/dev@^1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@remix-run/dev/-/dev-1.9.0.tgz#2e594d143d47afc29e4bbdee02291649b0766dc6" @@ -1623,6 +1752,32 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/prop-types@*": + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + +"@types/react-transition-group@^4.4.0": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416" + integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA== + dependencies: + "@types/react" "*" + +"@types/react@*": + version "18.0.28" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065" + integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" @@ -1630,6 +1785,11 @@ dependencies: "@types/node" "*" +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + "@types/semver@^7.3.12": version "7.3.13" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" @@ -1985,6 +2145,15 @@ babel-core@^7.0.0-bridge.0: resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + babel-plugin-polyfill-corejs2@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" @@ -2311,6 +2480,11 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +classnames@^2.2.6: + version "2.3.2" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" + integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -2462,7 +2636,7 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@^1.7.0: +convert-source-map@^1.5.0, convert-source-map@^1.7.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== @@ -2509,6 +2683,17 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2518,6 +2703,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +csstype@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" + integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" @@ -2528,7 +2718,7 @@ data-uri-to-buffer@3, data-uri-to-buffer@^3.0.1: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== -date-fns@^2.29.1: +date-fns@^2.24.0, date-fns@^2.29.1: version "2.29.3" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== @@ -2720,6 +2910,14 @@ dom-accessibility-api@^0.5.9: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dotenv@^16.0.0: version "16.0.3" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" @@ -2780,6 +2978,13 @@ enhanced-resolve@^5.10.0: graceful-fs "^4.2.4" tapable "^2.2.0" +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + es-abstract@^1.19.0, es-abstract@^1.20.4: version "1.20.5" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.5.tgz#e6dc99177be37cacda5988e692c3fa8b218e95d2" @@ -3479,6 +3684,11 @@ find-cache-dir@^2.0.0: make-dir "^2.0.0" pkg-dir "^3.0.0" +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -3914,6 +4124,13 @@ hast-util-whitespace@^2.0.0: resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz#4fc1086467cc1ef5ba20673cb6b03cec3a970f1c" integrity sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg== +hoist-non-react-statics@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -4103,6 +4320,11 @@ is-arguments@^1.0.4, is-arguments@^1.1.0, is-arguments@^1.1.1: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -4501,7 +4723,7 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-parse-even-better-errors@^2.3.1: +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== @@ -4616,6 +4838,11 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + loader-utils@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" @@ -4668,7 +4895,7 @@ longest-streak@^3.0.0: resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -4853,6 +5080,11 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== +memoize-one@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" + integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -5695,6 +5927,16 @@ parse-entities@^4.0.0: is-decimal "^2.0.0" is-hexadecimal "^2.0.0" +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + parse-ms@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d" @@ -5832,6 +6074,13 @@ pretty-ms@^7.0.1: dependencies: parse-ms "^2.1.0" +prisma@^4.11.0: + version "4.11.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.11.0.tgz#9695ba4129a43eab3e76b5f7a033c6c020377725" + integrity sha512-4zZmBXssPUEiX+GeL0MUq/Yyie4ltiKmGu7jCJFnYMamNrrulTBc+D+QwAQSJ01tyzeGHlD13kOnqPwRipnlNw== + dependencies: + "@prisma/engines" "4.11.0" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -5842,7 +6091,7 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== -prop-types@^15.8.1: +prop-types@^15, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -5945,6 +6194,18 @@ raw-body@2.5.1, raw-body@^2.2.0: iconv-lite "0.4.24" unpipe "1.0.0" +react-datepicker@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.10.0.tgz#3f386ac5873dac5ea56544e51cdc01109938796c" + integrity sha512-6IfBCZyWj54ZZGLmEZJ9c4Yph0s9MVfEGDC2evOvf9AmVz+RRcfP2Czqad88Ff9wREbcbqa4dk7IFYeXF1d3Ag== + dependencies: + "@popperjs/core" "^2.9.2" + classnames "^2.2.6" + date-fns "^2.24.0" + prop-types "^15.7.2" + react-onclickoutside "^6.12.2" + react-popper "^2.3.0" + react-dom@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -5953,7 +6214,17 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" -react-is@^16.13.1: +react-fast-compare@^3.0.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" + integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== + +react-icons@^4.8.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.8.0.tgz#621e900caa23b912f737e41be57f27f6b2bff445" + integrity sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg== + +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -5963,6 +6234,26 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-onclickoutside@^6.12.2: + version "6.12.2" + resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.12.2.tgz#8e6cf80c7d17a79f2c908399918158a7b02dda01" + integrity sha512-NMXGa223OnsrGVp5dJHkuKxQ4czdLmXSp5jSV9OqiCky9LOpPATn3vLldc+q5fK3gKbEHvr7J1u0yhBh/xYkpA== + +react-paginate@^8.1.4: + version "8.1.4" + resolved "https://registry.yarnpkg.com/react-paginate/-/react-paginate-8.1.4.tgz#f62bfef4f07bb420f3a994e0284d1065a865a6b9" + integrity sha512-c3rxjcTEqeDQa6LqXifxLeFguY2qy2CHGRphVjHLFFMGfIHyaJ+v3bOvIlLYEeohwQ1q+cQpknjsqBVrkc/SNA== + dependencies: + prop-types "^15" + +react-popper@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba" + integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q== + dependencies: + react-fast-compare "^3.0.1" + warning "^4.0.2" + react-router-dom@6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.5.0.tgz#3970bdcaa7c710a6e0b478a833ba0b4b8ae61a6f" @@ -5978,6 +6269,31 @@ react-router@6.5.0: dependencies: "@remix-run/router" "1.1.0" +react-select@^5.7.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.7.0.tgz#82921b38f1fcf1471a0b62304da01f2896cd8ce6" + integrity sha512-lJGiMxCa3cqnUr2Jjtg9YHsaytiZqeNOKeibv6WF5zbK/fPegZ1hg3y/9P1RZVLhqBTs0PfqQLKuAACednYGhQ== + dependencies: + "@babel/runtime" "^7.12.0" + "@emotion/cache" "^11.4.0" + "@emotion/react" "^11.8.1" + "@floating-ui/dom" "^1.0.1" + "@types/react-transition-group" "^4.4.0" + memoize-one "^6.0.0" + prop-types "^15.6.0" + react-transition-group "^4.3.0" + use-isomorphic-layout-effect "^1.1.2" + +react-transition-group@^4.3.0: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" @@ -6188,7 +6504,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== -resolve@^1.10.1, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.0: +resolve@^1.10.1, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -6553,7 +6869,7 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== -source-map@^0.5.6: +source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== @@ -6717,6 +7033,11 @@ style-to-object@^0.3.0: dependencies: inline-style-parser "0.1.1" +stylis@4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7" + integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA== + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -7135,6 +7456,11 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== +use-isomorphic-layout-effect@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" + integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -7210,6 +7536,13 @@ vm2@^3.9.8: acorn "^8.7.0" acorn-walk "^8.2.0" +warning@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" @@ -7371,6 +7704,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"