Skip to content

Commit 7d45540

Browse files
committed
fix(expo): public share modal + link on web
1 parent 2b39b2d commit 7d45540

File tree

1 file changed

+81
-18
lines changed

1 file changed

+81
-18
lines changed

expo-app/sources/components/sessionSharing/components/PublicLinkDialog.tsx

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import React, { memo, useState, useEffect } from 'react';
2-
import { View, Text, ScrollView, Switch } from 'react-native';
2+
import { View, Text, ScrollView, Switch, Platform, Linking } from 'react-native';
33
import { StyleSheet } from 'react-native-unistyles';
44
import QRCode from 'qrcode';
55
import { Image } from 'expo-image';
6+
import * as Clipboard from 'expo-clipboard';
67
import { PublicSessionShare } from '@/sync/sharingTypes';
78
import { Item } from '@/components/Item';
89
import { ItemList } from '@/components/ItemList';
910
import { RoundButton } from '@/components/RoundButton';
1011
import { t } from '@/text';
11-
import { getServerUrl } from '@/sync/serverConfig';
1212
import { Ionicons } from '@expo/vector-icons';
13+
import { BaseModal } from '@/modal/components/BaseModal';
14+
import { Modal } from '@/modal';
1315

1416
/**
1517
* Props for PublicLinkDialog component
@@ -43,21 +45,40 @@ export const PublicLinkDialog = memo(function PublicLinkDialog({
4345
onCancel
4446
}: PublicLinkDialogProps) {
4547
const [qrDataUrl, setQrDataUrl] = useState<string | null>(null);
48+
const [shareUrl, setShareUrl] = useState<string | null>(null);
4649
const [isConfiguring, setIsConfiguring] = useState(false);
4750
const [expiresInDays, setExpiresInDays] = useState<number | undefined>(7);
4851
const [maxUses, setMaxUses] = useState<number | undefined>(undefined);
4952
const [isConsentRequired, setIsConsentRequired] = useState(true);
5053

54+
const buildPublicShareUrl = (token: string): string => {
55+
const path = `/share/${token}`;
56+
57+
if (Platform.OS === 'web') {
58+
const origin =
59+
typeof window !== 'undefined' && window.location?.origin
60+
? window.location.origin
61+
: '';
62+
return `${origin}${path}`;
63+
}
64+
65+
const configuredWebAppUrl = (process.env.EXPO_PUBLIC_HAPPY_WEBAPP_URL || '').trim();
66+
const webAppUrl = configuredWebAppUrl || 'https://app.happy.engineering';
67+
return `${webAppUrl}${path}`;
68+
};
69+
5170
// Generate QR code when public share exists
5271
useEffect(() => {
5372
if (!publicShare?.token) {
5473
setQrDataUrl(null);
74+
setShareUrl(null);
5575
return;
5676
}
5777

58-
// Use the configured server URL to generate the share link
59-
const serverUrl = getServerUrl();
60-
const url = `${serverUrl}/share/${publicShare.token}`;
78+
// IMPORTANT: Public share links point to the web app route (`/share/:token`),
79+
// not the API server URL.
80+
const url = buildPublicShareUrl(publicShare.token);
81+
setShareUrl(url);
6182

6283
QRCode.toDataURL(url, {
6384
width: 250,
@@ -84,19 +105,43 @@ export const PublicLinkDialog = memo(function PublicLinkDialog({
84105
return new Date(timestamp).toLocaleDateString();
85106
};
86107

108+
const handleOpenLink = async () => {
109+
if (!shareUrl) return;
110+
try {
111+
if (Platform.OS === 'web') {
112+
window.open(shareUrl, '_blank', 'noopener,noreferrer');
113+
return;
114+
}
115+
await Linking.openURL(shareUrl);
116+
} catch {
117+
// ignore
118+
}
119+
};
120+
121+
const handleCopyLink = async () => {
122+
if (!shareUrl) return;
123+
try {
124+
await Clipboard.setStringAsync(shareUrl);
125+
Modal.alert(t('common.copied'), t('items.copiedToClipboard', { label: t('session.sharing.publicLink') }));
126+
} catch {
127+
Modal.alert(t('common.error'), t('textSelection.failedToCopy'));
128+
}
129+
};
130+
87131
return (
88-
<View style={styles.container}>
89-
<View style={styles.header}>
90-
<Text style={styles.title}>{t('session.sharing.publicLink')}</Text>
91-
<Item
92-
title={t('common.cancel')}
93-
onPress={onCancel}
94-
/>
95-
</View>
132+
<BaseModal visible={true} onClose={onCancel}>
133+
<View style={styles.container}>
134+
<View style={styles.header}>
135+
<Text style={styles.title}>{t('session.sharing.publicLink')}</Text>
136+
<Item
137+
title={t('common.cancel')}
138+
onPress={onCancel}
139+
/>
140+
</View>
96141

97-
<ScrollView style={styles.content}>
98-
{!publicShare || isConfiguring ? (
99-
<ItemList>
142+
<ScrollView style={styles.content}>
143+
{!publicShare || isConfiguring ? (
144+
<ItemList>
100145
<Text style={styles.description}>
101146
{t('session.sharing.publicLinkDescription')}
102147
</Text>
@@ -236,6 +281,23 @@ export const PublicLinkDialog = memo(function PublicLinkDialog({
236281
</View>
237282
)}
238283

284+
{/* Public link */}
285+
{shareUrl ? (
286+
<>
287+
<Item
288+
title={t('session.sharing.publicLink')}
289+
subtitle={<Text selectable>{shareUrl}</Text>}
290+
subtitleLines={0}
291+
onPress={handleOpenLink}
292+
/>
293+
<Item
294+
title={t('common.copy')}
295+
icon={<Ionicons name="copy-outline" size={29} color="#007AFF" />}
296+
onPress={handleCopyLink}
297+
/>
298+
</>
299+
) : null}
300+
239301
{/* Info */}
240302
{publicShare.token ? (
241303
<Item
@@ -288,8 +350,9 @@ export const PublicLinkDialog = memo(function PublicLinkDialog({
288350
</View>
289351
</ItemList>
290352
) : null}
291-
</ScrollView>
292-
</View>
353+
</ScrollView>
354+
</View>
355+
</BaseModal>
293356
);
294357
});
295358

0 commit comments

Comments
 (0)