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() {
-
+
+
+ {isDebugFeatureEnabled('devSetupPage') && (
+
+
+ Dev Setup
+
+
+ )}
+
- {/* 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)
}