From eae410304bf982c17f4e20f1a2d208b6c45bf25f Mon Sep 17 00:00:00 2001 From: IzioDev <9900846+IzioDev@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:33:14 +0200 Subject: [PATCH 01/81] chore(web): vite pwa cache policy tweak --- vite.config.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/vite.config.ts b/vite.config.ts index cc084644..8a43e723 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -78,6 +78,34 @@ const webPlugins: PluginOption[] = [ workbox: { // 14 mb maximumFileSizeToCacheInBytes: 14000000, + cleanupOutdatedCaches: true, + skipWaiting: true, + clientsClaim: true, + runtimeCaching: [ + // Navigations (index.html) + { + urlPattern: ({ request, sameOrigin }) => + sameOrigin && request.mode === "navigate", + handler: "NetworkFirst", + options: { + cacheName: "html-pages", + networkTimeoutSeconds: 5, + }, + }, + // JS/CSS/Workers + { + urlPattern: ({ request }) => + ["script", "style", "worker"].includes(request.destination), + handler: "StaleWhileRevalidate", + options: { cacheName: "assets" }, + }, + // Images + { + urlPattern: ({ request }) => request.destination === "image", + handler: "StaleWhileRevalidate", + options: { cacheName: "images" }, + }, + ], }, }), ]; From 556c94ea3e791a4fc5775a0b7c441fe17d96a9c9 Mon Sep 17 00:00:00 2001 From: HocusLocust <182385655+HocusLocusTee@users.noreply.github.com> Date: Sat, 11 Oct 2025 08:00:27 +0300 Subject: [PATCH 02/81] style: new toast styling --- src/components/Common/ToastContainer.tsx | 125 +++++++++++++++++------ src/index.css | 12 +++ src/store/toast.store.ts | 12 +-- 3 files changed, 110 insertions(+), 39 deletions(-) diff --git a/src/components/Common/ToastContainer.tsx b/src/components/Common/ToastContainer.tsx index dfc667b1..36c4a900 100644 --- a/src/components/Common/ToastContainer.tsx +++ b/src/components/Common/ToastContainer.tsx @@ -1,43 +1,106 @@ import { useToastStore } from "../../store/toast.store"; -import { CircleCheck, Ban, TriangleAlert, Info } from "lucide-react"; +import { CircleCheck, Ban, TriangleAlert, Info, XIcon } from "lucide-react"; +import { useIsMobile } from "../../hooks/useIsMobile"; +import { ToastType } from "../../store/toast.store"; import clsx from "clsx"; export function ToastContainer() { const { toasts, remove } = useToastStore(); + const isMobile = useIsMobile(); + + const getToastTransform = (offset: number) => { + if (Math.abs(offset) > 2) return { opacity: 0 }; + + const absOffset = Math.abs(offset); + // spread them out a little futher on desktop + const translateY = -offset * (12 * (isMobile ? 1.5 : 2)); + const scale = 1 - absOffset * 0.05; + const opacity = Math.max(0.4, 1 - absOffset * 0.2); + + return { + transform: `translateY(${translateY}px) scale(${scale})`, + opacity, + }; + }; + + const getToastTypeClasses = (type: ToastType) => { + switch (type) { + case "success": + return "bg-[var(--accent-green)]/80 text-white"; + case "error": + return "bg-[var(--accent-red)]/80 text-white"; + case "warning": + return "bg-[var(--accent-yellow)]/80 text-white"; + case "info": + return "bg-[var(--accent-blue)]/80 text-white"; + } + }; + + const getToastIcon = (type: ToastType) => { + switch (type) { + case "success": + return ; + case "error": + return ; + case "warning": + return ; + case "info": + return ; + } + }; + + // if no toasts, return + if (toasts.length === 0) { + return null; + } + return ( -
- {toasts.map((toast) => ( -
- - {toast.type === "success" && } - {toast.type === "error" && } - {toast.type === "warning" && } - {toast.type === "info" && } - - {toast.message} - -
- ))} + {/* toast icon */} + + {getToastIcon(toast.type)} + + + {/* message content */} + {toast.message} + + {/* close buttons*/} + +
+ ); + })} ); } diff --git a/src/index.css b/src/index.css index 5ab1a3c8..ef9f072c 100644 --- a/src/index.css +++ b/src/index.css @@ -115,6 +115,18 @@ input[type="password"]::-webkit-reveal-button { } } +@keyframes slideInFromRight { + from { + opacity: 0; + transform: translateX(40%); + } + + to { + opacity: 1; + transform: translateX(0); + } +} + @layer utilities { /* extra helper classes to account for mobile safe areas */ .p-safe { diff --git a/src/store/toast.store.ts b/src/store/toast.store.ts index 3a21ede4..f30c03cf 100644 --- a/src/store/toast.store.ts +++ b/src/store/toast.store.ts @@ -9,12 +9,7 @@ export type Toast = { message: string; }; -const DEFAULT_TIMEOUTS: Record = { - success: 5000, - error: 10000, - info: 5000, - warning: 5000, -}; +const DEFAULT_TIMEOUT: number = 5000; type ToastStore = { toasts: Toast[]; @@ -36,9 +31,10 @@ export const useToastStore = create((set) => ({ add: (type, message, timeout) => { const id = generateId(); const newToast = { id, type, message }; - const effectiveTimeout = timeout ?? DEFAULT_TIMEOUTS[type]; + const effectiveTimeout = timeout ?? DEFAULT_TIMEOUT; - set((state) => ({ toasts: [...state.toasts, newToast] })); + // add new toast at the front + set((state) => ({ toasts: [newToast, ...state.toasts] })); // store the timeout ID so we can cancel it later const timeoutId = setTimeout(() => { From f426966726b006036a040de29366dbe99dba5fab Mon Sep 17 00:00:00 2001 From: HocusLocust <182385655+HocusLocusTee@users.noreply.github.com> Date: Sun, 12 Oct 2025 03:32:59 +0300 Subject: [PATCH 03/81] feat: add donations to settings - refactor component a tad --- src/components/Common/Donations.tsx | 30 ++++++++++++++++++++++--- src/components/Modals/SettingsModal.tsx | 3 +++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/components/Common/Donations.tsx b/src/components/Common/Donations.tsx index 96892910..ce6a4952 100644 --- a/src/components/Common/Donations.tsx +++ b/src/components/Common/Donations.tsx @@ -4,14 +4,38 @@ import type { ModalType } from "../../store/ui.store"; interface DonationsProps { closeModalKey?: ModalType; + position?: "top-left" | "top-right" | "bottom-left" | "bottom-right"; + onClick?: () => void; } -export const Donations = ({ closeModalKey }: DonationsProps) => { +export const Donations = ({ + closeModalKey, + position = "top-left", + onClick, +}: DonationsProps) => { const openModal = useUiStore((state) => state.openModal); const closeModal = useUiStore((state) => state.closeModal); + const getPositionClasses = () => { + const baseClasses = "fixed z-50"; + switch (position) { + case "top-left": + return `${baseClasses} top-2 left-2`; + case "top-right": + return `${baseClasses} top-2 right-2`; + case "bottom-left": + return `${baseClasses} bottom-2 left-2`; + case "bottom-right": + return `${baseClasses} bottom-2 right-2`; + default: + return `${baseClasses} top-2 left-2`; + } + }; + const handleClick = () => { - if (closeModalKey) { + if (onClick) { + onClick(); + } else if (closeModalKey) { closeModal(closeModalKey); } openModal("donation"); @@ -20,7 +44,7 @@ export const Donations = ({ closeModalKey }: DonationsProps) => { return ( +

Send Payment From f5261e5e284bf000a56384d6177079668b7b4dce Mon Sep 17 00:00:00 2001 From: HocusLocust <182385655+HocusLocusTee@users.noreply.github.com> Date: Mon, 13 Oct 2025 10:10:57 +0300 Subject: [PATCH 12/81] style: remove toast clear on change route --- src/components/Layout/RootLayout.tsx | 5 ----- src/components/ModeSelector.tsx | 4 +--- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/components/Layout/RootLayout.tsx b/src/components/Layout/RootLayout.tsx index 2b1d7365..5b9d40e8 100644 --- a/src/components/Layout/RootLayout.tsx +++ b/src/components/Layout/RootLayout.tsx @@ -29,11 +29,6 @@ export const RootLayout: FC = () => { navigate("/"); }; - // when navigation changes, remove ALL toast notifications - useEffect(() => { - toast.removeAll(); - }, [location]); - return ( <> diff --git a/src/components/ModeSelector.tsx b/src/components/ModeSelector.tsx index 4f9b8075..778fe2f0 100644 --- a/src/components/ModeSelector.tsx +++ b/src/components/ModeSelector.tsx @@ -47,9 +47,7 @@ export const ModeSelector: FC = ({ const selectedMode = modes.find((mode) => mode.value === newMode); onModeChange(newMode); - setTimeout(() => { - toast.info(`Switched to ${selectedMode?.displayableString}`); - }, 100); + toast.info(`Switched to ${selectedMode?.displayableString}`); }; return ( From 26c7637cf402424d57f999a167035e4a477ce8b5 Mon Sep 17 00:00:00 2001 From: HocusLocust <182385655+HocusLocusTee@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:02:07 +0300 Subject: [PATCH 13/81] fix: misc - remove some redundnant imports - reduce toast duration on mode switch - remove some dodgy animation delays in hopes to make the holdable + more responsive --- src/components/Common/HoldToAction.tsx | 6 +----- src/components/Layout/RootLayout.tsx | 6 ++---- src/components/ModeSelector.tsx | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/components/Common/HoldToAction.tsx b/src/components/Common/HoldToAction.tsx index 2e20501f..ae2e94db 100644 --- a/src/components/Common/HoldToAction.tsx +++ b/src/components/Common/HoldToAction.tsx @@ -132,11 +132,7 @@ export const HoldToAction: FC = ({ releaseEvent.stopPropagation(); // only trigger short press if animation never started and not completed - if ( - !completedRef.current && - !animationStartedRef.current && - animationDelay > 0 - ) { + if (!completedRef.current && !animationStartedRef.current) { onShortPress?.(); } diff --git a/src/components/Layout/RootLayout.tsx b/src/components/Layout/RootLayout.tsx index 5b9d40e8..a1d8f894 100644 --- a/src/components/Layout/RootLayout.tsx +++ b/src/components/Layout/RootLayout.tsx @@ -1,5 +1,5 @@ -import { FC, useEffect } from "react"; -import { Outlet, useNavigate, useLocation } from "react-router"; +import { FC } from "react"; +import { Outlet, useNavigate } from "react-router"; import { useWalletStore } from "../../store/wallet.store"; import { useIsMobile } from "../../hooks/useIsMobile"; import { Header } from "../Layout/Header"; @@ -7,7 +7,6 @@ import { SlideOutMenu } from "../Layout/SlideOutMenu"; import { ToastContainer } from "../Common/ToastContainer"; import { useUiStore } from "../../store/ui.store"; -import { toast } from "../../utils/toast-helper"; import { ResizableAppContainer } from "./ResizableAppContainer"; import { ModalHost } from "./ModalHost"; @@ -19,7 +18,6 @@ export const RootLayout: FC = () => { const isMobile = useIsMobile(); const navigate = useNavigate(); - const location = useLocation(); const handleCloseWallet = () => { // close settings panel diff --git a/src/components/ModeSelector.tsx b/src/components/ModeSelector.tsx index 778fe2f0..634390d7 100644 --- a/src/components/ModeSelector.tsx +++ b/src/components/ModeSelector.tsx @@ -47,7 +47,7 @@ export const ModeSelector: FC = ({ const selectedMode = modes.find((mode) => mode.value === newMode); onModeChange(newMode); - toast.info(`Switched to ${selectedMode?.displayableString}`); + toast.info(`Switched to ${selectedMode?.displayableString}`, 1200); }; return ( From ccca67aaced86e9ef89b47c94d9677171b59d5cc Mon Sep 17 00:00:00 2001 From: HocusLocust <182385655+HocusLocusTee@users.noreply.github.com> Date: Tue, 14 Oct 2025 07:38:16 +0300 Subject: [PATCH 14/81] feat: add initial connection component --- src/components/Common/ConnectionIndicator.tsx | 33 +++++++++++++++++++ src/components/Layout/Header.tsx | 2 ++ 2 files changed, 35 insertions(+) create mode 100644 src/components/Common/ConnectionIndicator.tsx diff --git a/src/components/Common/ConnectionIndicator.tsx b/src/components/Common/ConnectionIndicator.tsx new file mode 100644 index 00000000..8fddf5d4 --- /dev/null +++ b/src/components/Common/ConnectionIndicator.tsx @@ -0,0 +1,33 @@ +import { useEffect, useState } from "react"; +import { useNetworkStore } from "../../store/network.store"; +import { WifiOff, Wifi } from "lucide-react"; +import clsx from "clsx"; + +export const ConnectionIndicator = () => { + const networkStore = useNetworkStore(); + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + if (networkStore.isConnected) { + setIsVisible(true); + // hide after 5 seconds + const timer = setTimeout(() => setIsVisible(false), 5000); + return () => clearTimeout(timer); + } else { + setIsVisible(false); + } + }, [networkStore.isConnected]); + + // show red wifi-off icon when disconnected + if (!networkStore.isConnected) { + return ; + } + + // show green wifi icon when connected and within fade period, otherwise nothing + return isVisible ? ( + + ) : null; +}; diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx index 18bee85c..1405d1db 100644 --- a/src/components/Layout/Header.tsx +++ b/src/components/Layout/Header.tsx @@ -1,6 +1,7 @@ import { FC } from "react"; import { ThemeToggle } from "../Common/ThemeToggle"; import { useNavigate } from "react-router"; +import { ConnectionIndicator } from "../Common/ConnectionIndicator"; type Props = { isWalletReady: boolean; @@ -27,6 +28,7 @@ export const Header: FC = () => {

+
From 605caf2864571a5ffb2412a840b30fa15609a400 Mon Sep 17 00:00:00 2001 From: HocusLocust <182385655+HocusLocusTee@users.noreply.github.com> Date: Tue, 14 Oct 2025 12:35:17 +0300 Subject: [PATCH 15/81] feat: finish connection indicator and finalize --- src/components/Common/ConnectionIndicator.tsx | 60 ++++++++++++++++--- src/components/Layout/RootLayout.tsx | 16 +++-- src/components/Layout/SlideOutMenu.tsx | 1 + src/index.css | 12 ++++ 4 files changed, 76 insertions(+), 13 deletions(-) diff --git a/src/components/Common/ConnectionIndicator.tsx b/src/components/Common/ConnectionIndicator.tsx index 8fddf5d4..28896fd4 100644 --- a/src/components/Common/ConnectionIndicator.tsx +++ b/src/components/Common/ConnectionIndicator.tsx @@ -1,33 +1,77 @@ import { useEffect, useState } from "react"; import { useNetworkStore } from "../../store/network.store"; +import { useWalletStore } from "../../store/wallet.store"; +import { useMessagingStore } from "../../store/messaging.store"; import { WifiOff, Wifi } from "lucide-react"; import clsx from "clsx"; +const CONNECTED_FADE_TIME = 5000; + export const ConnectionIndicator = () => { const networkStore = useNetworkStore(); + const walletStore = useWalletStore(); + const messagingStore = useMessagingStore(); const [isVisible, setIsVisible] = useState(false); + const [isReady, setIsReady] = useState(false); + + const isWalletReady = walletStore.unlockedWallet && messagingStore.isLoaded; + + // delay showing the indicator after wallet unlocks + useEffect(() => { + if (isWalletReady) { + const readyTimer = setTimeout( + () => setIsReady(true), + CONNECTED_FADE_TIME + 1000 + ); + return () => clearTimeout(readyTimer); + } else { + setIsReady(false); + } + }, [isWalletReady]); + // when connected, hide after some time useEffect(() => { - if (networkStore.isConnected) { + if (networkStore.isConnected && isReady) { setIsVisible(true); - // hide after 5 seconds - const timer = setTimeout(() => setIsVisible(false), 5000); + + const timer = setTimeout(() => setIsVisible(false), CONNECTED_FADE_TIME); return () => clearTimeout(timer); } else { setIsVisible(false); } - }, [networkStore.isConnected]); + }, [networkStore.isConnected, isReady]); + + // don't use until unlocked and loaded, and ready period has passed + if (!isWalletReady || !isReady) return null; + + // show yellow wifi-off icon when reconnecting + if (!networkStore.isConnected && networkStore.isConnecting) { + return ( + + ); + } - // show red wifi-off icon when disconnected + // show red wifi-off icon when disconnected and not reconnecting if (!networkStore.isConnected) { - return ; + return ( + + ); } // show green wifi icon when connected and within fade period, otherwise nothing return isVisible ? ( ) : null; }; diff --git a/src/components/Layout/RootLayout.tsx b/src/components/Layout/RootLayout.tsx index a1d8f894..71941a0f 100644 --- a/src/components/Layout/RootLayout.tsx +++ b/src/components/Layout/RootLayout.tsx @@ -9,6 +9,7 @@ import { ToastContainer } from "../Common/ToastContainer"; import { useUiStore } from "../../store/ui.store"; import { ResizableAppContainer } from "./ResizableAppContainer"; import { ModalHost } from "./ModalHost"; +import { ConnectionIndicator } from "../Common/ConnectionIndicator"; export const RootLayout: FC = () => { const walletStore = useWalletStore(); @@ -43,11 +44,16 @@ export const RootLayout: FC = () => { {/* mobile drawer */} {isMobile && ( - + <> +
+ +
+ + )} diff --git a/src/components/Layout/SlideOutMenu.tsx b/src/components/Layout/SlideOutMenu.tsx index 4fc9a0db..08750a49 100644 --- a/src/components/Layout/SlideOutMenu.tsx +++ b/src/components/Layout/SlideOutMenu.tsx @@ -4,6 +4,7 @@ import { RefreshCcw, User, ArrowLeft, Wallet, X } from "lucide-react"; import { Settings } from "lucide-react"; import { useUiStore } from "../../store/ui.store"; import { SettingsModal } from "../Modals/SettingsModal"; +import { ConnectionIndicator } from "../Common/ConnectionIndicator"; type SlideOutMenuProps = { address?: string; diff --git a/src/index.css b/src/index.css index 1fd729fa..fca00fcb 100644 --- a/src/index.css +++ b/src/index.css @@ -82,6 +82,18 @@ transform: translateX(0); } } + + --animate-fade-out-5s: fadeOut5s 5s ease-in-out forwards; + + @keyframes fadeOut5s { + from { + opacity: 1; + } + + to { + opacity: 0; + } + } } /* we need this to assist with splash > loaded app transition. Its our css limit */ From ec75310599608ef2637521c323fc83e58c0124d5 Mon Sep 17 00:00:00 2001 From: IzioDev <9900846+IzioDev@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:54:15 +0200 Subject: [PATCH 16/81] review: cache svg and png --- public/_headers | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/_headers b/public/_headers index afb43aaf..5e576f21 100644 --- a/public/_headers +++ b/public/_headers @@ -24,3 +24,9 @@ /workbox-*.js Cache-Control: no-cache + +/*.png + Cache-Control: public, max-age=604800 + +/*.svg + Cache-Control: public, max-age=604800 From 672aa399869cd79618d33c931e73ebbc0a040e2b Mon Sep 17 00:00:00 2001 From: IzioDev <9900846+IzioDev@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:59:03 +0200 Subject: [PATCH 17/81] review: reset metadata on wallet flush --- scripts/run-vite-with-env.cjs | 2 +- src/store/messaging.store.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/run-vite-with-env.cjs b/scripts/run-vite-with-env.cjs index 9475220f..7f307bf6 100644 --- a/scripts/run-vite-with-env.cjs +++ b/scripts/run-vite-with-env.cjs @@ -39,7 +39,7 @@ let fullCommand = ""; switch (command) { case "dev": - fullCommand = `vite --debug ${viteCommand}`; + fullCommand = `vite ${viteCommand}`; break; case "build:production": fullCommand = `tsc -b && vite build ${viteCommand}`; diff --git a/src/store/messaging.store.ts b/src/store/messaging.store.ts index 45fe7929..8b014df8 100644 --- a/src/store/messaging.store.ts +++ b/src/store/messaging.store.ts @@ -471,9 +471,6 @@ export const useMessagingStore = create((set, g) => { const oneOnOneConversations = await Promise.all( oneOnOneConversationPromises ); - console.trace("oooc hydrated", { - oneOnOneConversations, - }); const filteredConversations = oneOnOneConversations.filter( (c) => c !== null ); @@ -959,14 +956,17 @@ export const useMessagingStore = create((set, g) => { repositories.savedHandshakeRepository.deleteTenant(walletTenant), ]); - // 3. Reset all UI state immediately + // 3. Reset metadata + repositories.metadataRepository.erase(); + + // 4. Reset all UI state immediately set({ oneOnOneConversations: [], openedRecipient: null, isCreatingNewChat: false, }); - // 4. Clear and reinitialize conversation manager + // 5. Clear and reinitialize conversation manager const manager = g().conversationManager; if (manager) { // Reinitialize fresh conversation manager From 60990209e6c5a5f421203fdfffc4b9f07607e410 Mon Sep 17 00:00:00 2001 From: HocusLocust <182385655+HocusLocusTee@users.noreply.github.com> Date: Sun, 19 Oct 2025 07:59:11 +0300 Subject: [PATCH 18/81] feat: delete env indexer settings + add indexer utility --- .env.production | 1 - .env.staging | 1 - src/utils/indexer-settings.ts | 28 ++++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 src/utils/indexer-settings.ts diff --git a/.env.production b/.env.production index 75f42797..aac761d3 100644 --- a/.env.production +++ b/.env.production @@ -8,7 +8,6 @@ VITE_DEV_MODE=false VITE_INDEXER_MAINNET_URL=https://indexer.kasia.fyi/ VITE_INDEXER_TESTNET_URL=https://dev-indexer.kasia.fyi/ -VITE_DISABLE_INDEXER=false VITE_DEFAULT_MAINNET_KASPA_NODE_URL=wss://wrpc.kasia.fyi VITE_DEFAULT_TESTNET_KASPA_NODE_URL=wss://dev-wrpc.kasia.fyi \ No newline at end of file diff --git a/.env.staging b/.env.staging index c06cb09f..c5529d79 100644 --- a/.env.staging +++ b/.env.staging @@ -7,7 +7,6 @@ VITE_LOG_LEVEL=info VITE_INDEXER_MAINNET_URL=https://indexer.kasia.fyi/ VITE_INDEXER_TESTNET_URL=https://dev-indexer.kasia.fyi/ -VITE_DISABLE_INDEXER=false VITE_DEFAULT_MAINNET_KASPA_NODE_URL=wss://wrpc.kasia.fyi VITE_DEFAULT_TESTNET_KASPA_NODE_URL=wss://dev-wrpc.kasia.fyi \ No newline at end of file diff --git a/src/utils/indexer-settings.ts b/src/utils/indexer-settings.ts new file mode 100644 index 00000000..d2eb40ce --- /dev/null +++ b/src/utils/indexer-settings.ts @@ -0,0 +1,28 @@ +const INDEXER_DISABLED_KEY = "kasia_indexer_disabled"; +const INDEXER_URL_KEY = "kasia_indexer_url"; + +// gets whether the indexer is disabled. +// checks localStorage. Defaults to false (enabled) if no value is set. +export function isIndexerDisabled(): boolean { + const localStorageValue = localStorage.getItem(INDEXER_DISABLED_KEY); + return localStorageValue === "true"; +} + +// sets the indexer disabled setting in localStorage. +export function setIndexerDisabled(disabled: boolean): void { + localStorage.setItem(INDEXER_DISABLED_KEY, disabled.toString()); +} + +// gets the indexer URL from localStorage. +export function getIndexerUrl(): string | null { + return localStorage.getItem(INDEXER_URL_KEY); +} + +// Sets the indexer URL in localStorage. +export function setIndexerUrl(url: string | null): void { + if (url) { + localStorage.setItem(INDEXER_URL_KEY, url); + } else { + localStorage.removeItem(INDEXER_URL_KEY); + } +} From bf209c6aec514b3ff5421520b91a9b863c7ec532 Mon Sep 17 00:00:00 2001 From: HocusLocust <182385655+HocusLocusTee@users.noreply.github.com> Date: Sun, 19 Oct 2025 07:59:46 +0300 Subject: [PATCH 19/81] chore: update historical syncer --- src/service/historical-syncer.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/service/historical-syncer.ts b/src/service/historical-syncer.ts index 7fcaad53..04d44380 100644 --- a/src/service/historical-syncer.ts +++ b/src/service/historical-syncer.ts @@ -6,12 +6,15 @@ import { HandshakeResponse, SelfStashResponse, } from "./indexer/generated"; +import { isIndexerDisabled } from "../utils/indexer-settings"; /** * Handles historical events */ export class HistoricalSyncer { - private DISABLED = import.meta.env.VITE_DISABLE_INDEXER === "true"; + private get DISABLED(): boolean { + return isIndexerDisabled(); + } constructor(readonly address: string) {} From 512bb3f157e4064b44ca1b5a0309df232f7ef322 Mon Sep 17 00:00:00 2001 From: HocusLocust <182385655+HocusLocusTee@users.noreply.github.com> Date: Sun, 19 Oct 2025 08:38:20 +0300 Subject: [PATCH 20/81] style: settings flow and styling --- src/components/Modals/LockedSettingsModal.tsx | 412 ++++++++++++++---- src/types/all.ts | 2 + 2 files changed, 338 insertions(+), 76 deletions(-) diff --git a/src/components/Modals/LockedSettingsModal.tsx b/src/components/Modals/LockedSettingsModal.tsx index c177c0c0..8e3e9eed 100644 --- a/src/components/Modals/LockedSettingsModal.tsx +++ b/src/components/Modals/LockedSettingsModal.tsx @@ -1,17 +1,24 @@ import { useState } from "react"; import { NetworkSelector } from "../NetworkSelector"; import { useNetworkStore } from "../../store/network.store"; -import { NetworkType } from "../../types/all"; +import { NetworkType, ConnectionMode } from "../../types/all"; import { Button } from "../Common/Button"; import { useUiStore } from "../../store/ui.store"; import { ThemeToggle } from "../Common/ThemeToggle"; -import { Shield } from "lucide-react"; +import { Shield, ArrowLeft, Coffee, Check } from "lucide-react"; +import { Checkbox } from "@headlessui/react"; import { devMode } from "../../config/dev-mode"; import { deleteDB } from "idb"; import { useDBStore } from "../../store/db.store"; import { useOrchestrator } from "../../hooks/useOrchestrator"; import { AppVersion } from "../App/AppVersion"; import { Donations } from "../Common/Donations"; +import { + isIndexerDisabled, + setIndexerDisabled, + getIndexerUrl, + setIndexerUrl, +} from "../../utils/indexer-settings"; export const LockedSettingsModal: React.FC = () => { const networkStore = useNetworkStore(); @@ -34,6 +41,23 @@ export const LockedSettingsModal: React.FC = () => { const [connectionError, setConnectionError] = useState(); + const [indexerDisabled, setIndexerDisabledState] = + useState(isIndexerDisabled()); + + // ui collapsable states + const [showNodeSettings, setShowNodeSettings] = useState(false); + const [showIndexerSettings, setShowIndexerSettings] = useState(false); + const [showDevSettings, setShowDevSettings] = useState(false); + + // connection modes + const [nodeConnectionMode, setNodeConnectionMode] = useState( + networkStore.nodeUrl ? "manual" : "auto" + ); + const [indexerConnectionMode, setIndexerConnectionMode] = + useState(getIndexerUrl() ? "manual" : "auto"); + + const [indexerUrl, setIndexerUrlState] = useState(getIndexerUrl() || ""); + const deleteIndexDB = async () => { if (dbStore.db) { await deleteDB(dbStore.db.name); @@ -42,25 +66,23 @@ export const LockedSettingsModal: React.FC = () => { await dbStore.initDB(); }; - const devInfo = () => { - if (!devMode) { - return null; + const handleIndexerDisabledChange = (disabled: boolean) => { + setIndexerDisabled(disabled); + setIndexerDisabledState(disabled); + }; + + const handleIndexerUrlSave = () => { + if (indexerConnectionMode === "auto") { + setIndexerUrl(null); + } else { + setIndexerUrl(indexerUrl || null); } + setShowIndexerSettings(false); + }; - return ( -
-
- - Dev mode enabled -
- -
- -
-
- ); + const handleIndexerUrlCancel = () => { + setIndexerUrlState(getIndexerUrl() || ""); + setShowIndexerSettings(false); }; const connectWithErrorWrapper = async ( @@ -97,69 +119,307 @@ export const LockedSettingsModal: React.FC = () => { return; } - await connectWithErrorWrapper( - selectedNetwork, - nodeUrl === "" ? null : nodeUrl - ); + const urlToUse = + nodeConnectionMode === "auto" ? null : nodeUrl === "" ? null : nodeUrl; + + await connectWithErrorWrapper(selectedNetwork, urlToUse); }; return (
- -
- -
-
- -
- -

- Network Settings -

- - -
- setNodeUrl(e.target.value)} - className="w-full flex-grow rounded-3xl border border-[var(--border-color)] bg-[var(--input-bg)] p-2 text-[var(--text-primary)]" - placeholder="wss://your-own-node-url.com" - /> -
- - -
-
- {connectionError && ( -
{connectionError}
- )} - {connectionSuccess && ( -
- Successfully connected to the node! -
- )} + {/* Main settings list - shown when no section is expanded */} + {!showNodeSettings && !showIndexerSettings && !showDevSettings ? ( + <> +
+ +
+

+ Settings +

+ +
+ {/* Node Settings Button */} + + + {/* Indexer Settings Button */} + -
- -
+ {/* Dev Settings Button - only when dev mode is enabled */} + {devMode && ( + + )} +
- {devInfo()} +
+ +
+ + ) : showNodeSettings ? ( + /* Node Settings Expanded Section */ + <> +
+ +

Node Settings

+
+ +
+ +
+ +
+
+ +
+ + +
+
+ + {nodeConnectionMode === "manual" && ( + <> + + setNodeUrl(e.target.value)} + className="w-full flex-grow rounded-3xl border border-[var(--border-color)] bg-[var(--input-bg)] p-2 text-[var(--text-primary)]" + placeholder="wss://your-own-node-url.com" + /> + + )} + + {nodeConnectionMode === "manual" && ( +
+ + +
+ )} +
+ + {connectionError && ( +
{connectionError}
+ )} + {connectionSuccess && ( +
+ Successfully connected to the node! +
+ )} + + ) : showIndexerSettings ? ( + /* Indexer Settings Expanded Section */ + <> +
+ +

Indexer Settings

+
+ +
+ + + + +
+ +
+
+ +
+ + +
+
+ + {indexerConnectionMode === "manual" && ( + <> + + setIndexerUrlState(e.target.value)} + className="w-full flex-grow rounded-3xl border border-[var(--border-color)] bg-[var(--input-bg)] p-2 text-[var(--text-primary)]" + placeholder="https://your-indexer-url.com" + /> + + )} + + {indexerConnectionMode === "manual" && ( +
+ + +
+ )} +
+ + ) : showDevSettings ? ( + <> +
+ +

Dev Settings

+
+ +
+
+ + Dev mode enabled +
+ +
+ +
+
+ + ) : null} + + {/* show donations at the home settings */} + {!showNodeSettings && !showIndexerSettings && !showDevSettings && ( + + )}
); }; diff --git a/src/types/all.ts b/src/types/all.ts index 5e875264..570a8211 100644 --- a/src/types/all.ts +++ b/src/types/all.ts @@ -161,3 +161,5 @@ export type OneOnOneConversation = { }; export type KasiaConversationEvent = Message | Payment | Handshake; + +export type ConnectionMode = "manual" | "auto"; From 2b34a80156fc8e077a046c66ef21c0b3095cc016 Mon Sep 17 00:00:00 2001 From: HocusLocust <182385655+HocusLocusTee@users.noreply.github.com> Date: Sun, 19 Oct 2025 08:50:23 +0300 Subject: [PATCH 21/81] refactor: lift manual state etc to a local storage item (for later UI use --- src/components/Modals/LockedSettingsModal.tsx | 76 ++++++++++++------- src/utils/indexer-settings.ts | 35 +++++++++ 2 files changed, 83 insertions(+), 28 deletions(-) diff --git a/src/components/Modals/LockedSettingsModal.tsx b/src/components/Modals/LockedSettingsModal.tsx index 8e3e9eed..237fb49e 100644 --- a/src/components/Modals/LockedSettingsModal.tsx +++ b/src/components/Modals/LockedSettingsModal.tsx @@ -18,6 +18,10 @@ import { setIndexerDisabled, getIndexerUrl, setIndexerUrl, + getIndexerConnectionMode, + setIndexerConnectionMode, + getNodeConnectionMode, + setNodeConnectionMode, } from "../../utils/indexer-settings"; export const LockedSettingsModal: React.FC = () => { @@ -50,11 +54,21 @@ export const LockedSettingsModal: React.FC = () => { const [showDevSettings, setShowDevSettings] = useState(false); // connection modes - const [nodeConnectionMode, setNodeConnectionMode] = useState( - networkStore.nodeUrl ? "manual" : "auto" - ); - const [indexerConnectionMode, setIndexerConnectionMode] = - useState(getIndexerUrl() ? "manual" : "auto"); + const [nodeConnectionMode, setNodeConnectionModeState] = + useState(getNodeConnectionMode()); + const [indexerConnectionMode, setIndexerConnectionModeState] = + useState(getIndexerConnectionMode()); + + // wrapper functions that update both state and localStorage + const updateNodeConnectionMode = (mode: ConnectionMode) => { + setNodeConnectionModeState(mode); + setNodeConnectionMode(mode); + }; + + const updateIndexerConnectionMode = (mode: ConnectionMode) => { + setIndexerConnectionModeState(mode); + setIndexerConnectionMode(mode); + }; const [indexerUrl, setIndexerUrlState] = useState(getIndexerUrl() || ""); @@ -69,6 +83,11 @@ export const LockedSettingsModal: React.FC = () => { const handleIndexerDisabledChange = (disabled: boolean) => { setIndexerDisabled(disabled); setIndexerDisabledState(disabled); + + // when disabling indexer, automatically switch to AUTO mode + if (disabled) { + updateIndexerConnectionMode("auto"); + } }; const handleIndexerUrlSave = () => { @@ -147,7 +166,7 @@ export const LockedSettingsModal: React.FC = () => {
Node Settings
- Configure custom node URLs for networks + Configure custom node URLs for kaspa network connection
@@ -214,24 +233,24 @@ export const LockedSettingsModal: React.FC = () => {
- - - -
@@ -322,7 +342,7 @@ export const LockedSettingsModal: React.FC = () => { disabled={indexerDisabled} onClick={() => { if (!indexerDisabled) { - setIndexerConnectionMode("auto"); + updateIndexerConnectionMode("auto"); setIndexerUrl(""); setIndexerUrl(null); } @@ -334,7 +354,7 @@ export const LockedSettingsModal: React.FC = () => { } ${ indexerConnectionMode === "auto" ? "bg-kas-secondary border-kas-secondary text-[var(--text-primary)] shadow-md" - : "border-[var(--border-color)] bg-[var(--input-bg)] text-[var(--text-primary)] hover:scale-105 hover:shadow-sm active:scale-90 active:opacity-80" + : "border-secondary-border bg-[var(--input-bg)] text-[var(--text-primary)] hover:scale-105 hover:shadow-sm active:scale-90 active:opacity-80" }`} > Auto @@ -343,7 +363,7 @@ export const LockedSettingsModal: React.FC = () => { disabled={indexerDisabled} onClick={() => { if (!indexerDisabled) { - setIndexerConnectionMode("manual"); + updateIndexerConnectionMode("manual"); } }} className={`rounded-lg border px-4 py-2 transition-all duration-200 ${ @@ -353,7 +373,7 @@ export const LockedSettingsModal: React.FC = () => { } ${ indexerConnectionMode === "manual" ? "bg-kas-secondary border-kas-secondary text-[var(--text-primary)] shadow-md" - : "border-[var(--border-color)] bg-[var(--input-bg)] text-[var(--text-primary)] hover:scale-105 hover:shadow-sm active:scale-90 active:opacity-80" + : "border-secondary-border bg-[var(--input-bg)] text-[var(--text-primary)] hover:scale-105 hover:shadow-sm active:scale-90 active:opacity-80" }`} > Manual diff --git a/src/utils/indexer-settings.ts b/src/utils/indexer-settings.ts index d2eb40ce..ffd2c5df 100644 --- a/src/utils/indexer-settings.ts +++ b/src/utils/indexer-settings.ts @@ -1,5 +1,7 @@ const INDEXER_DISABLED_KEY = "kasia_indexer_disabled"; const INDEXER_URL_KEY = "kasia_indexer_url"; +const INDEXER_CONNECTION_MODE_KEY = "kasia_indexer_connection_mode"; +const NODE_CONNECTION_MODE_KEY = "kasia_node_connection_mode"; // gets whether the indexer is disabled. // checks localStorage. Defaults to false (enabled) if no value is set. @@ -26,3 +28,36 @@ export function setIndexerUrl(url: string | null): void { localStorage.removeItem(INDEXER_URL_KEY); } } + +// gets the indexer connection mode from localStorage. +// defaults to "auto" if no value is set. +export function getIndexerConnectionMode(): "auto" | "manual" { + const localStorageValue = localStorage.getItem(INDEXER_CONNECTION_MODE_KEY); + return localStorageValue === "manual" ? "manual" : "auto"; +} + +// sets the indexer connection mode in localStorage. +export function setIndexerConnectionMode(mode: "auto" | "manual"): void { + localStorage.setItem(INDEXER_CONNECTION_MODE_KEY, mode); +} + +// gets the node connection mode from localStorage. +// defaults to "auto" if no value is set. +export function getNodeConnectionMode(): "auto" | "manual" { + const localStorageValue = localStorage.getItem(NODE_CONNECTION_MODE_KEY); + return localStorageValue === "manual" ? "manual" : "auto"; +} + +// sets the node connection mode in localStorage. +export function setNodeConnectionMode(mode: "auto" | "manual"): void { + localStorage.setItem(NODE_CONNECTION_MODE_KEY, mode); +} + +// checks if any connection settings have been overridden from defaults +export function hasOverriddenSettings(): boolean { + return ( + getNodeConnectionMode() === "manual" || + getIndexerConnectionMode() === "manual" || + isIndexerDisabled() + ); +} From a0ed1699e75440043b8586a1daf25a4c1415ae9a Mon Sep 17 00:00:00 2001 From: HocusLocust <182385655+HocusLocusTee@users.noreply.github.com> Date: Sun, 19 Oct 2025 08:58:06 +0300 Subject: [PATCH 22/81] style: visual feedback to disable indexer --- src/components/Layout/Header.tsx | 5 +++++ src/components/Layout/SlideOutMenu.tsx | 19 ++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx index 1405d1db..31a8ffa0 100644 --- a/src/components/Layout/Header.tsx +++ b/src/components/Layout/Header.tsx @@ -1,6 +1,8 @@ import { FC } from "react"; import { ThemeToggle } from "../Common/ThemeToggle"; import { useNavigate } from "react-router"; +import { DatabaseZap } from "lucide-react"; +import { isIndexerDisabled } from "../../utils/indexer-settings"; import { ConnectionIndicator } from "../Common/ConnectionIndicator"; type Props = { @@ -29,6 +31,9 @@ export const Header: FC = () => {
+ {isIndexerDisabled() && ( + + )}
diff --git a/src/components/Layout/SlideOutMenu.tsx b/src/components/Layout/SlideOutMenu.tsx index 08750a49..5b821fa7 100644 --- a/src/components/Layout/SlideOutMenu.tsx +++ b/src/components/Layout/SlideOutMenu.tsx @@ -1,9 +1,17 @@ import { FC, useState, useEffect } from "react"; import clsx from "clsx"; -import { RefreshCcw, User, ArrowLeft, Wallet, X } from "lucide-react"; +import { + RefreshCcw, + User, + ArrowLeft, + Wallet, + X, + DatabaseZap, +} from "lucide-react"; import { Settings } from "lucide-react"; import { useUiStore } from "../../store/ui.store"; import { SettingsModal } from "../Modals/SettingsModal"; +import { isIndexerDisabled } from "../../utils/indexer-settings"; import { ConnectionIndicator } from "../Common/ConnectionIndicator"; type SlideOutMenuProps = { @@ -73,8 +81,13 @@ export const SlideOutMenu: FC = ({ alt="Kasia Logo" className="h-[50px] w-[50px] object-contain" /> -
- Kasia +
+
+ Kasia +
+ {isIndexerDisabled() && ( + + )}
+ + )} + {indexerConnectionMode === "manual" && ( <>