From fc10fad1c4320ec7fd85c281e0d979246ef90791 Mon Sep 17 00:00:00 2001 From: PEDZEO Date: Tue, 20 Jan 2026 02:42:36 +0300 Subject: [PATCH] Refactor ConnectionModal component: standardize icon sizes, enhance modal structure with a reusable ModalWrapper, and improve loading and error states. Update platform selection layout for better responsiveness and user experience. --- src/components/ConnectionModal.tsx | 700 +++++++++++------------------ 1 file changed, 263 insertions(+), 437 deletions(-) diff --git a/src/components/ConnectionModal.tsx b/src/components/ConnectionModal.tsx index dd1ed08..9a6bf13 100644 --- a/src/components/ConnectionModal.tsx +++ b/src/components/ConnectionModal.tsx @@ -10,37 +10,37 @@ interface ConnectionModalProps { // Platform SVG Icons const IosIcon = () => ( - + ) const AndroidIcon = () => ( - + ) const WindowsIcon = () => ( - + ) const MacosIcon = () => ( - + ) const LinuxIcon = () => ( - + ) const TvIcon = () => ( - + @@ -59,25 +59,25 @@ const BackIcon = () => ( ) const CopyIcon = () => ( - + ) const CheckIcon = () => ( - + ) const LinkIcon = () => ( - + ) const ChevronIcon = () => ( - + ) @@ -93,82 +93,6 @@ const platformIconComponents: Record = { appleTV: TvIcon, } -// Helper components -interface ModalContentProps { - title: string - subtitle?: string - onClose: () => void - onBack?: () => void - icon?: string - children: React.ReactNode -} - -const ModalContent = ({ title, subtitle, onClose, onBack, icon, children }: ModalContentProps) => ( - <> -
-
- {onBack && ( - - )} - {icon && !onBack && ( -
- {icon} -
- )} -
-

{title}

- {subtitle &&

{subtitle}

} -
-
- -
-
- {children} -
- -) - -interface CopyLinkButtonProps { - copied: boolean - onClick: () => void - t: (key: string) => string - compact?: boolean -} - -const CopyLinkButton = ({ copied, onClick, t, compact }: CopyLinkButtonProps) => ( - -) - // Platform order for display const platformOrder = ['ios', 'android', 'windows', 'macos', 'linux', 'androidTV', 'appleTV'] @@ -293,414 +217,316 @@ export default function ConnectionModal({ onClose }: ConnectionModalProps) { window.location.href = redirectUrl } + // Modal wrapper - centered on all devices + const ModalWrapper = ({ children }: { children: React.ReactNode }) => ( +
+
e.stopPropagation()} + > + {children} +
+
+ ) + // Loading state if (isLoading) { return ( -
-
e.stopPropagation()}> -
-
-
+ +
+
-
+
) } // Error state if (error || !appConfig) { return ( -
-
e.stopPropagation()}> -
-
- 😕 -
-

{t('common.error')}

- + +
+
+ 😕
+

{t('common.error')}

+
-
+ ) } // No subscription if (!appConfig.hasSubscription) { return ( -
-
e.stopPropagation()}> -
-
- 📱 -
-

{t('subscription.connection.title')}

-

{t('subscription.connection.noSubscription')}

- + +
+
+ 📱
+

{t('subscription.connection.title')}

+

{t('subscription.connection.noSubscription')}

+
-
+ ) } // Step 1: Select platform if (!selectedPlatform) { return ( -
- {/* Desktop: centered modal */} -
-
e.stopPropagation()} + + {/* Header */} +
+
+
+ 📱 +
+
+

{t('subscription.connection.title')}

+

{t('subscription.connection.selectDevice')}

+
+
+ +
+ + {/* Platforms grid */} +
+
+ {availablePlatforms.map((platform) => { + const IconComponent = platformIconComponents[platform] + const isDetected = platform === detectedPlatform + return ( + + ) + })} +
+
+ + {/* Copy link */} +
+ - ) - })} -
- -
- -
-
+ {copied ? : } + {copied ? t('subscription.connection.copied') : t('subscription.connection.copyLink')} +
- - {/* Mobile: bottom sheet */} -
e.stopPropagation()} - > -
- {/* Handle */} -
-
-
- - {/* Header */} -
-
-
- 📱 -
-
-

{t('subscription.connection.title')}

-

{t('subscription.connection.selectDevice')}

