added: settingsProvider

This commit is contained in:
venuswhispers
2024-12-19 06:54:22 +08:00
parent f2c0810a1b
commit 62e748784c
15 changed files with 686 additions and 33 deletions
+50
View File
@@ -0,0 +1,50 @@
// providers
import { ErrorBoundary } from 'react-error-boundary'
import { TooltipProvider } from '@hanzo/ui/primitives'
import { SettingsProvider } from '@/contexts/settings-provider'
// import { IntercomProvider } from 'react-use-intercom'
// import ErrorFallback from '@/components/ErrorFallback'
// import ColorSchema from '@/components/ColorSchema'
// import TonConnectProvider from './TonConnectProvider'
// import RainbowKit from '@/components/RainbowKit'
// import Solana from '@/components/SolanaProvider'
// import { SendErrorMessage } from '@/lib/telegram'
// import { AuthProvider } from '@/contexts/authContext'
// import { FeeProvider } from '@/contexts/feeContext'
// import { JotaiProvider } from '@/contexts/jotaiContext'
// import { EthersProvider } from '@/contexts/ethersContext'
//types
import type { BridgeSettings } from '@/types/bridge-settings'
import type { ErrorInfo, PropsWithChildren } from 'react'
import { type ThemeData, THEME_COLORS } from '@/types/theme'
import { BridgeAppSettings } from '@/types/bridge-app-settings'
const INTERCOM_APP_ID = 'o1kmvctg'
import { ToastProvider } from '@/contexts/toast-provider'
const Contexts: React.FC<
{
settings: BridgeSettings
} & PropsWithChildren
> = ({ settings, children }) => {
// @info These were was all that getServerSideProps() ever returned.
// So seems clearest to just do it this way.
const _settings = settings ?? {
networks: [],
// exchanges: [],
// sourceRoutes: [],
// destinationRoutes: [],
}
const themeData = THEME_COLORS.default // :aa TODO
return (
<SettingsProvider settings={new BridgeAppSettings(_settings)}>
<ToastProvider>
{children}
</ToastProvider>
</SettingsProvider>
)
}
export default Contexts
@@ -0,0 +1,58 @@
import {
createContext,
useContext,
type PropsWithChildren
} from 'react'
import { BridgeAppSettings } from '@/types/bridge-app-settings'
// Type for the Settings Container
type SettingsContainer = {
settings: BridgeAppSettings
};
// Create the Context
const SettingsContext = createContext<SettingsContainer | null>(null)
// Provider Component
const SettingsProvider: React.FC<{
settings: BridgeAppSettings
} & PropsWithChildren> = ({
children,
settings
}) => {
return (
<SettingsContext.Provider value={{ settings }}>
{children}
</SettingsContext.Provider>
);
};
// Hook to Access Settings
const useSettings = (): BridgeAppSettings => {
const container = useContext(SettingsContext)
if (!container) {
throw new Error('useSettings must be used within a SettingsProvider')
}
return container.settings
};
// Hook to Access the Entire Settings Container
const useSettingsContainer = (): SettingsContainer => {
const container = useContext(SettingsContext)
if (!container) {
throw new Error('useSettingsContainer must be used within a SettingsProvider')
}
return container
};
export {
SettingsProvider,
useSettings,
useSettingsContainer,
SettingsContext
};
@@ -0,0 +1,86 @@
'use client'
import React, { useContext } from 'react'
import { toast, ToastContainer } from 'react-toastify'
// import '@/styles/other-react-toastify.css'
interface ToastService {
notify: (msg: string, t: 'warn' | 'error' | 'success' | 'info') => void
}
const ToastContext = React.createContext<ToastService | null>(null)
const ToastProvider = ({ children }: { children: React.ReactNode }) => {
const notify = (msg: string, t: 'warn' | 'error' | 'success' | 'info') => {
if (t === 'success') {
toast.success(msg, {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
theme: 'dark',
})
} else if (t === 'info') {
toast.info(msg, {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
theme: 'dark',
})
} else if (t === 'warn') {
toast.warn(msg, {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
theme: 'dark',
})
} else if (t === 'error') {
toast.error(msg, {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
theme: 'dark',
})
}
}
return (
<ToastContext.Provider value={{ notify: notify }}>
{children}
<ToastContainer
position="top-right"
autoClose={50000}
hideProgressBar={false}
newestOnTop
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
</ToastContext.Provider>
)
}
const useNotify = () => {
const context = useContext(ToastContext)
if (!context) {
throw new Error('Invalid ToastContext!')
}
return {
notify: context.notify,
}
}
export { ToastProvider, useNotify }
+50 -32
View File
@@ -7,11 +7,13 @@ import {
Scripts,
ScrollRestoration,
type MetaFunction,
LiveReload
LiveReload,
useLoaderData
} from '@remix-run/react'
import { json, type LoaderFunction, redirect } from "@remix-run/node"
import Contexts from '@/contexts'
import type { LinksFunction } from '@vercel/remix'
import { Analytics } from '@vercel/analytics/react';
import { Analytics } from '@vercel/analytics/react'
import { BreakpointIndicator } from '@hanzo/ui/primitives-common'
import '@luxfi/ui/style/lux-global-non-next.css'
@@ -19,8 +21,9 @@ import '@luxfi/ui/style/cart-animation.css'
import '@luxfi/ui/style/checkout-animation.css'
import Main from 'app/components/main'
import _links from './links';
import metadata from './metadata';
import _links from './links'
import metadata from './metadata'
import getBridgeSettings from './utils/get-bridge-settings'
export const config = { runtime: 'edge' }
@@ -29,35 +32,50 @@ const bodyClasses = 'bg-background text-foreground flex flex-col min-h-full '
export const links: LinksFunction = () => (_links)
export const meta: MetaFunction = () => (metadata)
export const loader: LoaderFunction = async () => {
const settings = await getBridgeSettings()
if (!settings) {
return redirect("/") // Redirect if settings are not available
}
return json({ settings })
}
const Layout: React.FC<PropsWithChildren> = ({
children
}) => (
<html
lang='en'
suppressHydrationWarning
className='hanzo-ui-dark-theme'
style={{backgroundColor: '#000'}}
>
<head>
<meta charSet='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<Meta />
<Links />
</head>
<body className={bodyClasses} >
<Main className='gap-4 '>
{children}
</Main>
<ScrollRestoration />
<Scripts />
{/* https://github.com/remix-run/remix/issues/2958#issuecomment-2188876125
process.env.NODE_ENV === 'development' && <LiveReload />
*/}
{process.env.NODE_ENV === 'development' && <BreakpointIndicator />}
<Analytics />
</body>
</html>
)
}) => {
const { settings } = useLoaderData<typeof loader>()
return (
<html
lang='en'
suppressHydrationWarning
className='hanzo-ui-dark-theme'
style={{backgroundColor: '#000'}}
>
<head>
<meta charSet='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<Meta />
<Links />
</head>
<body className={bodyClasses} >
<Contexts settings={settings}>
<Main className='gap-4 '>
{children}
</Main>
<ScrollRestoration />
<Scripts />
{/* https://github.com/remix-run/remix/issues/2958#issuecomment-2188876125
process.env.NODE_ENV === 'development' && <LiveReload />
*/}
{process.env.NODE_ENV === 'development' && <BreakpointIndicator />}
<Analytics />
</Contexts>
</body>
</html>
)
}
const App: React.FC = () => (
<Outlet />
+18
View File
@@ -0,0 +1,18 @@
export type ApiError = {
code: LSAPIKnownErrorCode | string,
message: string;
}
export enum LSAPIKnownErrorCode {
NOT_FOUND = "NOT_FOUND",
INVALID_CREDENTIALS = "INVALID_CREDENTIALS",
INSUFFICIENT_FUNDS = "INSUFFICIENT_FUNDS",
FUNDS_ON_HOLD = "FUNDS_ON_HOLD_ERROR",
COINBASE_AUTHORIZATION_LIMIT_EXCEEDED = "COINBASE_AUTHORIZATION_LIMIT_EXCEEDED",
COINBASE_INVALID_2FA = "COINBASE_INVALID_2FA",
ACTIVE_SWAP_LIMIT_EXCEEDED = "ACTIVE_SWAP_LIMIT_EXCEEDED",
NETWORK_ACCOUNT_ALREADY_EXISTS = "NETWORK_ACCOUNT_ALREADY_EXISTS",
BLACKLISTED_ADDRESS = "BLACKLISTED_ADDRESS",
INVALID_ADDRESS_ERROR = "INVALID_ADDRESS_ERROR",
UNACTIVATED_ADDRESS_ERROR = "UNACTIVATED_ADDRESS_ERROR"
}
+13
View File
@@ -0,0 +1,13 @@
import { type ApiError } from "./api-error";
export class EmptyApiResponse {
constructor(error?: ApiError) {
this.error = error;
}
error?: ApiError;
}
export class ApiResponse<T> extends EmptyApiResponse {
data?: T
}
@@ -0,0 +1,20 @@
// import type { Exchange } from "./Exchange";
import type { BridgeSettings } from "@/types/bridge-settings";
import type { CryptoNetwork } from "./crypto-network";
export class BridgeAppSettings {
// exchanges: Exchange[];
networks: CryptoNetwork[];
// sourceRoutes: Route[];
// destinationRoutes: Route[];
constructor(settings: BridgeSettings | any) {
this.networks = settings.networks
// this.exchanges = settings.exchanges;
// this.sourceRoutes = settings.sourceRoutes;
// this.destinationRoutes = settings.destinationRoutes;
}
}
+20
View File
@@ -0,0 +1,20 @@
type AuthGetCodeResponse = {
data: {
next: Date,
already_sent: boolean
};
error: string;
}
type AuthConnectResponse = {
access_token: string;
expires_in: number;
token_type: string;
refresh_token: string;
scope: string;
}
export {
type AuthGetCodeResponse,
type AuthConnectResponse
}
@@ -0,0 +1,6 @@
import { type CryptoNetwork } from './crypto-network'
export type BridgeSettings = {
networks: CryptoNetwork[]
}
@@ -0,0 +1,63 @@
export enum NetworkType {
EVM = "evm",
Starknet = "starknet",
Solana = "solana",
Cosmos = "cosmos",
StarkEx = "stark_ex",
ZkSyncLite = "zk_sync_lite",
TON = "ton",
Bitocoin = "btc",
Cardano = "cardano",
}
export type CryptoNetwork = {
display_name: string
internal_name: string
logo?: string
is_testnet: boolean
is_featured: boolean
chain_id: string | null | undefined
status: 'active' | 'inactive'
type: NetworkType
transaction_explorer_template: string
account_explorer_template: string
currencies: NetworkCurrency[]
metadata: Metadata | null | undefined
managed_accounts: string[]
nodes: string[]
}
export type NetworkCurrency = {
name: string
asset: string
logo: string
contract_address: string | null | undefined
decimals: number
status: string
is_deposit_enabled: boolean
is_withdrawal_enabled: boolean
is_refuel_enabled: boolean
precision: number
price_in_usd: number
is_native: boolean
}
export type NetworkNode = {
url: string
}
export type ManagedAccount = {
address: `0x${string}`
}
export type Metadata = {
multicall3?: {
address: `0x${string}`
blockCreated: number
}
ensRegistry?: {
address: `0x${string}`
}
ensUniversalResolver?: {
address: `0x${string}`
}
WatchdogContractAddress?: `0x${string}`
L1Network?: string
}
+24
View File
@@ -0,0 +1,24 @@
export enum SwapStatus {
Created = "created",
UserTransferPending = "user_transfer_pending",
UserTransferDelayed = "user_transfer_delayed",
UserDepositPending = "user_deposit_pending",
BridgeTransferPending = "bridge_transfer_pending",
Completed = "completed",
Failed = "failed",
Expired = "expired",
Cancelled = "cancelled",
TeleportProcessPending = "teleport_processing_pending",
UserPayoutPending = "user_payout_pending",
PayoutSuccess = "payout_success"
}
export const statusMapping: { [key: number]: SwapStatus } = {
"0": SwapStatus.Created,
"1": SwapStatus.UserTransferPending,
"2": SwapStatus.UserTransferDelayed,
"3": SwapStatus.BridgeTransferPending,
"4": SwapStatus.Completed,
"5": SwapStatus.Failed,
"6": SwapStatus.Expired,
"7": SwapStatus.Cancelled,
};
+243
View File
@@ -0,0 +1,243 @@
export type ThemeData = {
backdrop?: string,
actionButtonText: string,
logo: string,
placeholderText: string,
primary: ThemeColor,
secondary?: ThemeColor
}
export type ThemeColor = {
DEFAULT: string;
50: string;
100: string;
200: string;
300: string;
400: string;
500: string;
600: string;
700: string;
800: string;
900: string;
950?: string;
text: string,
textMuted?: string,
}
export const THEME_COLORS: { [key: string]: ThemeData } = {
"imxMarketplace": {
backdrop: "0, 121, 133",
actionButtonText: '0, 0, 0',
placeholderText: '140, 152, 192',
logo: '255, 255, 255',
primary: {
DEFAULT: '46, 236, 255',
'50': '230, 253, 255',
'100': '209, 251, 255',
'200': '168, 247, 255',
'300': '128, 243, 255',
'400': '87, 240, 255',
'500': '46, 236, 255',
'600': '0, 232, 255',
'700': '0, 172, 189',
'800': '0, 121, 133',
'900': '0, 70, 77',
'text': '255, 255, 255',
'textMuted': '86, 97, 123',
},
secondary: {
DEFAULT: '17, 29, 54',
'50': '49, 60, 155',
'100': '46, 59, 147',
'200': '35, 42, 112',
'300': '32, 41, 101',
'400': '28, 39, 89',
'500': '22, 37, 70',
'600': '20, 33, 62',
'700': '17, 29, 54',
'800': '15, 25, 47',
'900': '12, 21, 39',
'950': '11, 17, 35',
'text': '209, 251, 255',
},
},
"ea7df14a1597407f9f755f05e25bab42": {
backdrop: "0, 121, 133",
placeholderText: '198, 242, 246',
actionButtonText: '0, 0, 0',
logo: '255, 255, 255',
primary: {
DEFAULT: '128, 226, 235',
'50': '255, 255, 255',
'100': '255, 255, 255',
'200': '234, 250, 252',
'300': '198, 242, 246',
'400': '163, 234, 241',
'500': '128, 226, 235',
'600': '80, 215, 227',
'700': '34, 201, 217',
'800': '26, 156, 168',
'900': '19, 111, 120',
'950': '15, 89, 96',
'text': '255, 255, 255',
'textMuted': '86, 97, 123',
},
secondary: {
DEFAULT: '46, 89, 112',
'50': '193, 217, 230',
'100': '179, 208, 224',
'200': '150, 191, 212',
'300': '121, 173, 200',
'400': '92, 155, 188',
'500': '34, 66, 83',
'600': '16, 35, 49',
'700': '15, 29, 39',
'800': '34, 66, 83',
'900': '22, 43, 54',
'950': '14, 27, 34',
'text': '209, 251, 255',
}
},
"light": {
placeholderText: '134, 134, 134',
actionButtonText: '255, 255, 255',
logo: '255, 0, 147',
primary: {
DEFAULT: '228, 37, 117',
'50': '248, 200, 220',
'100': '246, 182, 209',
'200': '241, 146, 186',
'300': '237, 110, 163',
'400': '232, 73, 140',
'500': '228, 37, 117',
'600': '166, 51, 94',
'700': '136, 17, 67',
'800': '147, 8, 99',
'900': '196, 153, 175',
'text': '17, 17, 17',
'textMuted': '86, 97, 123',
},
secondary: {
DEFAULT: '240, 240, 240',
'50': '49, 60, 155',
'100': '46, 59, 147',
'200': '134, 134, 134',
'300': '139, 139, 139',
'400': '177, 177, 177',
'500': '218, 218, 218',
'600': '223, 223, 223',
'700': '240, 240, 240',
'800': '243, 244, 246',
'900': '250, 248, 248',
'950': '255, 255, 255',
'text': '108, 108, 108',
},
},
"default": {
backdrop: "62, 18, 64",
placeholderText: '140, 152, 192',
actionButtonText: '255, 255, 255',
logo: '255, 0, 147',
primary: {
DEFAULT: '228, 37, 117',
'50': '248, 200, 220',
'100': '246, 182, 209',
'200': '241, 146, 186',
'300': '237, 110, 163',
'400': '232, 73, 140',
'500': '228, 37, 117',
'600': '166, 51, 94',
'700': '136, 17, 67',
'800': '147, 8, 99',
'900': '110, 0, 64',
'text': '255, 255, 255',
'textMuted': '86, 97, 123',
},
secondary: {
DEFAULT: '17, 29, 54',
'50': '49, 60, 155',
'100': '46, 59, 147',
'200': '35, 42, 112',
'300': '32, 41, 101',
'400': '28, 39, 89',
'500': '22, 37, 70',
'600': '20, 33, 62',
'700': '17, 29, 54',
'800': '15, 25, 47',
'900': '12, 21, 39',
'950': '11, 17, 35',
'text': '171, 181, 209',
},
},
"evmos": {
placeholderText: '128, 110, 107',
actionButtonText: '255, 255, 255',
logo: '226, 49, 115',
primary: {
DEFAULT: '237, 78, 51',
'50': '248, 200, 220',
'100': '246, 182, 209',
'200': '241, 146, 186',
'300': '237, 110, 163',
'400': '232, 73, 140',
'500': '219, 211, 209',
'600': '166, 51, 94',
'700': '136, 17, 67',
'800': '147, 8, 99',
'900': '237, 78, 51',
'text': '74, 61, 59',
'textMuted': '86, 97, 123',
},
secondary: {
DEFAULT: '239, 239, 239',
'50': '49, 60, 155',
'100': '46, 59, 147',
'200': '134, 134, 134',
'300': '139, 139, 139',
'400': '177, 177, 177',
'500': '227, 227, 227',
'600': '223, 223, 223',
'700': '244, 243, 242',
'800': '248, 247, 247',
'900': '250, 248, 248',
'950': '255, 255, 255',
'text': '122, 91, 91',
},
},
"ton": {
placeholderText: '134, 134, 134',
actionButtonText: '255, 255, 255',
logo: '15, 15, 15',
primary: {
DEFAULT: '51, 144, 236',
'50': '248, 200, 220',
'100': '246, 182, 209',
'200': '241, 146, 186',
'300': '237, 110, 163',
'400': '232, 73, 140',
'500': '45, 148, 229',
'600': '166, 51, 94',
'700': '136, 17, 67',
'800': '45, 148, 229',
'900': '51, 144, 236',
'text': '15, 15, 15',
'textMuted': '86, 97, 123',
},
secondary: {
DEFAULT: '240, 240, 240',
'50': '190, 195, 200',
'100': '199, 201, 206',
'200': '208, 210, 211',
'300': '212, 214, 219',
'400': '220, 222, 226',
'500': '227, 230, 233',
'600': '229, 231, 235',
'700': '241, 243, 245',
'800': '243, 244, 246',
'900': '250, 248, 248',
'950': '255, 255, 255',
'text': '106, 119, 133',
},
}
}
@@ -0,0 +1,5 @@
export enum TransactionType {
Input = "input",
Output = "output",
Refuel = "refuel",
}
@@ -0,0 +1,21 @@
import type { BridgeSettings } from '@/types/bridge-settings'
import axios from 'axios'
// import BridgeApiClient from '@/lib/bridge-api-client'
const getBridgeSettings = async (): Promise<BridgeSettings | undefined> => {
// const apiClient = new BridgeApiClient()
// const data = await apiClient.GetSettingsAsync()
const { data } = await axios.get(
`https://api-bridge.lux.network/api/settings?version=mainnet`
)
if (data) {
return data
} else {
return undefined
}
}
export default getBridgeSettings
+9 -1
View File
@@ -19,6 +19,7 @@
"@remix-run/serve": "^2.15.1",
"@vercel/analytics": "^1.4.1",
"@vercel/remix": "^2.15.1",
"axios": "^1.7.9",
"isbot": "^4.4.0",
"lucide-react": "catalog:",
"mobx": "^6.12.3",
@@ -26,7 +27,11 @@
"mobx-utils": "^6.1.0",
"react": "catalog:",
"react-currency-input-field": "^3.9.0",
"react-dom": "catalog:"
"react-dom": "catalog:",
"react-error-boundary": "^4.0.11",
"react-toastify": "^10.0.6",
"react-use-intercom": "^2.1.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"@originjs/vite-plugin-commonjs": "^1.0.3",
@@ -34,7 +39,10 @@
"@remix-run/server-runtime": "^2.15.1",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.20",
"i": "^0.3.7",
"npm": "^11.0.0",
"postcss": "^8.4.49",
"tailwindcss": "catalog:",
"typescript": "catalog:",