mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
feat: replace payment modals with page-based navigation
- Add /balance/top-up route for payment method selection - Add /balance/top-up/:methodId route for amount entry and payment - Remove TopUpModal component (619 lines) - Simplify InsufficientBalancePrompt to use navigate instead of modals - Support ?amount and ?returnTo query params for cross-page data flow - Fix nav isActive to highlight Balance on sub-routes - Remove unused MainButton platform abstraction - Increase toast accent opacity for better visibility
This commit is contained in:
@@ -5,9 +5,6 @@ import {
|
||||
onBackButtonClick,
|
||||
offBackButtonClick,
|
||||
isBackButtonVisible,
|
||||
setMainButtonParams,
|
||||
onMainButtonClick,
|
||||
offMainButtonClick,
|
||||
hapticFeedbackImpactOccurred,
|
||||
hapticFeedbackNotificationOccurred,
|
||||
hapticFeedbackSelectionChanged,
|
||||
@@ -30,12 +27,10 @@ import type {
|
||||
PlatformContext,
|
||||
PlatformCapabilities,
|
||||
BackButtonController,
|
||||
MainButtonController,
|
||||
HapticController,
|
||||
DialogController,
|
||||
ThemeController,
|
||||
CloudStorageController,
|
||||
MainButtonConfig,
|
||||
PopupOptions,
|
||||
InvoiceStatus,
|
||||
HapticImpactStyle,
|
||||
@@ -47,7 +42,7 @@ function createCapabilities(): PlatformCapabilities {
|
||||
|
||||
return {
|
||||
hasBackButton: inTelegram,
|
||||
hasMainButton: inTelegram,
|
||||
|
||||
hasHapticFeedback: inTelegram,
|
||||
hasNativeDialogs: inTelegram,
|
||||
hasThemeSync: inTelegram,
|
||||
@@ -112,91 +107,6 @@ function createBackButtonController(): BackButtonController {
|
||||
};
|
||||
}
|
||||
|
||||
function createMainButtonController(): MainButtonController {
|
||||
const inTelegram = isInTelegramWebApp();
|
||||
let currentCallback: (() => void) | null = null;
|
||||
|
||||
return {
|
||||
get isVisible() {
|
||||
return false; // SDK v3 doesn't expose this as a simple getter
|
||||
},
|
||||
|
||||
show(config: MainButtonConfig) {
|
||||
if (!inTelegram) return;
|
||||
|
||||
if (currentCallback) {
|
||||
try {
|
||||
offMainButtonClick(currentCallback);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
currentCallback = config.onClick;
|
||||
|
||||
try {
|
||||
setMainButtonParams({
|
||||
text: config.text,
|
||||
isVisible: true,
|
||||
isEnabled: config.isActive !== false,
|
||||
isLoaderVisible: config.isLoading ?? false,
|
||||
backgroundColor: config.color as `#${string}` | undefined,
|
||||
textColor: config.textColor as `#${string}` | undefined,
|
||||
});
|
||||
onMainButtonClick(config.onClick);
|
||||
} catch {
|
||||
// Main button not mounted
|
||||
}
|
||||
},
|
||||
|
||||
hide() {
|
||||
if (!inTelegram) return;
|
||||
|
||||
if (currentCallback) {
|
||||
try {
|
||||
offMainButtonClick(currentCallback);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
currentCallback = null;
|
||||
}
|
||||
|
||||
try {
|
||||
setMainButtonParams({ isVisible: false, isLoaderVisible: false });
|
||||
} catch {
|
||||
// Main button not mounted
|
||||
}
|
||||
},
|
||||
|
||||
showProgress(show: boolean) {
|
||||
if (!inTelegram) return;
|
||||
try {
|
||||
setMainButtonParams({ isLoaderVisible: show });
|
||||
} catch {
|
||||
// Main button not mounted
|
||||
}
|
||||
},
|
||||
|
||||
setText(text: string) {
|
||||
if (!inTelegram) return;
|
||||
try {
|
||||
setMainButtonParams({ text });
|
||||
} catch {
|
||||
// Main button not mounted
|
||||
}
|
||||
},
|
||||
|
||||
setActive(active: boolean) {
|
||||
if (!inTelegram) return;
|
||||
try {
|
||||
setMainButtonParams({ isEnabled: active });
|
||||
} catch {
|
||||
// Main button not mounted
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createHapticController(): HapticController {
|
||||
const inTelegram = isInTelegramWebApp();
|
||||
|
||||
@@ -382,7 +292,7 @@ export function createTelegramAdapter(): PlatformContext {
|
||||
platform: 'telegram',
|
||||
capabilities: createCapabilities(),
|
||||
backButton: createBackButtonController(),
|
||||
mainButton: createMainButtonController(),
|
||||
|
||||
haptic: createHapticController(),
|
||||
dialog: createDialogController(),
|
||||
theme: createThemeController(),
|
||||
|
||||
@@ -2,12 +2,10 @@ import type {
|
||||
PlatformContext,
|
||||
PlatformCapabilities,
|
||||
BackButtonController,
|
||||
MainButtonController,
|
||||
HapticController,
|
||||
DialogController,
|
||||
ThemeController,
|
||||
CloudStorageController,
|
||||
MainButtonConfig,
|
||||
PopupOptions,
|
||||
InvoiceStatus,
|
||||
HapticImpactStyle,
|
||||
@@ -20,7 +18,7 @@ const STORAGE_PREFIX = 'bedolaga_';
|
||||
function createCapabilities(): PlatformCapabilities {
|
||||
return {
|
||||
hasBackButton: false, // No native back button in web
|
||||
hasMainButton: false, // No native main button in web
|
||||
|
||||
hasHapticFeedback: 'vibrate' in navigator, // Web Vibration API
|
||||
hasNativeDialogs: false, // Use custom dialogs
|
||||
hasThemeSync: false, // No header/bottom bar sync in web
|
||||
@@ -47,34 +45,6 @@ function createBackButtonController(): BackButtonController {
|
||||
};
|
||||
}
|
||||
|
||||
function createMainButtonController(): MainButtonController {
|
||||
// Web doesn't have a native main button - this is a no-op
|
||||
// The UI will render its own submit buttons
|
||||
return {
|
||||
isVisible: false,
|
||||
|
||||
show(_config: MainButtonConfig) {
|
||||
// No-op in web - handled by UI components
|
||||
},
|
||||
|
||||
hide() {
|
||||
// No-op in web
|
||||
},
|
||||
|
||||
showProgress(_show: boolean) {
|
||||
// No-op in web
|
||||
},
|
||||
|
||||
setText(_text: string) {
|
||||
// No-op in web
|
||||
},
|
||||
|
||||
setActive(_active: boolean) {
|
||||
// No-op in web
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createHapticController(): HapticController {
|
||||
// Web Vibration API fallback (works on mobile browsers)
|
||||
const canVibrate = 'vibrate' in navigator;
|
||||
@@ -219,7 +189,7 @@ export function createWebAdapter(): PlatformContext {
|
||||
platform: 'web',
|
||||
capabilities: createCapabilities(),
|
||||
backButton: createBackButtonController(),
|
||||
mainButton: createMainButtonController(),
|
||||
|
||||
haptic: createHapticController(),
|
||||
dialog: createDialogController(),
|
||||
theme: createThemeController(),
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { usePlatform } from '@/platform/hooks/usePlatform';
|
||||
import type { MainButtonConfig } from '@/platform/types';
|
||||
|
||||
interface UseMainButtonOptions extends Omit<MainButtonConfig, 'onClick'> {
|
||||
/**
|
||||
* Whether the main button should be visible
|
||||
* @default true
|
||||
*/
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to manage the Telegram MainButton
|
||||
* Automatically shows/hides based on component lifecycle
|
||||
*
|
||||
* @param onClick - Callback when main button is pressed
|
||||
* @param options - Configuration options
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function SubmitForm() {
|
||||
* const mutation = useMutation(...);
|
||||
*
|
||||
* useMainButton(handleSubmit, {
|
||||
* text: t('actions.submit'),
|
||||
* isLoading: mutation.isPending,
|
||||
* isActive: isFormValid,
|
||||
* });
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useMainButton(
|
||||
onClick: (() => void) | null | undefined,
|
||||
options: UseMainButtonOptions = { text: '' },
|
||||
): void {
|
||||
const { mainButton, capabilities } = usePlatform();
|
||||
const { visible = true, text, isLoading, isActive, color, textColor } = options;
|
||||
|
||||
// Use ref to prevent callback recreation issues
|
||||
const callbackRef = useRef(onClick);
|
||||
callbackRef.current = onClick;
|
||||
|
||||
// Stable callback wrapper
|
||||
const handleClick = useCallback(() => {
|
||||
callbackRef.current?.();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// If no native main button support, do nothing
|
||||
// UI components will render their own submit buttons
|
||||
if (!capabilities.hasMainButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If callback is null/undefined or visible is false, hide button
|
||||
if (!onClick || !visible || !text) {
|
||||
mainButton.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the main button with configuration
|
||||
mainButton.show({
|
||||
text,
|
||||
onClick: handleClick,
|
||||
isLoading,
|
||||
isActive,
|
||||
color,
|
||||
textColor,
|
||||
});
|
||||
|
||||
// Cleanup: hide button when component unmounts
|
||||
return () => {
|
||||
mainButton.hide();
|
||||
};
|
||||
}, [
|
||||
mainButton,
|
||||
capabilities.hasMainButton,
|
||||
handleClick,
|
||||
onClick,
|
||||
visible,
|
||||
text,
|
||||
isLoading,
|
||||
isActive,
|
||||
color,
|
||||
textColor,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for simple main button usage
|
||||
* Shows button only when conditions are met
|
||||
*
|
||||
* @param text - Button text
|
||||
* @param onClick - Click handler
|
||||
* @param enabled - Whether button should be shown and active
|
||||
* @param loading - Whether to show loading state
|
||||
*/
|
||||
export function useSimpleMainButton(
|
||||
text: string,
|
||||
onClick: () => void,
|
||||
enabled: boolean = true,
|
||||
loading: boolean = false,
|
||||
): void {
|
||||
useMainButton(enabled ? onClick : null, {
|
||||
text,
|
||||
isLoading: loading,
|
||||
isActive: enabled && !loading,
|
||||
visible: enabled,
|
||||
});
|
||||
}
|
||||
@@ -10,14 +10,12 @@ export type {
|
||||
PlatformType,
|
||||
PlatformContext as PlatformContextType,
|
||||
PlatformCapabilities,
|
||||
MainButtonConfig,
|
||||
PopupOptions,
|
||||
PopupButton,
|
||||
InvoiceStatus,
|
||||
HapticImpactStyle,
|
||||
HapticNotificationType,
|
||||
BackButtonController,
|
||||
MainButtonController,
|
||||
HapticController,
|
||||
DialogController,
|
||||
ThemeController,
|
||||
@@ -28,7 +26,6 @@ export type {
|
||||
// Hooks
|
||||
export { usePlatform, useIsTelegram, useCapability } from './hooks/usePlatform';
|
||||
export { useBackButton, useConditionalBackButton } from './hooks/useBackButton';
|
||||
export { useMainButton, useSimpleMainButton } from './hooks/useMainButton';
|
||||
export { useHaptic, useHapticClick, useHapticFeedback } from './hooks/useHaptic';
|
||||
export { useNativeDialog, useDestructiveConfirm, PopupButtons } from './hooks/useNativeDialog';
|
||||
export { useNotify } from './hooks/useNotify';
|
||||
|
||||
@@ -7,15 +7,6 @@ export type HapticNotificationType = 'success' | 'warning' | 'error';
|
||||
|
||||
export type InvoiceStatus = 'paid' | 'cancelled' | 'failed' | 'pending';
|
||||
|
||||
export interface MainButtonConfig {
|
||||
text: string;
|
||||
onClick: () => void;
|
||||
isLoading?: boolean;
|
||||
isActive?: boolean;
|
||||
color?: string;
|
||||
textColor?: string;
|
||||
}
|
||||
|
||||
export interface PopupButton {
|
||||
id: string;
|
||||
type?: 'default' | 'ok' | 'close' | 'cancel' | 'destructive';
|
||||
@@ -30,7 +21,7 @@ export interface PopupOptions {
|
||||
|
||||
export interface PlatformCapabilities {
|
||||
hasBackButton: boolean;
|
||||
hasMainButton: boolean;
|
||||
|
||||
hasHapticFeedback: boolean;
|
||||
hasNativeDialogs: boolean;
|
||||
hasThemeSync: boolean;
|
||||
@@ -46,15 +37,6 @@ export interface BackButtonController {
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
export interface MainButtonController {
|
||||
show: (config: MainButtonConfig) => void;
|
||||
hide: () => void;
|
||||
showProgress: (show: boolean) => void;
|
||||
setText: (text: string) => void;
|
||||
setActive: (active: boolean) => void;
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
export interface HapticController {
|
||||
impact: (style?: HapticImpactStyle) => void;
|
||||
notification: (type: HapticNotificationType) => void;
|
||||
@@ -104,9 +86,6 @@ export interface PlatformContext {
|
||||
// Navigation
|
||||
backButton: BackButtonController;
|
||||
|
||||
// Main action button
|
||||
mainButton: MainButtonController;
|
||||
|
||||
// Haptic feedback
|
||||
haptic: HapticController;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user