Skip to content

Commit 453b52c

Browse files
authored
feat: add Contacts and Teams modules with associated screens, hooks, and navigation updates (#413)
Signed-off-by: Prashantkumar Khatri <96608160+ShantKhatri@users.noreply.github.com>
1 parent be7a7c2 commit 453b52c

25 files changed

Lines changed: 4217 additions & 4120 deletions

apps/mobile/App.tsx

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,50 @@
11
import React from 'react';
2-
import { NavigationContainer } from '@react-navigation/native';
2+
import { NavigationContainer, LinkingOptions } from '@react-navigation/native';
33
import { SafeAreaProvider } from 'react-native-safe-area-context';
44
import { GestureHandlerRootView } from 'react-native-gesture-handler';
55
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
66
import { AuthProvider, useAuth } from './src/context/AuthContext';
77
import { ThemeProvider } from './src/context/ThemeContext';
88
import AuthStack from './src/navigation/AuthStack';
99
import MainTabs from './src/navigation/MainTabs';
10+
import SplashScreen from './src/screens/SplashScreen';
11+
import { DEEP_LINK_SCHEME } from './src/config';
1012

1113
import { Linking, StyleSheet } from 'react-native';
1214

15+
// ── Deep Link Configuration ───────────────────────────────────────────────────
16+
17+
const linking: LinkingOptions<{}> = {
18+
prefixes: [`${DEEP_LINK_SCHEME}://`],
19+
config: {
20+
screens: {
21+
MainTabs: {
22+
screens: {
23+
Home: 'home',
24+
Scan: 'scan',
25+
},
26+
},
27+
DevCardView: 'u/:username',
28+
},
29+
},
30+
};
31+
32+
// ── App Content ───────────────────────────────────────────────────────────────
33+
1334
function AppContent() {
1435
const { isAuthenticated, isLoading, login } = useAuth();
1536

1637
React.useEffect(() => {
1738
const handleDeepLink = (event: { url: string }) => {
18-
console.log('--- DEEP LINK RECEIVED ---');
19-
console.log('URL:', event.url);
20-
const url = new URL(event.url);
21-
const hashParams = new URLSearchParams(url.hash.replace(/^#/, ''));
22-
const token = url.searchParams.get('token') || hashParams.get('token');
23-
if (token) {
24-
console.log('Token found, logging in...');
25-
login(token);
39+
try {
40+
const url = new URL(event.url);
41+
const hashParams = new URLSearchParams(url.hash.replace(/^#/, ''));
42+
const token = url.searchParams.get('token') || hashParams.get('token');
43+
if (token) {
44+
login(token);
45+
}
46+
} catch (error) {
47+
console.error('Deep link parse error:', error);
2648
}
2749
};
2850

@@ -38,16 +60,18 @@ function AppContent() {
3860
}, [login]);
3961

4062
if (isLoading) {
41-
return null; // Splash screen could go here
63+
return <SplashScreen />;
4264
}
4365

4466
return (
45-
<NavigationContainer>
67+
<NavigationContainer linking={linking}>
4668
{isAuthenticated ? <MainTabs /> : <AuthStack />}
4769
</NavigationContainer>
4870
);
4971
}
5072

73+
// ── Root ───────────────────────────────────────────────────────────────────────
74+
5175
export default function App() {
5276
return (
5377
<GestureHandlerRootView style={styles.gestureRoot}>

apps/mobile/index.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'react-native-gesture-handler';
2-
import { registerRootComponent } from 'expo';
2+
import { AppRegistry } from 'react-native';
33
import App from './App';
4+
import { name as appName } from './app.json';
45

5-
// registerRootComponent handles mounting and bootstrapping the app
6-
// on both native mobile devices (Expo Go) and web browsers seamlessly.
7-
registerRootComponent(App);
6+
AppRegistry.registerComponent(appName, () => App);

apps/mobile/metro.config.js

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,52 @@
1-
const { getDefaultConfig } = require('expo/metro-config');
1+
const { getDefaultConfig } = require('@react-native/metro-config');
22
const path = require('path');
33

44
// Monorepo root
55
const projectRoot = __dirname;
66
const monorepoRoot = path.resolve(projectRoot, '../..');
77

88
/**
9-
* Metro configuration for Expo monorepo
9+
* Metro configuration for React Native monorepo
1010
*/
11-
const config = getDefaultConfig(projectRoot);
11+
module.exports = (async () => {
12+
const config = await getDefaultConfig(projectRoot);
1213

13-
config.watchFolders = [monorepoRoot];
14-
config.resolver.nodeModulesPaths = [
15-
path.resolve(projectRoot, 'node_modules'),
16-
path.resolve(monorepoRoot, 'node_modules'),
17-
];
18-
config.resolver.disableHierarchicalLookup = false;
14+
config.watchFolders = [monorepoRoot];
15+
config.resolver = config.resolver || {};
16+
config.resolver.nodeModulesPaths = [
17+
path.resolve(projectRoot, 'node_modules'),
18+
path.resolve(monorepoRoot, 'node_modules'),
19+
];
20+
config.resolver.disableHierarchicalLookup = false;
1921

20-
const pinnedModules = {
21-
react: path.resolve(projectRoot, 'node_modules/react'),
22-
'react-native': path.resolve(projectRoot, 'node_modules/react-native'),
23-
'react-native-reanimated': path.resolve(
24-
projectRoot,
25-
'node_modules/react-native-reanimated'
26-
),
27-
'react-native-worklets': path.resolve(
28-
projectRoot,
29-
'node_modules/react-native-worklets'
30-
),
31-
'react-native-gesture-handler': path.resolve(
32-
projectRoot,
33-
'node_modules/react-native-gesture-handler'
34-
),
35-
};
22+
const pinnedModules = {
23+
react: path.resolve(projectRoot, 'node_modules/react'),
24+
'react-native': path.resolve(projectRoot, 'node_modules/react-native'),
25+
'react-native-reanimated': path.resolve(
26+
projectRoot,
27+
'node_modules/react-native-reanimated'
28+
),
29+
'react-native-worklets': path.resolve(
30+
projectRoot,
31+
'node_modules/react-native-worklets'
32+
),
33+
'react-native-gesture-handler': path.resolve(
34+
projectRoot,
35+
'node_modules/react-native-gesture-handler'
36+
),
37+
};
3638

37-
config.resolver.extraNodeModules = pinnedModules;
38-
config.resolver.resolveRequest = (context, moduleName, platform) => {
39-
for (const [name, modulePath] of Object.entries(pinnedModules)) {
40-
if (moduleName === name || moduleName.startsWith(`${name}/`)) {
41-
const target = path.join(modulePath, moduleName.slice(name.length));
42-
return context.resolveRequest(context, target, platform);
39+
config.resolver.extraNodeModules = pinnedModules;
40+
config.resolver.resolveRequest = (context, moduleName, platform) => {
41+
for (const [name, modulePath] of Object.entries(pinnedModules)) {
42+
if (moduleName === name || moduleName.startsWith(`${name}/`)) {
43+
const target = path.join(modulePath, moduleName.slice(name.length));
44+
return context.resolveRequest(context, target, platform);
45+
}
4346
}
44-
}
4547

46-
return context.resolveRequest(context, moduleName, platform);
47-
};
48+
return context.resolveRequest(context, moduleName, platform);
49+
};
4850

49-
module.exports = config;
51+
return config;
52+
})();
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React from 'react';
2+
import { View, TouchableOpacity, StyleSheet } from 'react-native';
3+
import { COLORS, SPACING, BORDER_RADIUS } from '../theme/tokens';
4+
5+
// ── Predefined Accent Color Palette ───────────────────────────────────────────
6+
// 8 curated colors that work well as card accent on the dark DevCard theme.
7+
8+
export const ACCENT_COLORS = [
9+
'#6366F1', // Indigo (default)
10+
'#8B5CF6', // Violet
11+
'#EC4899', // Pink
12+
'#EF4444', // Red
13+
'#F59E0B', // Amber
14+
'#22C55E', // Green
15+
'#06B6D4', // Cyan
16+
'#3B82F6', // Blue
17+
] as const;
18+
19+
export type AccentColor = (typeof ACCENT_COLORS)[number];
20+
21+
interface ColorPickerProps {
22+
selected: string;
23+
onSelect: (color: string) => void;
24+
}
25+
26+
export default function ColorPicker({ selected, onSelect }: ColorPickerProps) {
27+
return (
28+
<View style={styles.container}>
29+
{ACCENT_COLORS.map((color) => {
30+
const isActive = selected === color;
31+
return (
32+
<TouchableOpacity
33+
key={color}
34+
style={[
35+
styles.swatch,
36+
{ backgroundColor: color },
37+
isActive && styles.swatchActive,
38+
]}
39+
onPress={() => onSelect(color)}
40+
activeOpacity={0.7}
41+
accessibilityLabel={`Select accent color ${color}`}
42+
accessibilityRole="radio"
43+
accessibilityState={{ selected: isActive }}
44+
/>
45+
);
46+
})}
47+
</View>
48+
);
49+
}
50+
51+
const styles = StyleSheet.create({
52+
container: {
53+
flexDirection: 'row',
54+
flexWrap: 'wrap',
55+
gap: SPACING.sm,
56+
justifyContent: 'center',
57+
},
58+
swatch: {
59+
width: 40,
60+
height: 40,
61+
borderRadius: BORDER_RADIUS.full,
62+
borderWidth: 2,
63+
borderColor: COLORS.transparent,
64+
},
65+
swatchActive: {
66+
borderColor: COLORS.white,
67+
transform: [{ scale: 1.15 }],
68+
shadowColor: '#000',
69+
shadowOffset: { width: 0, height: 2 },
70+
shadowOpacity: 0.4,
71+
shadowRadius: 4,
72+
elevation: 6,
73+
},
74+
});

apps/mobile/src/config.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
1-
import Constants from 'expo-constants';
2-
import * as Linking from 'expo-linking';
1+
import { Platform } from 'react-native';
32

4-
// DevCard API Configuration
3+
// ── DevCard API Configuration ─────────────────────────────────────────────────
4+
// Environment-aware URLs with no Expo dependency. On Android emulators the
5+
// loopback address is 10.0.2.2; on iOS simulators localhost works directly.
56

6-
// Prefer explicit configuration via Expo/EAS extras. Fallback to sensible defaults
7-
const extras = (Constants as any).manifest?.extra || (Constants as any).expoConfig?.extra;
7+
const ANDROID_LOCALHOST = '10.0.2.2';
8+
const IOS_LOCALHOST = 'localhost';
9+
const DEV_HOST = Platform.OS === 'android' ? ANDROID_LOCALHOST : IOS_LOCALHOST;
810

9-
const DEV_API = extras?.API_BASE_URL || extras?.DEV_API_BASE_URL;
10-
const DEV_APP = extras?.APP_URL;
11+
export const API_BASE_URL: string = __DEV__
12+
? `http://${DEV_HOST}:3000`
13+
: 'https://api.devcard.dev';
1114

12-
export const API_BASE_URL = __DEV__
13-
? DEV_API ?? `http://10.0.2.2:3000` // 10.0.2.2 is a common emulator host for Android
14-
: extras?.API_BASE_URL ?? 'https://api.devcard.dev';
15+
export const APP_URL: string = __DEV__
16+
? 'http://localhost:5173'
17+
: 'https://devcard.dev';
1518

16-
export const APP_URL = __DEV__
17-
? DEV_APP ?? `http://localhost:5173`
18-
: extras?.APP_URL ?? 'https://devcard.dev';
19+
// Deep link scheme — must match android/app/build.gradle and ios/Info.plist
20+
export const DEEP_LINK_SCHEME = 'devcard';
1921

20-
export const OAUTH_REDIRECT_URI = Linking.createURL('oauth/callback');
22+
export const OAUTH_REDIRECT_URI = `${DEEP_LINK_SCHEME}://oauth/callback`;

0 commit comments

Comments
 (0)