@@ -22,8 +22,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO
2222*/
2323
2424import type { PropsWithChildren , ReactNode } from 'react' ;
25- import React , { useEffect } from 'react' ;
26- import { Link , NavigationContainer } from '@react-navigation/native' ;
25+ import React , { useEffect , useState } from 'react' ;
26+ import { Appearance , Linking , StatusBar } from 'react-native' ;
27+ import {
28+ Link ,
29+ NavigationContainer ,
30+ useNavigation ,
31+ type NavigationProp ,
32+ } from '@react-navigation/native' ;
2733import { createBottomTabNavigator } from '@react-navigation/bottom-tabs' ;
2834import { createNativeStackNavigator } from '@react-navigation/native-stack' ;
2935import { ApolloClient , InMemoryCache , ApolloProvider } from '@apollo/client' ;
@@ -46,7 +52,6 @@ import type {
4652} from '@shopify/checkout-sheet-kit' ;
4753import { ConfigProvider } from './context/Config' ;
4854import { ThemeProvider , getNavigationTheme , useTheme } from './context/Theme' ;
49- import { Appearance , StatusBar } from 'react-native' ;
5055import { CartProvider , useCart } from './context/Cart' ;
5156import CartScreen from './screens/CartScreen' ;
5257import ProductDetailsScreen from './screens/ProductDetailsScreen' ;
@@ -78,7 +83,7 @@ export type RootStackParamList = {
7883 Catalog : undefined ;
7984 CatalogScreen : undefined ;
8085 ProductDetails : { product : ShopifyProduct ; variant ?: ProductVariant } ;
81- Cart : { userId : string } ;
86+ Cart : undefined ;
8287 CartModal : undefined ;
8388 Settings : undefined ;
8489} ;
@@ -115,6 +120,49 @@ const createNavigationIcon =
115120 return < Icon name = { name } color = { color } size = { size } /> ;
116121 } ;
117122
123+ // See https://reactnative.dev/docs/linking#get-the-deep-link for more information
124+ const useInitialURL = ( ) : { url : string | null } => {
125+ const [ url , setUrl ] = useState < string | null > ( null ) ;
126+
127+ useEffect ( ( ) => {
128+ const getUrlAsync = async ( ) => {
129+ // Get the deep link used to open the app
130+ const initialUrl = await Linking . getInitialURL ( ) ;
131+
132+ if ( initialUrl !== url ) {
133+ setUrl ( initialUrl ) ;
134+ }
135+ } ;
136+
137+ getUrlAsync ( ) ;
138+ } , [ url ] ) ;
139+
140+ return {
141+ url,
142+ } ;
143+ } ;
144+
145+ // This code is meant as example only.
146+ class StorefrontURL {
147+ readonly url : string ;
148+
149+ constructor ( url : string ) {
150+ this . url = url ;
151+ }
152+
153+ isThankYouPage ( ) : boolean {
154+ return / t h a n k [ - _ ] y o u / i. test ( this . url ) ;
155+ }
156+
157+ isCheckout ( ) : boolean {
158+ return this . url . includes ( '/checkout' ) ;
159+ }
160+
161+ isCart ( ) {
162+ return this . url . includes ( '/cart' ) ;
163+ }
164+ }
165+
118166function AppWithContext ( { children} : PropsWithChildren ) {
119167 const shopify = useShopifyCheckoutSheet ( ) ;
120168
@@ -210,48 +258,99 @@ function CartIcon() {
210258 ) ;
211259}
212260
213- function AppWithNavigation ( ) {
261+ function AppWithNavigation ( { children } : PropsWithChildren ) {
214262 const { colorScheme, preference} = useTheme ( ) ;
215- const { totalQuantity} = useCart ( ) ;
216-
217263 return (
218264 < NavigationContainer theme = { getNavigationTheme ( colorScheme , preference ) } >
219- < Tab . Navigator >
220- < Tab . Screen
221- name = "Catalog"
222- component = { CatalogStack }
223- options = { {
224- headerShown : false ,
225- tabBarIcon : createNavigationIcon ( 'shop' ) ,
226- } }
227- />
228- < Tab . Screen
229- name = "Cart"
230- component = { CartScreen }
231- options = { {
232- tabBarIcon : createNavigationIcon ( 'shopping-bag' ) ,
233- tabBarBadge : totalQuantity > 0 ? totalQuantity : undefined ,
234- } }
235- />
236- < Tab . Screen
237- name = "Settings"
238- component = { SettingsScreen }
239- options = { {
240- tabBarIcon : createNavigationIcon ( 'cog' ) ,
241- } }
242- />
243- </ Tab . Navigator >
265+ { children }
244266 </ NavigationContainer >
245267 ) ;
246268}
247269
270+ function Routes ( ) {
271+ const { totalQuantity} = useCart ( ) ;
272+ const navigation = useNavigation < NavigationProp < RootStackParamList > > ( ) ;
273+ const { url : initialUrl } = useInitialURL ( ) ;
274+ const shopify = useShopifyCheckoutSheet ( ) ;
275+
276+ useEffect ( ( ) => {
277+ async function handleUniversalLink ( url : string ) {
278+ const storefrontUrl = new StorefrontURL ( url ) ;
279+
280+ console . log ( 'HANDLING UNIVERSAL LINK' , url ) ;
281+
282+ switch ( true ) {
283+ // Checkout URLs
284+ case storefrontUrl . isCheckout ( ) && ! storefrontUrl . isThankYouPage ( ) :
285+ console . log ( 'should present' ) ;
286+ shopify . present ( url . replace ( 'rn://' , 'https://' ) ) ;
287+ return ;
288+ // Cart URLs
289+ case storefrontUrl . isCart ( ) :
290+ navigation . navigate ( 'Cart' ) ;
291+ return ;
292+ }
293+
294+ // Open everything else in a mobile browser
295+ const canOpenUrl = await Linking . canOpenURL ( url ) ;
296+
297+ if ( canOpenUrl ) {
298+ await Linking . openURL ( url ) ;
299+ }
300+ }
301+
302+ if ( initialUrl ) {
303+ handleUniversalLink ( initialUrl ) ;
304+ }
305+
306+ // Subscribe to universal links
307+ const subscription = Linking . addEventListener ( 'url' , ( { url} ) => {
308+ handleUniversalLink ( url ) ;
309+ } ) ;
310+
311+ return ( ) => {
312+ subscription . remove ( ) ;
313+ } ;
314+ } , [ initialUrl , shopify , navigation ] ) ;
315+
316+ return (
317+ < Tab . Navigator >
318+ < Tab . Screen
319+ name = "Catalog"
320+ component = { CatalogStack }
321+ options = { {
322+ headerShown : false ,
323+ tabBarIcon : createNavigationIcon ( 'shop' ) ,
324+ } }
325+ />
326+ < Tab . Screen
327+ name = "Cart"
328+ component = { CartScreen }
329+ options = { {
330+ tabBarIcon : createNavigationIcon ( 'shopping-bag' ) ,
331+ tabBarBadge : totalQuantity > 0 ? totalQuantity : undefined ,
332+ } }
333+ />
334+ < Tab . Screen
335+ name = "Settings"
336+ component = { SettingsScreen }
337+ options = { {
338+ tabBarIcon : createNavigationIcon ( 'cog' ) ,
339+ } }
340+ />
341+ </ Tab . Navigator >
342+ ) ;
343+ }
344+
248345function App ( ) {
249346 return (
250347 < ErrorBoundary >
251348 < ShopifyCheckoutSheetProvider configuration = { config } >
252349 < AppWithTheme >
253350 < AppWithContext >
254- < AppWithNavigation />
351+ < AppWithNavigation >
352+ < Routes />
353+ </ AppWithNavigation >
255354 </ AppWithContext >
256355 </ AppWithTheme >
257356 </ ShopifyCheckoutSheetProvider >
0 commit comments