diff --git a/src/components/ConnectionModal.tsx b/src/components/ConnectionModal.tsx index 981c4f0..dd1ed08 100644 --- a/src/components/ConnectionModal.tsx +++ b/src/components/ConnectionModal.tsx @@ -93,6 +93,82 @@ 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'] @@ -272,89 +348,120 @@ export default function ConnectionModal({ onClose }: ConnectionModalProps) { // Step 1: Select platform if (!selectedPlatform) { return ( -
+
+ {/* Desktop: centered modal */} +
+
e.stopPropagation()} + > + +
+ {availablePlatforms.map((platform) => { + const IconComponent = platformIconComponents[platform] + const isDetected = platform === detectedPlatform + return ( + + ) + })} +
+
+
+ +
+
+
+ + {/* Mobile: bottom sheet */}
e.stopPropagation()} > - {/* Header */} -
-
-
- 📱 -
-
-

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

-

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

-
+
+ {/* Handle */} +
+
- -
- {/* Content */} -
-
- {availablePlatforms.map((platform) => { - const IconComponent = platformIconComponents[platform] - const isDetected = platform === detectedPlatform - return ( - +
+ + {/* Content */} +
+
+ {availablePlatforms.map((platform) => { + const IconComponent = platformIconComponents[platform] + const isDetected = platform === detectedPlatform + return ( + - ) - })} +

+ {getPlatformName(platform)} +

+ + ) + })} +
-
- {/* Footer */} -
- + {/* Footer */} +
+ +
@@ -363,189 +470,235 @@ export default function ConnectionModal({ onClose }: ConnectionModalProps) { // Step 2: Select app if (!selectedApp) { - return ( -
-
e.stopPropagation()} - > - {/* Header */} -
-
- -
-

{getPlatformName(selectedPlatform)}

-

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

+ const appListContent = platformApps.length === 0 ? ( +
+
+ 📭 +
+

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

+
+ ) : ( +
+ {platformApps.map((app) => ( + -
+
+ +
+ + ))} +
+ ) - {/* Content */} -
- {platformApps.length === 0 ? ( -
-
- 📭 + return ( +
+ {/* Desktop */} +
+
e.stopPropagation()}> + setSelectedPlatform(null)}> + {appListContent} + +
+
+ + {/* Mobile */} +
e.stopPropagation()}> +
+
+
+
+
+
+ +
+

{getPlatformName(selectedPlatform)}

+

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

-

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

- ) : ( -
- {platformApps.map((app) => ( - - ))} -
- )} + +
+
+ {appListContent} +
) } - // Step 3: App instructions + // 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) => ( + + + {getLocalizedText(btn.buttonText)} + + ))} +
+ )} +
+ )} + + {/* 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)} +

+
+ )} +
+ ) + return ( -
+
+ {/* Desktop: centered modal */} +
+
e.stopPropagation()} + > + setSelectedApp(null)} + > + {instructionsContent} + +
+ +
+
+
+ + {/* Mobile: bottom sheet */}
e.stopPropagation()} > - {/* Header */} -
-
- -
-

{selectedApp.name}

-

Инструкция по подключению

+
+ {/* Handle */} +
+
+
+ + {/* Header */} +
+
+ +
+

{selectedApp.name}

+

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

+
+
- -
- {/* Content */} -
-
- {/* 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)} - - ))} -
- )} -
- )} - - {/* 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)} -

-
- )} + {/* Content */} +
+ {instructionsContent}
-
- {/* Footer */} -
- + {/* Footer */} +
+ +
diff --git a/src/pages/Referral.tsx b/src/pages/Referral.tsx index 4526fd2..af3c681 100644 --- a/src/pages/Referral.tsx +++ b/src/pages/Referral.tsx @@ -68,6 +68,12 @@ export default function Referral() { queryFn: referralApi.getReferralInfo, }) + // Build referral link using frontend env variable for correct bot username + const botUsername = import.meta.env.VITE_TELEGRAM_BOT_USERNAME + const referralLink = info?.referral_code && botUsername + ? `https://t.me/${botUsername}?start=${info.referral_code}` + : info?.referral_link || '' + const { data: terms } = useQuery({ queryKey: ['referral-terms'], queryFn: referralApi.getReferralTerms, @@ -90,15 +96,15 @@ export default function Referral() { }) const copyLink = () => { - if (info?.referral_link) { - navigator.clipboard.writeText(info.referral_link) + if (referralLink) { + navigator.clipboard.writeText(referralLink) setCopied(true) setTimeout(() => setCopied(false), 2000) } } const shareLink = () => { - if (!info?.referral_link) return + if (!referralLink) return const shareText = t('referral.shareMessage', { percent: info?.commission_percent || 0, botName: branding?.name || import.meta.env.VITE_APP_NAME || 'Cabinet', @@ -109,7 +115,7 @@ export default function Referral() { .share({ title: t('referral.title'), text: shareText, - url: info.referral_link, + url: referralLink, }) .catch(() => { // ignore cancellation errors @@ -118,7 +124,7 @@ export default function Referral() { } const telegramUrl = `https://t.me/share/url?url=${encodeURIComponent( - info.referral_link + referralLink )}&text=${encodeURIComponent(shareText)}` window.open(telegramUrl, '_blank', 'noopener,noreferrer') } @@ -176,16 +182,16 @@ export default function Referral() {