diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..59cb1d6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +**/dev-dist +**/node_modules/ +**/dist +.git +npm-debug.log diff --git a/.env.template b/.env.template deleted file mode 100644 index e02bd97..0000000 --- a/.env.template +++ /dev/null @@ -1,2 +0,0 @@ -VITE_API_URL= -VITE_BUX_PAYMAIL_DOMAIN= diff --git a/.gitignore b/.gitignore index 1054271..ccfc358 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ dist-ssr # docker compose data/ bux-wallet-backend.env.private + +# overriden config +env-config.json diff --git a/docker-compose.yml b/docker-compose.yml index f80e91a..558d404 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,31 @@ version: "3.9" services: + bux-wallet-frontend: + build: + context: . + dockerfile: release/Dockerfile + container_name: bux-wallet-frontend + ports: + - "3002:80" + depends_on: + - bux-wallet-backend + # volumes: + # - '/host/path/to/env-config.json:/usr/share/nginx/html/env-config.json' + + db: image: postgres:15.2-alpine volumes: - - ./data/sql/db:/var/lib/postgresql/data:Z + - pgdata:/var/lib/postgresql/data + # - ./data/sql/db:/var/lib/postgresql/data:Z environment: - POSTGRES_NAME=postgres - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres -# Uncomment following lines if you need to check database (ex. for debugging) -# ports: -# - 5432:5432 + # Uncomment following lines if you need to check database (ex. for debugging) + # ports: + # - 5432:5432 healthcheck: test: ["CMD-SHELL", "sh -c 'pg_isready -U postgres -d postgres'"] timeout: 5s @@ -21,15 +35,22 @@ services: image: 4chain/bux-wallet-backend:latest environment: DB_HOST: 'db' + # For running bux-server locally + # BUX_SERVER_URL: 'http://host.docker.internal:3003/v1' BUX_SERVER_URL: 'https://bux.4chain.space/v1' + BUX_PAYMAIL_DOMAIN: '4chain.space' env_file: - bux-wallet-backend.env.private - # Maybe this will be needed to have working session with frontend proxy - # HTTP_SERVER_COOKIE_DOMAIN: 'localhost:3000' ports: - "8080:8080" links: - db + # For running bux-server locally + # extra_hosts: + # - "host.docker.internal:host-gateway" depends_on: db: condition: service_healthy +volumes: + pgdata: + driver: local diff --git a/package.json b/package.json index e59e5b7..6a53644 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dev": "vite", "build": "tsc && vite build", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "backend": "docker compose pull bux-wallet-backend && docker compose up", + "backend": "docker compose pull bux-wallet-backend && docker compose up db bux-wallet-backend", "preview": "vite preview" }, "dependencies": { diff --git a/public/config.default.json b/public/config.default.json new file mode 100644 index 0000000..85e8bea --- /dev/null +++ b/public/config.default.json @@ -0,0 +1,4 @@ +{ + "apiUrl": "http://localhost:3002", + "paymailDomain": "4chain.space" +} diff --git a/release/Dockerfile b/release/Dockerfile index a5d51fa..1245e9d 100644 --- a/release/Dockerfile +++ b/release/Dockerfile @@ -3,14 +3,10 @@ FROM node:18-alpine as build WORKDIR /app -RUN addgroup --system app && adduser -S -G app app && \ - chown -R app:app /app +RUN addgroup --system app && adduser --system app --ingroup app && chown -R app:app /app USER app -ARG VITE_API_URL -ARG VITE_BUX_PAYMAIL_DOMAIN - ENV NODE_PATH=/node_modules ENV PATH=$PATH:/node_modules/.bin diff --git a/src/App.tsx b/src/App.tsx index a490778..5c2841e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,7 +13,7 @@ import { getUser, LoggedInUser } from '@/api' import { Loader } from '@/components/Loader' import { useEffect, useState } from 'react' import { ErrorBar } from '@/components/ErrorBar' -import { useMountEffect } from '@/hooks' +import { useApiUrl } from './api/apiUrl' const ROUTES = [ { @@ -59,45 +59,50 @@ const ROUTES_AUTHENTICATED = [ export const App = () => { const { authorization, setAuthorization } = useAuthorization() + const apiUrl = useApiUrl() const navigate = useNavigate() const [loading, setLoading] = useState(true) const [errors, setError] = useState('') const location = useLocation() - useMountEffect(() => { - getUser() - .then((response) => { - const currentUserData: LoggedInUser = { - email: response.email, - paymail: response.paymail, - balance: response.balance, - } + useEffect(() => { + setError('') + apiUrl && + getUser(apiUrl) + .then((response) => { + const currentUserData: LoggedInUser = { + email: response.email, + paymail: response.paymail, + balance: response.balance, + } - if (currentUserData) { - setAuthorization(currentUserData) - setLoading(false) - } - }) - .catch((error) => { - let errorMsg - if (error.response.status === 401 || error.response.status === 400) { - setAuthorization(null) - navigate('/') - setLoading(false) - return - } + if (currentUserData) { + setAuthorization(currentUserData) + setLoading(false) + } + }) + .catch((error) => { + let errorMsg + if (error.response.status === 401 || error.response.status === 400) { + setAuthorization(null) + navigate('/') + setLoading(false) + return + } - if (error.response.status === 404) { - errorMsg = error.response.data + ". If you can't log in again, please contact our support or try again later!" - } else { - errorMsg = error.response.data ? error.response.data : 'Something went wrong... Please, try again later!' - } + if (error.response.status === 404) { + errorMsg = + error.response.data + ". If you can't log in again, please contact our support or try again later!" + } else { + errorMsg = error.response.data ? error.response.data : 'Something went wrong... Please, try again later!' + } - setError(errorMsg) - setLoading(false) - }) - }) + setError(errorMsg) + setLoading(false) + }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [apiUrl]) useEffect(() => { if (location) { diff --git a/src/api/apiUrl.ts b/src/api/apiUrl.ts new file mode 100644 index 0000000..6a8c603 --- /dev/null +++ b/src/api/apiUrl.ts @@ -0,0 +1,6 @@ +import { useConfig } from '@/providers' + +export const useApiUrl = () => { + const { config } = useConfig() + return config.apiUrl ? `${config.apiUrl}/api/v1` : undefined +} diff --git a/src/api/client.ts b/src/api/client.ts deleted file mode 100644 index 787ed49..0000000 --- a/src/api/client.ts +++ /dev/null @@ -1,5 +0,0 @@ -import axios from 'axios' - -export const axiosClient = axios.create({ - baseURL: `${import.meta.env.VITE_API_URL}/api/v1`, -}) diff --git a/src/api/requests/GetTransactionDetails.ts b/src/api/requests/GetTransactionDetails.ts index 25881ac..9f29bb5 100644 --- a/src/api/requests/GetTransactionDetails.ts +++ b/src/api/requests/GetTransactionDetails.ts @@ -1,6 +1,6 @@ -import { axiosClient } from '@/api/client' +import axios from 'axios' -export const getTransactionsDetails = async (transactionId: string) => { - const { data: response } = await axiosClient.get(`/transaction/${transactionId}`) +export const getTransactionsDetails = async (apiUrl: string, transactionId: string) => { + const { data: response } = await axios.get(`${apiUrl}/transaction/${transactionId}`) return response } diff --git a/src/api/requests/GetTransactions.ts b/src/api/requests/GetTransactions.ts index e8ea792..4e538b8 100644 --- a/src/api/requests/GetTransactions.ts +++ b/src/api/requests/GetTransactions.ts @@ -1,8 +1,16 @@ -import { axiosClient } from '@/api/client' +import axios from 'axios' -export const getTransactions = async (PAGE?: number, PAGE_SIZE?: number, ORDER?: string, SORT?: 'desc' | 'asc') => { - const { data: transactions } = await axiosClient.get( - `/transaction?page=${PAGE || 1}&page_size=${PAGE_SIZE || 10}&order=${ORDER || 'created_at'}&sort=${SORT || 'desc'}` +export const getTransactions = async ( + apiUrl: string, + PAGE?: number, + PAGE_SIZE?: number, + ORDER?: string, + SORT?: 'desc' | 'asc' +) => { + const { data: transactions } = await axios.get( + `${apiUrl}/transaction?page=${PAGE || 1}&page_size=${PAGE_SIZE || 10}&order=${ORDER || 'created_at'}&sort=${ + SORT || 'desc' + }` ) return { transactions, diff --git a/src/api/requests/GetUser.ts b/src/api/requests/GetUser.ts index 6d96d19..166b825 100644 --- a/src/api/requests/GetUser.ts +++ b/src/api/requests/GetUser.ts @@ -1,6 +1,6 @@ -import { axiosClient } from '@/api/client' +import axios from 'axios' -export const getUser = async () => { - const { data: response } = await axiosClient.get('/user') +export const getUser = async (apiUrl: string) => { + const { data: response } = await axios.get(`${apiUrl}/user`) return response } diff --git a/src/api/requests/LoginUser.ts b/src/api/requests/LoginUser.ts index a63663e..776e530 100644 --- a/src/api/requests/LoginUser.ts +++ b/src/api/requests/LoginUser.ts @@ -1,7 +1,7 @@ import { User } from '@/api/types/user' -import { axiosClient } from '@/api/client' +import axios from 'axios' -export const loginUser = async (data: User) => { - const { data: response } = await axiosClient.post('/sign-in', data, { withCredentials: true }) +export const loginUser = async (apiUrl: string, data: User) => { + const { data: response } = await axios.post(`${apiUrl}/sign-in`, data, { withCredentials: true }) return response } diff --git a/src/api/requests/Logout.ts b/src/api/requests/Logout.ts index 3b3d9ec..322332e 100644 --- a/src/api/requests/Logout.ts +++ b/src/api/requests/Logout.ts @@ -1,5 +1,5 @@ -import { axiosClient } from '@/api/client' +import axios from 'axios' -export const logoutUser = async () => { - await axiosClient.post('/sign-out') +export const logoutUser = async (apiUrl: string) => { + await axios.post(`${apiUrl}/sign-out`) } diff --git a/src/api/requests/RegisterUser.ts b/src/api/requests/RegisterUser.ts index 47b4bd7..318a9e0 100644 --- a/src/api/requests/RegisterUser.ts +++ b/src/api/requests/RegisterUser.ts @@ -1,7 +1,7 @@ import { RegisterNewUserDto } from '@/api/types/user' -import { axiosClient } from '@/api/client' +import axios from 'axios' -export const registerUser = async (data: RegisterNewUserDto) => { - const { data: response } = await axiosClient.post('/user', data) +export const registerUser = async (apiUrl: string, data: RegisterNewUserDto) => { + const { data: response } = await axios.post(`${apiUrl}/user`, data) return response } diff --git a/src/api/requests/SendTransaction.ts b/src/api/requests/SendTransaction.ts index 3006e15..fb7ee0a 100644 --- a/src/api/requests/SendTransaction.ts +++ b/src/api/requests/SendTransaction.ts @@ -1,7 +1,7 @@ -import { axiosClient } from '@/api/client' import { SendNewTransaction } from '@/api/types/transaction' +import axios from 'axios' -export const sendTransaction = async (data: SendNewTransaction) => { - const { data: response } = await axiosClient.post('/transaction', data) +export const sendTransaction = async (apiUrl: string, data: SendNewTransaction) => { + const { data: response } = await axios.post(`${apiUrl}/transaction`, data) return response } diff --git a/src/components/AccountSummary/AccountSummary.tsx b/src/components/AccountSummary/AccountSummary.tsx index c1404d2..3098a00 100644 --- a/src/components/AccountSummary/AccountSummary.tsx +++ b/src/components/AccountSummary/AccountSummary.tsx @@ -7,6 +7,7 @@ import { useEffect, useState } from 'react' import { getUser } from '@/api' import { Loader } from '@/components/Loader' import { ErrorBar } from '@/components/ErrorBar' +import { useApiUrl } from '@/api/apiUrl' interface CurrencyRates { usd?: number @@ -22,37 +23,40 @@ interface AccountDetails { export const AccountSummary = () => { const { autoupdate } = useAutoupdate() + const apiUrl = useApiUrl() const [details, setDetails] = useState(null) const [loading, setLoading] = useState(false) const [errors, setErrors] = useState('') useEffect(() => { setLoading(true) - getUser() - .then((response) => { - const accountDetails = { - balance: response.balance, - email: response.email, - paymail: response.paymail, - } - setDetails(accountDetails) - }) - .catch((error) => { - let errorMsg + setErrors('') + apiUrl && + getUser(apiUrl) + .then((response) => { + const accountDetails = { + balance: response.balance, + email: response.email, + paymail: response.paymail, + } + setDetails(accountDetails) + }) + .catch((error) => { + let errorMsg - if (error.response.status === 404) { - errorMsg = - "User's account details not found. If you can't log in again, please contact our support or try again later!" - } else { - errorMsg = error.response.data ? error.response.data : 'Something went wrong... Please, try again later!' - } + if (error.response.status === 404) { + errorMsg = + "User's account details not found. If you can't log in again, please contact our support or try again later!" + } else { + errorMsg = error.response.data ? error.response.data : 'Something went wrong... Please, try again later!' + } - setErrors(errorMsg) - }) - .finally(() => { - setLoading(false) - }) - }, [autoupdate]) + setErrors(errorMsg) + }) + .finally(() => { + setLoading(false) + }) + }, [apiUrl, autoupdate]) return ( }> diff --git a/src/components/Modal/_modals/TransactionConfirmModal/TransactionConfirmModal.tsx b/src/components/Modal/_modals/TransactionConfirmModal/TransactionConfirmModal.tsx index d1f40ee..458a629 100644 --- a/src/components/Modal/_modals/TransactionConfirmModal/TransactionConfirmModal.tsx +++ b/src/components/Modal/_modals/TransactionConfirmModal/TransactionConfirmModal.tsx @@ -11,6 +11,7 @@ import { sendTransaction } from '@/api/requests/SendTransaction' import { Loader } from '@/components/Loader' import { ErrorBar } from '@/components/ErrorBar' import { useAutoupdate } from '@/providers/autoupdate' +import { useApiUrl } from '@/api/apiUrl' export interface TransactionData { paymail: string @@ -39,6 +40,7 @@ export const TransactionConfirmModal: FC = ({ const [errorWithReload, setErrorWithReload] = useState(false) const { setAutoupdate } = useAutoupdate() + const apiUrl = useApiUrl() const onFormSubmitHandler = () => { if (!password) { @@ -58,42 +60,45 @@ export const TransactionConfirmModal: FC = ({ password: userPassword, } - sendTransaction(newTransactionData) - .then(() => { - setSuccessMsg(SUCCESS_SCREEN_MSG) + apiUrl && + sendTransaction(apiUrl, newTransactionData) + .then(() => { + setSuccessMsg(SUCCESS_SCREEN_MSG) - //store info about new transaction in global context - const updateTime = new Date().toISOString() - setAutoupdate(updateTime) + //store info about new transaction in global context + const updateTime = new Date().toISOString() + setAutoupdate(updateTime) - setTimeout(() => { - setSuccessMsg('') - secondaryButtonOnClickHandler && secondaryButtonOnClickHandler() - }, 3000) - }) - .catch((error) => { - if (error) { - if (error.response.status === 401) { - setErrors('Session expired! Please login in to your wallet') - setErrorWithReload(true) - return - } + setTimeout(() => { + setSuccessMsg('') + secondaryButtonOnClickHandler && secondaryButtonOnClickHandler() + }, 3000) + }) + .catch((error) => { + if (error) { + if (error.response.status === 401) { + setErrors('Session expired! Please login in to your wallet') + setErrorWithReload(true) + return + } - if (error.response.status === 400) { - setErrors('Transfer was not sent. Probably you filled the form with incorrect data. Please try once again!') - return - } + if (error.response.status === 400) { + setErrors( + 'Transfer was not sent. Probably you filled the form with incorrect data. Please try once again!' + ) + return + } - setErrors( - error.response.data - ? error.response.data - : 'Transfer was not sent. Please verify transfer data and try once again. If problem will happen again, contact with our support.' - ) - } - }) - .finally(() => { - setLoading(false) - }) + setErrors( + error.response.data + ? error.response.data + : 'Transfer was not sent. Please verify transfer data and try once again. If problem will happen again, contact with our support.' + ) + } + }) + .finally(() => { + setLoading(false) + }) } //back states to initial values on close modal diff --git a/src/components/Modal/_modals/TransactionDetailsModal/TransactionDetailsModal.tsx b/src/components/Modal/_modals/TransactionDetailsModal/TransactionDetailsModal.tsx index c460f18..49d9f11 100644 --- a/src/components/Modal/_modals/TransactionDetailsModal/TransactionDetailsModal.tsx +++ b/src/components/Modal/_modals/TransactionDetailsModal/TransactionDetailsModal.tsx @@ -1,5 +1,5 @@ import { Modal } from '@/components/Modal' -import { FC, useState } from 'react' +import { FC, useEffect, useState } from 'react' import { DataName, DetailsLink, @@ -7,11 +7,11 @@ import { ListElement, } from '@/components/Modal/_modals/TransactionDetailsModal/TransactionDetailsModal.styles' import { getTransactionsDetails } from '@/api/requests/GetTransactionDetails' -import { useMountEffect } from '@/hooks' import { TransactionDetails } from '@/api/types/transaction' import { format } from 'date-fns' import { Loader } from '@/components/Loader' import { ErrorBar } from '@/components/ErrorBar' +import { useApiUrl } from '@/api/apiUrl' interface TransactionDetailsProps { open: boolean @@ -25,19 +25,23 @@ export const TransactionDetailsModal: FC = ({ open, pri const [errors, setErrors] = useState('') const [loading, setLoading] = useState(true) - useMountEffect(() => { - getTransactionsDetails(id) - .then((response) => { - setTransactionData(response) - }) - .catch((error) => { - const errorMsg = error.response.data ? error.response.data : 'Something went wrong... Please try again later' - errorMsg && setErrors(errorMsg) - }) - .finally(() => { - setLoading(false) - }) - }) + const apiUrl = useApiUrl() + + useEffect(() => { + setErrors('') + apiUrl && + getTransactionsDetails(apiUrl, id) + .then((response) => { + setTransactionData(response) + }) + .catch((error) => { + const errorMsg = error.response.data ? error.response.data : 'Something went wrong... Please try again later' + errorMsg && setErrors(errorMsg) + }) + .finally(() => { + setLoading(false) + }) + }, [apiUrl, id]) return ( { const [currentPage, setCurrentPage] = useState(1) @@ -37,6 +38,7 @@ export const TransactionTable = () => { const [errors, setErrors] = useState('') const { autoupdate } = useAutoupdate() + const apiUrl = useApiUrl() const ITEMS_PER_PAGE = 10 const TOTAL_ITEMS = totalPages * ITEMS_PER_PAGE @@ -47,20 +49,21 @@ export const TransactionTable = () => { useEffect(() => { setLoading(true) - getTransactions(currentPage, ITEMS_PER_PAGE, 'created_at', 'desc') - .then((response) => { - const transactions = response.transactions + apiUrl && + getTransactions(apiUrl, currentPage, ITEMS_PER_PAGE, 'created_at', 'desc') + .then((response) => { + const transactions = response.transactions - setTotalPages(transactions.pages) - setTransactionsList(transactions.transactions) - setLoading(false) - }) - .catch((error) => { - const errorMsg = error.response.data ? error.response.data : 'Something went wrong... Please try again later' - setErrors(errorMsg) - }) - .finally(() => setLoading(false)) - }, [currentPage, autoupdate]) + setTotalPages(transactions.pages) + setTransactionsList(transactions.transactions) + setLoading(false) + }) + .catch((error) => { + const errorMsg = error.response.data ? error.response.data : 'Something went wrong... Please try again later' + setErrors(errorMsg) + }) + .finally(() => setLoading(false)) + }, [apiUrl, currentPage, autoupdate]) const smMatch = useMediaMatch('sm') diff --git a/src/components/TransferForm/TransferForm.tsx b/src/components/TransferForm/TransferForm.tsx index 5d1bafa..31e0307 100644 --- a/src/components/TransferForm/TransferForm.tsx +++ b/src/components/TransferForm/TransferForm.tsx @@ -9,6 +9,7 @@ import { Loader } from '@/components/Loader' import { TransactionConfirmModal, TransactionData } from '@/components/Modal/_modals/TransactionConfirmModal' import { EMAIL_REGEX } from '@/utils/constants' import { ErrorBar } from '@/components/ErrorBar' +import { useConfig } from '@/providers' export const TransferForm = () => { const [paymail, setPaymail] = useState('') @@ -17,6 +18,8 @@ export const TransferForm = () => { const [transactionData, setTransactionData] = useState(null) const [errors, setErrors] = useState('') + const { config } = useConfig() + const sendButtonDisabled = !paymail || !amount const cancelButtonDisabled = !paymail && !amount @@ -65,7 +68,7 @@ export const TransferForm = () => { Money transfer form setPaymail(event.target.value)} diff --git a/src/components/UserMenu/UserMenu.tsx b/src/components/UserMenu/UserMenu.tsx index 84069f9..55ab417 100644 --- a/src/components/UserMenu/UserMenu.tsx +++ b/src/components/UserMenu/UserMenu.tsx @@ -6,6 +6,7 @@ import { LogoutModal } from '@/components/Modal' import { useNavigate } from 'react-router-dom' import { useAuthorization } from '@/providers' import { logoutUser } from '@/api/requests/Logout' +import { useApiUrl } from '@/api/apiUrl' interface MenuProps { userEmail?: string @@ -18,6 +19,7 @@ export const UserMenu: FC = ({ userEmail }) => { const [loading, setLoading] = useState(false) const Navigate = useNavigate() const { setAuthorization } = useAuthorization() + const apiUrl = useApiUrl() const userMenuHandler = (event: React.MouseEvent) => { event.stopPropagation() @@ -47,19 +49,20 @@ export const UserMenu: FC = ({ userEmail }) => { const logoutHandler = () => { setLoading(true) - logoutUser() - .then(() => { - closeModal() - setAuthorization(null) - Navigate('/') - }) - .catch((error) => { - const errorMsg = error.response.data ? error.response.data : 'Something went wrong... Please try again!' - errorMsg && setErrors(errorMsg) - }) - .finally(() => { - setLoading(false) - }) + apiUrl && + logoutUser(apiUrl) + .then(() => { + closeModal() + setAuthorization(null) + Navigate('/') + }) + .catch((error) => { + const errorMsg = error.response.data ? error.response.data : 'Something went wrong... Please try again!' + errorMsg && setErrors(errorMsg) + }) + .finally(() => { + setLoading(false) + }) } return ( diff --git a/src/hooks/index.ts b/src/hooks/index.ts index b920c47..6238505 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,2 +1 @@ -export * from './useMountEffect' export * from './useMediaMatch' diff --git a/src/hooks/useMountEffect.ts b/src/hooks/useMountEffect.ts deleted file mode 100644 index 6c2dab1..0000000 --- a/src/hooks/useMountEffect.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { EffectCallback, useEffect, useRef } from 'react' - -export const useMountEffect = (effect: EffectCallback) => { - const mountedRef = useRef(false) - - return useEffect(() => { - if (mountedRef.current) { - return - } - effect() - return () => { - mountedRef.current = true - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) -} diff --git a/src/main.tsx b/src/main.tsx index 7e752af..dacee4e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,20 +1,22 @@ import React from 'react' import ReactDOM from 'react-dom/client' import { App } from './App' -import { AuthorizationProvider } from '@/providers' +import { AuthorizationProvider, ConfigProvider } from '@/providers' import { BrowserRouter } from 'react-router-dom' import { AutoupdateProvider } from '@/providers/autoupdate' import { registerSW } from 'virtual:pwa-register' ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - - - - - - - + + + + + + + + + ) diff --git a/src/providers/config/index.ts b/src/providers/config/index.ts new file mode 100644 index 0000000..6f29423 --- /dev/null +++ b/src/providers/config/index.ts @@ -0,0 +1 @@ +export * from './provider' diff --git a/src/providers/config/provider.tsx b/src/providers/config/provider.tsx new file mode 100644 index 0000000..fefc7f1 --- /dev/null +++ b/src/providers/config/provider.tsx @@ -0,0 +1,47 @@ +import { loadConfigFromFile } from '@/utils/loadConfig' +import { FC, ReactNode, createContext, useContext, useEffect, useState } from 'react' + +export type ConfigType = { + apiUrl: string | undefined + paymailDomain: string | undefined +} + +type ConfigContextType = { + config: ConfigType + setConfig: React.Dispatch> +} + +const ConfigContext = createContext(undefined) +ConfigContext.displayName = 'ConfigContext' + +type ConfigProviderProps = { + children: ReactNode +} + +export const ConfigProvider: FC = ({ children }) => { + const [config, setConfig] = useState({ + apiUrl: undefined, + paymailDomain: undefined, + }) + + useEffect(() => { + const fetchConfig = async () => { + const mergedConfig = await loadConfigFromFile() + setConfig(mergedConfig) + } + + fetchConfig().catch(console.error) + }, []) + + const value = { config, setConfig } + + return {children} +} + +export const useConfig = () => { + const ctx = useContext(ConfigContext) + if (!ctx) { + throw new Error('useConfig must be use within ConfigProvider') + } + return ctx +} diff --git a/src/providers/index.ts b/src/providers/index.ts index a46294e..edb1591 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1 +1,2 @@ export * from './authorization' +export * from './config' diff --git a/src/utils/loadConfig.ts b/src/utils/loadConfig.ts new file mode 100644 index 0000000..3c41ef7 --- /dev/null +++ b/src/utils/loadConfig.ts @@ -0,0 +1,38 @@ +import { ConfigType } from '@/providers' + +export const loadConfigFromFile = async () => { + let defaultConfig + try { + const defaultPublicConfigFile = await fetch('/config.default.json') + defaultConfig = await defaultPublicConfigFile.json() + } catch { + throw new Error('Default config does not exist in the public directory') + } + + let overrideConfig + try { + const overrideConfigFile = await fetch('/env-config.json') + overrideConfig = await overrideConfigFile.json() + } catch { + console.info('File env-config.json not specified or wrong format (requires json). Using default config...') + return defaultConfig + } + + console.info('Using merged (default with override) config...') + return mergeConfig(defaultConfig, overrideConfig) +} + +const mergeConfig = (defaultConfig: ConfigType, overrideConfig: ConfigType) => { + const initialConfigKeys = Object.keys(defaultConfig) + const overrideConfigMatchingKeys = Object.keys(overrideConfig).filter((key) => + initialConfigKeys.includes(key) + ) as (keyof ConfigType)[] + + overrideConfigMatchingKeys.forEach((key) => { + if (typeof defaultConfig[key] === typeof overrideConfig[key]) { + defaultConfig[key] = overrideConfig[key] + } + }) + + return defaultConfig +} diff --git a/src/views/LoginPage/LoginPage.tsx b/src/views/LoginPage/LoginPage.tsx index 2a29ef9..ff4da7b 100644 --- a/src/views/LoginPage/LoginPage.tsx +++ b/src/views/LoginPage/LoginPage.tsx @@ -9,6 +9,7 @@ import { Loader } from '@/components/Loader' import { useAuthorization } from '@/providers' import { useNavigate } from 'react-router-dom' import { LoggedInUser, loginUser } from '@/api' +import { useApiUrl } from '@/api/apiUrl' export const LoginPage = () => { const [email, setEmail] = useState('') @@ -18,6 +19,7 @@ export const LoginPage = () => { const { setAuthorization } = useAuthorization() const navigate = useNavigate() + const apiUrl = useApiUrl() const handleSubmit = (event: FormEvent) => { event.preventDefault() @@ -36,23 +38,26 @@ export const LoginPage = () => { password: password, } - loginUser(User) - .then((response) => { - setErrors('') - const LoggedInUser: LoggedInUser = { - email: email, - paymail: response.paymail, - balance: response.balance, - } - setAuthorization(LoggedInUser) - navigate('/dashboard') - setLoading(false) - }) - .catch((error) => { - const errorMsg = error.response.data ? error.response.data : 'Something went wrong... Please, try again later!' - setErrors(errorMsg) - setLoading(false) - }) + apiUrl && + loginUser(apiUrl, User) + .then((response) => { + setErrors('') + const LoggedInUser: LoggedInUser = { + email: email, + paymail: response.paymail, + balance: response.balance, + } + setAuthorization(LoggedInUser) + navigate('/dashboard') + setLoading(false) + }) + .catch((error) => { + const errorMsg = error.response.data + ? error.response.data + : 'Something went wrong... Please, try again later!' + setErrors(errorMsg) + setLoading(false) + }) } return ( diff --git a/src/views/SignupPage/SignupPage.tsx b/src/views/SignupPage/SignupPage.tsx index 9ae6022..b9c8b97 100644 --- a/src/views/SignupPage/SignupPage.tsx +++ b/src/views/SignupPage/SignupPage.tsx @@ -11,6 +11,7 @@ import { Loader } from '@/components/Loader' import { AfterRegistrationSteps } from '@/components/StepsList/_lists/AfterRegistrationSteps' import { RegisterNewUserDto } from '@/api/types/user' import { registerUser } from '@/api' +import { useApiUrl } from '@/api/apiUrl' export const SignupPage = () => { const [email, setEmail] = useState('') @@ -23,6 +24,7 @@ export const SignupPage = () => { const [mnemonic, setMnemonic] = useState('') const [paymail, setPaymail] = useState('') const [loading, setLoading] = useState(false) + const apiUrl = useApiUrl() const handleSubmit = (event: FormEvent) => { event.preventDefault() @@ -46,20 +48,23 @@ export const SignupPage = () => { passwordConfirmation: confirmedPassword, } - registerUser(newUser) - .then((response) => { - setRegistered(true) - setErrors('') - setMnemonic(response.mnemonic) - setPaymail(response.paymail) - setLoading(false) - }) - .catch((error) => { - const errorMsg = error.response.data ? error.response.data : 'Something went wrong... Please, try again later!' - setErrors(errorMsg) - setRegistered(false) - setLoading(false) - }) + apiUrl && + registerUser(apiUrl, newUser) + .then((response) => { + setRegistered(true) + setErrors('') + setMnemonic(response.mnemonic) + setPaymail(response.paymail) + setLoading(false) + }) + .catch((error) => { + const errorMsg = error.response.data + ? error.response.data + : 'Something went wrong... Please, try again later!' + setErrors(errorMsg) + setRegistered(false) + setLoading(false) + }) } return ( diff --git a/vite.config.ts b/vite.config.ts index 67d1c74..17eb2e0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -37,7 +37,7 @@ export default defineConfig({ host: true, proxy: { '/api': { - target: 'http://localhost:8080', + target: 'http://127.0.0.1:8080', changeOrigin: true, secure: false, },