-
-
- -
- - {/* Content */} -
-
- {availablePlatforms.map((platform) => { - const IconComponent = platformIconComponents[platform] - const isDetected = platform === detectedPlatform - return ( - - ) - })} -
-
- - {/* Footer */} -
- -
-
-
-
+ ) } // Step 2: Select app if (!selectedApp) { - const appListContent = platformApps.length === 0 ? ( -
-
- 📭 -
-

{t('subscription.connection.noApps')}

-
- ) : ( -
- {platformApps.map((app) => ( - - ))} -
- ) - return ( -
- {/* Desktop */} -
-
e.stopPropagation()}> - setSelectedPlatform(null)}> - {appListContent} - + + {/* Header */} +
+
+ +
+

{getPlatformName(selectedPlatform)}

+

{t('subscription.connection.selectApp')}

+
+
- {/* Mobile */} -
e.stopPropagation()}> -
-
-
-
-
-
- -
-

{getPlatformName(selectedPlatform)}

-

{t('subscription.connection.selectApp')}

-
+ {/* Apps list */} +
+ {platformApps.length === 0 ? ( +
+
+ 📭
- +

{t('subscription.connection.noApps')}

-
- {appListContent} -
-
-
-
- ) - } - - // Step 3: App instructions - shared content - const instructionsContent = ( -
- {/* Step 1: Install */} - {selectedApp.installationStep && ( -
-
-
1
-

{t('subscription.connection.installApp')}

-
-

- {getLocalizedText(selectedApp.installationStep.description)} -

- {selectedApp.installationStep.buttons && selectedApp.installationStep.buttons.length > 0 && ( -
- {selectedApp.installationStep.buttons - .filter((btn) => isValidExternalUrl(btn.buttonLink)) - .map((btn, idx) => ( - + {platformApps.map((app) => ( + ))}
)}
- )} - - {/* Step 2: Add subscription */} - {selectedApp.addSubscriptionStep && ( -
-
-
2
-

{t('subscription.connection.addSubscription')}

-
-

- {getLocalizedText(selectedApp.addSubscriptionStep.description)} -

- -
- {/* Connect button */} - {selectedApp.deepLink && ( - - )} - - {/* Copy link */} - -
-
- )} - - {/* Step 3: Connect */} - {selectedApp.connectAndUseStep && ( -
-
-
3
-

{t('subscription.connection.connectVpn')}

-
-

- {getLocalizedText(selectedApp.connectAndUseStep.description)} -

-
- )} -
- ) + + ) + } + // Step 3: App instructions return ( -
- {/* Desktop: centered modal */} -
-
e.stopPropagation()} - > - setSelectedApp(null)} - > - {instructionsContent} - -
- + + {/* Header */} +
+
+ +
+

{selectedApp.name}

+

{t('subscription.connection.instructions') || 'Инструкция'}

+
- {/* Mobile: bottom sheet */} -
e.stopPropagation()} - > -
- {/* Handle */} -
-
-
- - {/* Header */} -
-
- -
-

{selectedApp.name}

-

{t('subscription.connection.instructions')}

+ {/* Instructions */} +
+ {/* Step 1: Install */} + {selectedApp.installationStep && ( +
+
+
1
+
+

{t('subscription.connection.installApp')}

+

+ {getLocalizedText(selectedApp.installationStep.description)} +

+ {selectedApp.installationStep.buttons && selectedApp.installationStep.buttons.length > 0 && ( +
+ {selectedApp.installationStep.buttons + .filter((btn) => isValidExternalUrl(btn.buttonLink)) + .map((btn, idx) => ( + + + {getLocalizedText(btn.buttonText)} + + ))} +
+ )}
-
+ )} - {/* Content */} -
- {instructionsContent} + {/* Step 2: Add subscription */} + {selectedApp.addSubscriptionStep && ( +
+
+
2
+
+

{t('subscription.connection.addSubscription')}

+

+ {getLocalizedText(selectedApp.addSubscriptionStep.description)} +

+
+ {selectedApp.deepLink && ( + + )} + +
+
+
+ )} - {/* Footer */} -
- + {/* Step 3: Connect */} + {selectedApp.connectAndUseStep && ( +
+
+
3
+
+

{t('subscription.connection.connectVpn')}

+

+ {getLocalizedText(selectedApp.connectAndUseStep.description)} +

+
+
-
+ )}
-
+ + {/* Footer */} +
+ +
+ ) }