From 8ca65a112b327e2a11d26be288f80d3afe2ef28b Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Sun, 14 Sep 2025 16:48:35 +0200 Subject: [PATCH] chore: add debugFeatures, routes and dev pages --- package.json | 2 +- src/App.tsx | 126 ++++++++++++++------- src/components/Footer.tsx | 15 ++- src/components/JamLanding.tsx | 27 ----- src/components/dev/DevPage.tsx | 27 +++++ src/components/dev/DevSetupPage.tsx | 114 +++++++++++++++++++ src/components/settings/FeeLimitDialog.tsx | 2 + src/constants/debugFeatures.ts | 40 +++++++ src/constants/routes.ts | 23 ++++ src/hooks/useFeatures.ts | 4 +- src/hooks/useFeeConfigValidation.ts | 2 + src/lib/config.ts | 5 +- 12 files changed, 313 insertions(+), 74 deletions(-) create mode 100644 src/components/dev/DevPage.tsx create mode 100644 src/components/dev/DevSetupPage.tsx create mode 100644 src/constants/debugFeatures.ts create mode 100644 src/constants/routes.ts diff --git a/package.json b/package.json index 845fc8a8..370a21df 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.1.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "VITE_JAM_DEV_MODE=true vite", "dev:secondary": "JAM_BACKEND=jam-standalone JAM_API_PORT=29080 npm run dev", "build": "tsc -b && vite build", "lint": "eslint .", diff --git a/src/App.tsx b/src/App.tsx index db6638f7..e812d9b4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,35 +1,42 @@ -import { useEffect, useMemo } from 'react' +import { lazy, Suspense, useEffect, useMemo } from 'react' import type { PropsWithChildren } from 'react' import { QueryClientProvider, useQuery } from '@tanstack/react-query' +import { Loader2 } from 'lucide-react' import { ThemeProvider } from 'next-themes' +import { useTranslation } from 'react-i18next' import { Navigate, Route, BrowserRouter as Router, Routes } from 'react-router-dom' import { toast } from 'sonner' import { useStore } from 'zustand' import CreateWallet from '@/components/CreateWallet' import JamLanding from '@/components/JamLanding' import LoginPage from '@/components/Login' +import { Logs } from '@/components/Logs' +import { Orderbook } from '@/components/Orderbook' import SwitchWallet from '@/components/SwitchWallet' +import { EarnPage } from '@/components/earn/EarnPage' import { Layout } from '@/components/layout/Layout' +import { Receive } from '@/components/receive/Receive' +import { SendPage } from '@/components/send/SendPage' +import { RescanChain } from '@/components/settings/RescanChain' +import { Settings } from '@/components/settings/Settings' +import { SweepPage } from '@/components/sweep/SweepPage' import { Toaster } from '@/components/ui/sonner' +import { JAM_API_AUTH_TOKEN_RENEW_INTERVAL, JAM_JM_SESSION_REFRESH_INTERVAL } from '@/constants/jam' +import { routes } from '@/constants/routes' import { useApiClient } from '@/hooks/useApiClient' import { token } from '@/lib/jm-api/generated/client' +import { sessionOptions } from '@/lib/jm-api/generated/client/@tanstack/react-query.gen' import { queryClient } from '@/lib/queryClient' import { setIntervalDebounced } from '@/lib/utils' import { authStore } from '@/store/authStore' -import { Logs } from './components/Logs' -import { Orderbook } from './components/Orderbook' -import { EarnPage } from './components/earn/EarnPage' -import { Receive } from './components/receive/Receive' -import { SendPage } from './components/send/SendPage' -import { RescanChain } from './components/settings/RescanChain' -import { Settings } from './components/settings/Settings' -import { SweepPage } from './components/sweep/SweepPage' -import { JAM_API_AUTH_TOKEN_RENEW_INTERVAL, JAM_JM_SESSION_REFRESH_INTERVAL } from './constants/jam' -import { sessionOptions } from './lib/jm-api/generated/client/@tanstack/react-query.gen' -import { jmSessionStore } from './store/jmSessionStore' +import { jmSessionStore } from '@/store/jmSessionStore' +import { isDebugFeatureEnabled, isDevMode } from './constants/debugFeatures' + +const DevSetupPage = lazy(() => import('@/components/dev/DevSetupPage')) +const DevPage = lazy(() => import('@/components/dev/DevPage')) const ProtectedRoute = ({ authenticated, children }: PropsWithChildren<{ authenticated: boolean }>) => { - return authenticated ? <>{children} : + return authenticated ? <>{children} : } function App() { @@ -47,10 +54,16 @@ function App() { - : } /> - : } /> : } + /> + : } + /> + @@ -58,7 +71,7 @@ function App() { } /> @@ -68,7 +81,7 @@ function App() { } /> @@ -78,7 +91,7 @@ function App() { } /> @@ -88,17 +101,7 @@ function App() { } /> - - - - - } - /> - @@ -108,7 +111,7 @@ function App() { } /> @@ -118,7 +121,7 @@ function App() { } /> @@ -128,7 +131,7 @@ function App() { } /> @@ -138,7 +141,7 @@ function App() { } /> @@ -147,7 +150,40 @@ function App() { } /> - } /> + + + + + + } + /> + {isDebugFeatureEnabled('devPage') && ( + + + + } + /> + )} + {isDebugFeatureEnabled('devSetupPage') && ( + + }> + + + + } + /> + )} + } /> @@ -156,12 +192,22 @@ function App() { ) } +const Loading = () => { + const { t } = useTranslation() + return ( +
+ + {t('global.loading')} +
+ ) +} + function RefreshApiToken() { const client = useApiClient() // TODO: stop this interval if no wallet is active useEffect(() => { - if (import.meta.env.DEV) { + if (isDevMode()) { toast.info(`[DEV] setup refresh interval`) } @@ -182,7 +228,7 @@ function RefreshApiToken() { if (!response.data) { authStore.getState().clear() - if (import.meta.env.DEV) { + if (isDevMode()) { const message = response.error?.message || response.error?.error_description || 'Unknown error.' toast.error(`[DEV] Error while renewing auth token: ${message}`) } @@ -194,7 +240,7 @@ function RefreshApiToken() { }, }) - if (import.meta.env.DEV) { + if (isDevMode()) { toast.info(`[DEV] Successfully renewed auth token.`, { id: 'token-renew-success', }) @@ -227,7 +273,7 @@ function RefreshJmSession() { if (sessionQuery.data) { jmSessionStore.getState().update(sessionQuery.data) - if (import.meta.env.DEV) { + if (isDevMode()) { toast.info(`[DEV] Successfully refreshed session data.`, { id: 'jm-session-refresh-success', }) @@ -238,7 +284,7 @@ function RefreshJmSession() { useEffect(() => { if (authState === undefined) { sessionQuery.refetch().catch(() => { - if (import.meta.env.DEV) { + if (isDevMode()) { toast.error(`[DEV] Error while refreshing session data.`) } }) diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index d02bf2a1..77f61d2d 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -3,11 +3,13 @@ import { BlocksIcon, BookOpen, Check, File, X } from 'lucide-react' import { useTranslation, Trans } from 'react-i18next' import { Link } from 'react-router-dom' import { useStore } from 'zustand' +import { OrderbookDialog } from '@/components/OrderbookDialog' import { Button } from '@/components/ui/button' +import { isDebugFeatureEnabled } from '@/constants/debugFeatures' +import { routes } from '@/constants/routes' import { toSemVer } from '@/lib/utils' import { jmSessionStore } from '@/store/jmSessionStore' import packageInfo from '../../package.json' -import { OrderbookDialog } from './OrderbookDialog' const APP_DISPLAY_VERSION = (() => { const version = toSemVer(packageInfo.version) @@ -39,7 +41,16 @@ export function Footer() { -
+ - {/* Fee Config Error Alert */} - {maxFeesConfigMissing && ( - setShowFeeConfigDialog(true)} className="mb-4 max-w-2xl" /> - )} - {error && ( @@ -131,18 +116,6 @@ export default function JamLanding() { {t('global.refresh')}
- - {import.meta.env.DEV && ( -
- -
- )} - - ) } diff --git a/src/components/dev/DevPage.tsx b/src/components/dev/DevPage.tsx new file mode 100644 index 00000000..c4b6a90e --- /dev/null +++ b/src/components/dev/DevPage.tsx @@ -0,0 +1,27 @@ +import { useState } from 'react' +import { useStore } from 'zustand' +import { FeeLimitDialog } from '@/components/settings/FeeLimitDialog' +import { FeeConfigTestComponent } from '@/components/ui/FeeConfigTestComponent' +import { authStore } from '@/store/authStore' + +export default function DevPage() { + const authState = useStore(authStore, (state) => state.state) + const [showFeeConfigDialog, setShowFeeConfigDialog] = useState(false) + + return ( +
+

Development

+

Development specific information

+ +
+ + + +
+
+ ) +} diff --git a/src/components/dev/DevSetupPage.tsx b/src/components/dev/DevSetupPage.tsx new file mode 100644 index 00000000..f34f462d --- /dev/null +++ b/src/components/dev/DevSetupPage.tsx @@ -0,0 +1,114 @@ +import { BlocksIcon, TerminalIcon } from 'lucide-react' +import { Badge } from '../ui/badge' + +const DEFAULT_BASIC_AUTH = { + user: 'joinmarket', + password: 'joinmarket', +} + +const LINK_JM_REGTEST_JOINMARKET2 = 'http://localhost:29080' +const LINK_JM_REGTEST_JOINMARKET2_AUTH = DEFAULT_BASIC_AUTH +const LINK_JM_REGTEST_JOINMARKET3 = 'http://localhost:30080' +const LINK_JM_REGTEST_EXPLORER = 'http://localhost:3002' +const LINK_JM_REGTEST_EXPLORER_AUTH = DEFAULT_BASIC_AUTH +const LINK_JM_REGTEST_RPC_TERMINAL = `${LINK_JM_REGTEST_EXPLORER}/rpc-terminal` + +export default function DevSetupPage() { + return ( +
+

Development setup

+ +
+
+
Test Wallet
+
+ Name: Satoshi +
+ Password: test +
+
+
+ +
+
+
Links
+
+ {/* + Error Example Page + */} +
+
+
+ +
+
+
Jam Instances
+
+ + +
+ Basic Authentication +
+ + User: {LINK_JM_REGTEST_JOINMARKET2_AUTH.user} +
+ Password: {LINK_JM_REGTEST_JOINMARKET2_AUTH.password} +
+
+ +
+
+
+ +
+
+
Block Explorer
+
+ {' '} + +
+ Basic Authentication +
+ + User: {LINK_JM_REGTEST_EXPLORER_AUTH.user} +
+ Password: {LINK_JM_REGTEST_EXPLORER_AUTH.password} +
+
+
+
+ +
+ Mine a block, e.g.: +
generatetoaddress 1 bcrt1qrnz0thqslhxu86th069r9j6y7ldkgs2tzgf5wx
+
+
+
+
+
+ ) +} diff --git a/src/components/settings/FeeLimitDialog.tsx b/src/components/settings/FeeLimitDialog.tsx index c90cb9e7..e7f7c067 100644 --- a/src/components/settings/FeeLimitDialog.tsx +++ b/src/components/settings/FeeLimitDialog.tsx @@ -22,6 +22,8 @@ import { DevBadge } from '../ui/DevBadge' import { CollaboratorFeesForm, type CollaboratorFeesFormRef } from './CollaboratorFeesForm' import { MiningFeesForm, type MiningFeesFormRef } from './MiningFeesForm' +//TODO: needs testing! + interface FeeLimitDialogProps { open: boolean onOpenChange: (open: boolean) => void diff --git a/src/constants/debugFeatures.ts b/src/constants/debugFeatures.ts new file mode 100644 index 00000000..f6528f12 --- /dev/null +++ b/src/constants/debugFeatures.ts @@ -0,0 +1,40 @@ +interface DebugFeatures { + insecureScheduleTesting: boolean + allowCreatingExpiredFidelityBond: boolean + skipWalletBackupConfirmation: boolean + errorExamplePage: boolean + devPage: boolean + devSetupPage: boolean + importDummyMnemonicPhrase: boolean + allowFeeValuesReset: boolean + enableDemoEarnReport: boolean + enableDemoOrderbook: boolean +} + +const devMode = import.meta.env.DEV && import.meta.env.VITE_JAM_DEV_MODE === 'true' + +const debugFeatures: DebugFeatures = { + allowCreatingExpiredFidelityBond: devMode, + insecureScheduleTesting: devMode, + skipWalletBackupConfirmation: devMode, + errorExamplePage: devMode, + devPage: devMode, + devSetupPage: devMode, + importDummyMnemonicPhrase: devMode, + allowFeeValuesReset: devMode, + enableDemoEarnReport: devMode, + enableDemoOrderbook: devMode, +} + +type DebugFeature = keyof DebugFeatures + +export const isDevMode = (): boolean => devMode + +export const isDebugFeatureEnabled = (name: DebugFeature): boolean => { + return debugFeatures[name] || false +} + +// only to be used in tests +export const __testSetDebugFeatureEnabled = (name: DebugFeature, enabled: boolean): boolean => { + return (debugFeatures[name] = enabled) +} diff --git a/src/constants/routes.ts b/src/constants/routes.ts new file mode 100644 index 00000000..503966bc --- /dev/null +++ b/src/constants/routes.ts @@ -0,0 +1,23 @@ +export const routes = { + home: '/', + login: '/login', + createWallet: '/create-wallet', + switchWallet: '/switch-wallet', + receive: '/receive', + send: '/send', + rescan: '/rescan', + earn: '/earn', + sweep: '/sweep', + settings: '/settings', + orderbook: '/orderbook', + logs: '/logs', + /* walletList: '/', + wallet: '/wallet', + importWallet: '/import-wallet', + */ + __dev: '/dev', + //__devErrorExample: '/dev/error-example', + __devSetup: '/dev/setup', +} + +export type Route = keyof typeof routes diff --git a/src/hooks/useFeatures.ts b/src/hooks/useFeatures.ts index 07596524..72ef5f34 100644 --- a/src/hooks/useFeatures.ts +++ b/src/hooks/useFeatures.ts @@ -1,5 +1,6 @@ import { useQuery } from '@tanstack/react-query' import { useStore } from 'zustand' +import { isDevMode } from '@/constants/debugFeatures' import { fetchFeatures } from '@/lib/api/logs' import { authStore } from '@/store/authStore' @@ -45,7 +46,8 @@ export const useFeatures = () => { const isLogsEnabled = () => { if (error) { - return import.meta.env.DEV + // show regardless of error in development mode + return isDevMode() } if (features) { diff --git a/src/hooks/useFeeConfigValidation.ts b/src/hooks/useFeeConfigValidation.ts index 5bd28171..a2e95f13 100644 --- a/src/hooks/useFeeConfigValidation.ts +++ b/src/hooks/useFeeConfigValidation.ts @@ -6,6 +6,8 @@ import { useApiClient } from '@/hooks/useApiClient' import { configgetMutation } from '@/lib/jm-api/generated/client/@tanstack/react-query.gen' import { authStore } from '@/store/authStore' +//TODO: needs testing! + export interface FeeConfigValues { max_cj_fee_abs?: string max_cj_fee_rel?: string diff --git a/src/lib/config.ts b/src/lib/config.ts index 51db1374..97769476 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,3 +1,4 @@ +import { isDevMode } from '@/constants/debugFeatures' import { createClient } from '@/lib/jm-api' import type { ClientOptions, UnlockWalletResponse } from '@/lib/jm-api/generated/client' import { authStore } from '@/store/authStore' @@ -39,9 +40,7 @@ export const createApiClient = (): Client => { const jamAuthMiddleware = createJamAuthenticationMiddleware() client.interceptors.request.use(jamAuthMiddleware) - const isDevelopment = import.meta.env.DEV - - if (isDevelopment) { + if (isDevMode()) { client.interceptors.request.use(loggingRequestInterceptor) client.interceptors.response.use(loggingResponseInterceptor) }