| @@ -3,7 +3,10 @@ | |||
| "version": "0.1.0", | |||
| "private": true, | |||
| "dependencies": { | |||
| "@emotion/react": "^11.10.5", | |||
| "@emotion/styled": "^11.10.5", | |||
| "@faker-js/faker": "^7.6.0", | |||
| "@mui/material": "^5.10.12", | |||
| "@testing-library/jest-dom": "^5.14.1", | |||
| "@testing-library/react": "^13.0.0", | |||
| "@testing-library/user-event": "^13.2.1", | |||
| @@ -14,8 +17,10 @@ | |||
| "axios": "^1.1.3", | |||
| "date-fns": "^2.29.3", | |||
| "i18next": "^22.0.4", | |||
| "qs": "^6.11.0", | |||
| "react": "^18.2.0", | |||
| "react-dom": "^18.2.0", | |||
| "react-helmet-async": "^1.3.0", | |||
| "react-i18next": "^12.0.0", | |||
| "react-scripts": "5.0.1", | |||
| "scss": "^0.2.4", | |||
| @@ -0,0 +1,18 @@ | |||
| import { createContext, FC, ReactNode } from "react"; | |||
| import { ThemeProvider } from "@mui/material/styles"; | |||
| import useToggleColorMode from "../hooks/useToggleColorMode"; | |||
| export const ColorModeContext = createContext({ | |||
| toggleColorMode: () => {}, | |||
| }); | |||
| const ColorModeProvider = ({ children }) => { | |||
| const [toggleColorMode, theme] = useToggleColorMode(); | |||
| return ( | |||
| <ColorModeContext.Provider value={toggleColorMode}> | |||
| <ThemeProvider theme={theme}> {children} </ThemeProvider> | |||
| </ColorModeContext.Provider> | |||
| ); | |||
| }; | |||
| export default ColorModeProvider; | |||
| @@ -0,0 +1,17 @@ | |||
| import { useEffect, useState } from 'react'; | |||
| function useDebounce<T>(value: T, delay?: number) { | |||
| const [debouncedValue, setDebouncedValue] = useState<T>(value); | |||
| useEffect(() => { | |||
| const timer = setTimeout(() => setDebouncedValue(value), delay || 500); | |||
| return () => { | |||
| clearTimeout(timer); | |||
| }; | |||
| }, [value, delay]); | |||
| return debouncedValue; | |||
| }; | |||
| export default useDebounce; | |||
| @@ -0,0 +1,32 @@ | |||
| import { useState, useMemo } from 'react'; | |||
| import { createTheme } from '@mui/material/styles'; | |||
| import { | |||
| authScopeSetHelper, | |||
| authScopeStringGetHelper, | |||
| } from '../util/helpers/authScopeHelpers'; | |||
| import { PaletteMode } from '@mui/material'; | |||
| const useToggleColorMode = () => { | |||
| const currentColorMode = authScopeStringGetHelper('colorMode') || 'light'; | |||
| const [mode, setMode] = useState<PaletteMode>(currentColorMode); | |||
| const toggleColorMode = () => { | |||
| const nextMode = mode === 'light' ? 'dark' : 'light'; | |||
| setMode(nextMode); | |||
| authScopeSetHelper('colorMode', nextMode); | |||
| }; | |||
| const theme = useMemo( | |||
| () => | |||
| createTheme({ | |||
| palette: { | |||
| mode | |||
| } | |||
| }), | |||
| [mode] | |||
| ); | |||
| return [toggleColorMode, theme]; | |||
| }; | |||
| export default useToggleColorMode; | |||
| @@ -1,19 +1,20 @@ | |||
| import React from 'react'; | |||
| import ReactDOM from 'react-dom/client'; | |||
| import './index.css'; | |||
| import "./main.scss"; | |||
| import App from './App'; | |||
| import reportWebVitals from './reportWebVitals'; | |||
| import { HelmetProvider } from 'react-helmet-async'; | |||
| import './i18n'; | |||
| import ColorModeProvider from './context/ColorModeContext'; | |||
| const root = ReactDOM.createRoot( | |||
| document.getElementById('root') as HTMLElement | |||
| ); | |||
| root.render( | |||
| <React.StrictMode> | |||
| <App /> | |||
| </React.StrictMode> | |||
| <HelmetProvider> | |||
| <React.StrictMode> | |||
| <ColorModeProvider> | |||
| <App /> | |||
| </ColorModeProvider> | |||
| </React.StrictMode> | |||
| </HelmetProvider> | |||
| ); | |||
| // If you want to start measuring performance in your app, pass a function | |||
| // to log results (for example: reportWebVitals(console.log)) | |||
| // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals | |||
| reportWebVitals(); | |||
| @@ -0,0 +1,71 @@ | |||
| import { | |||
| deleteRequest, | |||
| getRequest, | |||
| patchRequest, | |||
| replaceInUrl, | |||
| postRequest, | |||
| } from './index'; | |||
| import apiEndpoints from './apiEndpoints'; | |||
| export const getAccount = (accountUid: string) => | |||
| getRequest(replaceInUrl(apiEndpoints.accounts.get, { accountUid })); | |||
| export const getAccountUsers = (accountUid: string) => | |||
| getRequest(replaceInUrl(apiEndpoints.accounts.getUsers, { accountUid })); | |||
| export const getUserPermissions = (currentAccountUid: string, currentUserUid: string) => | |||
| getRequest( | |||
| replaceInUrl(apiEndpoints.accounts.getCurrentUserPermissions, { | |||
| currentAccountUid, | |||
| currentUserUid, | |||
| }), | |||
| ); | |||
| export const getAccountAddresses = (accountUid: string) => | |||
| getRequest(replaceInUrl(apiEndpoints.accounts.getAddresses, { accountUid })); | |||
| export const getAccountSettingsRequest = (accountUid: string) => | |||
| getRequest(replaceInUrl(apiEndpoints.accounts.getSettings, { accountUid })); | |||
| export const updateAccountAddressRequest = (accountUid: string, addressUid: string, data: any) => | |||
| patchRequest( | |||
| replaceInUrl(apiEndpoints.accounts.updateAddress, { | |||
| accountUid, | |||
| addressUid, | |||
| }), | |||
| data, | |||
| ); | |||
| export const deleteAccountAddressRequest = (accountUid: string, addressUid: string) => | |||
| deleteRequest( | |||
| replaceInUrl(apiEndpoints.accounts.deleteAddress, { | |||
| accountUid, | |||
| addressUid, | |||
| }), | |||
| ); | |||
| export const postNewAccountUserRequest = (accountUid: string, data: any) => | |||
| postRequest( | |||
| replaceInUrl(apiEndpoints.accounts.createUser, { | |||
| accountUid, | |||
| }), | |||
| data, | |||
| ); | |||
| export const updateAccountUserRequest = ( | |||
| accountUid: string, | |||
| userUid: string, | |||
| actionType: string, | |||
| data: any, | |||
| ) => | |||
| patchRequest( | |||
| replaceInUrl(apiEndpoints.accounts.updateUser, { | |||
| accountUid, | |||
| userUid, | |||
| actionType, | |||
| }), | |||
| data, | |||
| ); | |||
| export const postAgreementRequest = (data: any) => | |||
| postRequest(apiEndpoints.accounts.agreement, data); | |||
| @@ -0,0 +1,156 @@ | |||
| export default { | |||
| accounts: { | |||
| get: 'accounts/{accountUid}', | |||
| getCurrentUserPermissions: | |||
| 'accounts/{currentAccountUid}/users/{currentUserUid}/permissions', | |||
| getAddresses: 'accounts/{accountUid}/addresses', | |||
| updateAddress: 'account/{accountUid}/addresses/{addressUid}', | |||
| deleteAddress: 'accounts/{accountUid}/addresses/{addressUid}', | |||
| getUsers: 'accounts/{accountUid}/users', | |||
| createUser: 'accounts/{accountUid}/users', | |||
| updateUser: 'account/{accountUid}/users/{userUid}?actionType={actionType}', | |||
| deleteUser: 'accounts/{accountUid}/users/{userUid}', | |||
| getSettings: 'accounts/{accountUid}/settings', | |||
| getIraSettings: 'accounts/{accountUid}/iraSettings', | |||
| getSettingsRegistration: 'application/settings', | |||
| agreement: 'accounts/agreement', | |||
| }, | |||
| authentications: { | |||
| getUsernames: 'authenticate/usernames', | |||
| login: 'authenticate', | |||
| getUserSecurityQuestion: 'users/username/securityquestion', | |||
| confirmSecurityQuestion: 'authenticate/confirm', | |||
| confirmForgotPassword: 'users/passwords/reset_token', | |||
| resetPassword: 'users/passwords', | |||
| refreshToken: '/authenticate/refresh', | |||
| generateToken: '/authenticate/generate', | |||
| authenticate: | |||
| '/authenticate?fp={fp}&offer={offer}&landingPageUrl={landingPageUrl}®istrationFlowType={registrationFlowType}', | |||
| confirmAuthentication: | |||
| '/authenticate/confirm?fp={fp}&offer={offer}&landingPageUrl={landingPageUrl}®istrationFlowType={registrationFlowType}', | |||
| }, | |||
| bankAccounts: { | |||
| get: 'accounts/{accountUid}/bankaccounts', | |||
| getBankAccount: | |||
| 'accounts/{accountUid}/bankaccounts/{bankAccountUid}?type={type}', | |||
| getBankAccountsByType: | |||
| 'accounts/{accountUid}/bankaccounts?type={type}&active=true', | |||
| getBankDetailsByRoutingNumber: 'banks/{routingNumber}', | |||
| newAccount: 'accounts/{accountUid}/bankaccounts', | |||
| deleteAccount: | |||
| 'accounts/{accountUid}/bankaccounts/{bankAccountUid}?type={type}', | |||
| verify: '/accounts/{accountUid}/bankaccountverification/{bankAccountUid}', | |||
| postBankAccountRegistration: '/accounts/{applicationUid}/bankaccounts', | |||
| getRegistration: 'banks/{applicationUid}/bankaccounts', | |||
| }, | |||
| documents: { | |||
| getDocuments: 'accounts/{accountUid}/documents?year={year}', | |||
| getDocument: 'accounts/{accountUid}/documents/{documentType}', | |||
| }, | |||
| countries: '/countries', | |||
| metalStream: { | |||
| getMetalStreamSettings: 'accounts/{accountUid}/metalstream', | |||
| getMetalStreamFundings: 'applications/{applicationUid}/metalStreamFunding', | |||
| }, | |||
| orders: { | |||
| buyForStorage: '/accounts/{accountUid}/orders/buyForStorage', | |||
| buyForDelivery: '/accounts/{accountUid}/orders/buyForDelivery', | |||
| verifyBuyForDelivery: '/accounts/{accountUid}/orders/buyForDelivery/verify', | |||
| sellFromStorage: '/accounts/{accountUid}/orders/sellFromStorage', | |||
| fractionalConversion: '/accounts/{accountUid}/orders/fractionalConversion', | |||
| deliverFromStorageVerify: | |||
| '/accounts/{accountUid}/orders/deliverFromStorage/verify', | |||
| deliverFromStorage: '/accounts/{accountUid}/orders/deliverFromStorage', | |||
| iraCashDistribution: '/accounts/{accountUid}/orders/iraCashDistribution', | |||
| iraCashTransfer: '/accounts/{accountUid}/orders/iraCashTransfer', | |||
| iraFeeWithdrawal: 'accounts/{accountUid}/orders/iraFeeWithdrawal', | |||
| achDeposit: 'accounts/{accountUid}/orders/achDeposit', | |||
| wireWithdrawal: '/accounts/{accountUid}/orders/wireWithdrawal', | |||
| checkWithdrawal: '/accounts/{accountUid}/orders/checkWithdrawal', | |||
| }, | |||
| portfolio: { | |||
| getPortfolioValuations: 'accounts/{accountUid}/portfolio/valuations', | |||
| getPortfolioMetalPrices: 'marketprices', | |||
| getPortfolioHoldings: | |||
| 'accounts/{accountUid}/portfolio/products?valuation=true', | |||
| getPortfolioProductCodes: '/accounts/{accountUid}/portfolio/productcodes', | |||
| getPortfolioBalances: '/accounts/{accountUid}/portfolio/balances', | |||
| getPortfolioProductBySymbol: | |||
| '/accounts/{accountUid}/portfolio/products/{symbol}', | |||
| getPortfolioTransactions: '/accounts/{accountUid}/transactions', | |||
| getPortfolioSingleTransaction: | |||
| '/accounts/{accountUid}/transactions/{transactionUid}', | |||
| getProductPortoflioTransactions: 'accounts/{accountUid}/transactions', | |||
| getRecentPortfolioTransactions: | |||
| 'accounts/{accountUid}/transactions?content=Recent', | |||
| getFinancialPortfolioTransactions: 'accounts/{accountUid}/transactions', | |||
| getFinancialPortfolioPendingTransactions: | |||
| 'accounts/{accountUid}/transactions/fundinghistory', | |||
| patchFinancialPortfolioPendingTransactions: | |||
| '/accounts/{accountUid}/transactions/fundinghistory/{depositKey}', | |||
| }, | |||
| products: { | |||
| getPrices: '/accounts/{accountUid}/products/prices', | |||
| prices: 'accounts/{accountUid}/products/prices?side={side}', | |||
| tiers: | |||
| '/accounts/{accountUid}/products/prices/{symbol}/tiers?side={side}&location={location}', | |||
| symbolPrices: '/accounts/{accountUid}/products/{symbol}/prices?side={side}', | |||
| getPricesRegistration: 'applications/{applicationUid}/products/prices', | |||
| }, | |||
| settings: { | |||
| get: 'settings', | |||
| }, | |||
| taxForms: { | |||
| getTaxForms: 'settings/taxForms/{applicationType}', | |||
| }, | |||
| users: { | |||
| getAccounts: '/users/{userUid}/accounts', | |||
| getRegistrationAccounts: '/users/{userUid}/accounts', | |||
| updateUser: '/users/{userUid}?updateUserActionType={actionType}', | |||
| updateUserPassword: '/users/{userUid}/passwords', | |||
| logout: '/users/{userUid}/logout', | |||
| getUsernames: '/users/email', | |||
| createUser: | |||
| '/users?fp={fp}&offer={offer}&landingPageUrl={landingPageUrl}®istrationFlowType={registrationFlowType}', | |||
| updateUserRegistration: '/users/{userUid}', | |||
| invite: '/users/invite', | |||
| }, | |||
| applications: { | |||
| application: '/applications/{applicationUid}', | |||
| addPerson: '/applications/{applicationUid}/persons', | |||
| updatePerson: '/applications/{applicationUid}/persons/{personUid}', | |||
| addPersonWithGiftState: | |||
| '/applications/{applicationUid}/UTMA/persons?giftState={giftState}', | |||
| updatePersonWithGiftState: | |||
| '/applications/{applicationUid}/UTMA/persons/{personUid}?giftState={giftState}', | |||
| addPersonWithCompanyName: | |||
| '/applications/{applicationUid}/IRA/persons?companyName={companyName}', | |||
| updatePersonWithCompanyName: | |||
| '/applications/{applicationUid}/IRA/persons/{personUid}?companyName={companyName}', | |||
| submitLegalEntity: '/applications/{applicationUid}/legalEntities', | |||
| updateLegalEntity: | |||
| '/applications/{applicationUid}/legalEntities/{personUid}', | |||
| postNonIraFunding: '/applications/{applicationUid}/funding', | |||
| postIraFunding: '/applications/{applicationUid}/iraFunding', | |||
| postMSFunding: '/applications/{applicationUid}/metalStreamFunding', | |||
| consent: '/applications/{applicationUid}/consents', | |||
| updateConsent: `/applications/{applicationUid}/consents/{agreementConsentUid}`, | |||
| submitMetalStreamRegistration: | |||
| '/applications/{applicationUid}/metalStreamFunding', | |||
| }, | |||
| common: { | |||
| getCountries: '/countries/', | |||
| getTaxForms: '/taxForms/', | |||
| getContributionYears: 'contributionYears', | |||
| getCountryStates: '/countries/{iso3CountryCode}/states/', | |||
| getSecurityQuestions: '/registration/securityQuestions/', | |||
| getPortalSecurityQuestions: '/securityQuestions', | |||
| }, | |||
| plaid: { | |||
| getToken: '/bankaccounts/createplaidlinktoken', | |||
| }, | |||
| affiliate: { | |||
| setCookie: '/affiliate/picture', | |||
| setFingerprint: '/affiliate/fingerprint', | |||
| }, | |||
| }; | |||
| @@ -0,0 +1,68 @@ | |||
| import axios, { AxiosError, AxiosResponse } from 'axios'; | |||
| import queryString from 'qs'; | |||
| const request = axios.create({ | |||
| baseURL: process.env.REACT_APP_BASE_API_URL, | |||
| headers: { | |||
| 'Content-Type': 'application/json', | |||
| }, | |||
| withCredentials: true, | |||
| paramsSerializer: { | |||
| encode: (params) => queryString.stringify(params, { arrayFormat: 'comma' }) | |||
| } | |||
| }); | |||
| export const getRequest = (url: string, params = {}, options = {}) => | |||
| request.get(url, { params, ...options }); | |||
| export const postRequest = (url: string, data?: any, params = null, options = {}) => | |||
| request.post(url, data, { params, ...options }); | |||
| export const putRequest = (url: string, data: any, params = null, options = {}) => | |||
| request.put(url, data, { params, ...options }); | |||
| export const patchRequest = (url: string, data: any, params = null, options = {}) => | |||
| request.patch(url, data, { params, ...options }); | |||
| export const deleteRequest = (url: string, params = null, options = {}) => | |||
| request.delete(url, { params, ...options }); | |||
| export const downloadRequest = (url: string, params = null, options = {}) => | |||
| request.get(url, { params, ...options, responseType: 'blob' }); | |||
| export const replaceInUrl = (url: string, pathVariables: any = {}) => { | |||
| const keys = Object.keys(pathVariables); | |||
| if (!keys.length) { | |||
| return url; | |||
| } | |||
| return keys.reduce( | |||
| (acc, key) => acc.replace(`{${key}}`, pathVariables[`${key}`]), | |||
| url, | |||
| ); | |||
| }; | |||
| export const addHeaderToken = (token: string) => { | |||
| request.defaults.headers.Authorization = `Bearer ${token}`; | |||
| }; | |||
| export const addHeaderCookie = (key: string, value: string) => { | |||
| request.defaults.headers[`${key}`] = value; | |||
| }; | |||
| export const removeHeaderToken = () => { | |||
| delete request.defaults.headers.Authorization; | |||
| }; | |||
| const onResponse = (response: AxiosResponse): AxiosResponse => | |||
| response; | |||
| export const attachPostRequestListener = (postRequestListener: (response: AxiosError) => void) => { | |||
| request.interceptors.response.use( | |||
| onResponse, | |||
| (response) => postRequestListener(response) | |||
| ); | |||
| }; | |||
| export const apiDefaultUrl = request.defaults.baseURL; | |||
| @@ -0,0 +1,13 @@ | |||
| import axios from 'axios'; | |||
| const JSON_SERVER_ENDPOINT = 'http://localhost:4000'; | |||
| const request = axios.create({ | |||
| baseURL: JSON_SERVER_ENDPOINT, | |||
| headers: { | |||
| 'Content-Type': 'application/json', | |||
| }, | |||
| }); | |||
| export const getRequest = (url: string, params = null, options = {}) => | |||
| request.get(url, { params, ...options }); | |||
| @@ -0,0 +1,26 @@ | |||
| import { getRequest, postRequest, replaceInUrl } from './index'; | |||
| import apiEndpoints from './apiEndpoints'; | |||
| export const getUsernames = (emailorusername: string) => | |||
| getRequest(apiEndpoints.authentications.getUsernames, { | |||
| emailorusername, | |||
| }); | |||
| export const attemptLogin = (payload: string) => | |||
| postRequest(apiEndpoints.authentications.login, payload); | |||
| export const updateSecurityAnswer = (payload: any) => | |||
| postRequest(apiEndpoints.authentications.confirmSecurityQuestion, payload); | |||
| export const refreshTokenRequest = (payload: any) => | |||
| postRequest(apiEndpoints.authentications.refreshToken, payload); | |||
| export const logoutUserRequest = (userUid: string) => | |||
| postRequest( | |||
| replaceInUrl(apiEndpoints.users.logout, { | |||
| userUid, | |||
| }), | |||
| ); | |||
| export const generateTokenRequest = (payload: any) => | |||
| postRequest(apiEndpoints.authentications.generateToken, payload); | |||