Skip to content

refactor: use reusable ProfileLink component on home screen#204

Closed
Saurav09s wants to merge 8 commits into
Dev-Card:mainfrom
Saurav09s:refactor/profile-link-component
Closed

refactor: use reusable ProfileLink component on home screen#204
Saurav09s wants to merge 8 commits into
Dev-Card:mainfrom
Saurav09s:refactor/profile-link-component

Conversation

@Saurav09s

Copy link
Copy Markdown

Summary

Refactored HomeScreen to use reusable ProfileLink component.

Changes

  • Imported ProfileLink
  • Replaced hardcoded platform badges with reusable component
  • Passed platform, username, url, and onPress props
  • Preserved existing UI styling and spacing

Testing

  • Verified mobile screen renders correctly
  • Verified links display properly

Copilot AI review requested due to automatic review settings May 20, 2026 18:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the mobile HomeScreen to render platform/profile links using a new reusable ProfileLink component, aiming to replace the previously hardcoded platform badge UI.

Changes:

  • Added a new ProfileLink component for displaying a platform + username row with an “Open” affordance.
  • Updated HomeScreen to render up to 4 links via ProfileLink and show a “+N” overflow indicator.
  • Reorganized the HomeScreen layout around the links/QR/actions sections.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

File Description
apps/mobile/src/screens/HomeScreen.tsx Switches platform link rendering to ProfileLink and rearranges sections, but currently introduces JSX syntax issues.
apps/mobile/src/components/ProfileLink.tsx Introduces the reusable link row component used by HomeScreen.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +144 to +150
<ProfileLink
key={link.id}
platform={platform?.name || link.platform}
username={link.username}
url={link.url}
onPress={() => Linking.openURL(link.url)}
/>
Comment thread apps/mobile/src/screens/HomeScreen.tsx Outdated
Comment on lines 18 to 23
import { APP_URL, API_BASE_URL } from '../config';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import type { RootStackParamList } from '../navigation/MainTabs';
import ProfileLink from '../components/ProfileLink';
import { Linking } from 'react-native';

Comment on lines +309 to +311
marginTop: SPACING.md,
gap: SPACING.sm,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +46 to +69
const styles = StyleSheet.create({
card: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 16,
borderRadius: 12,
backgroundColor: '#161616',
marginBottom: 12,
},
platform: {
fontSize: 16,
fontWeight: '600',
color: '#ffffff',
},
username: {
marginTop: 4,
fontSize: 14,
color: '#a1a1aa',
},
link: {
fontSize: 14,
fontWeight: '600',
color: '#4f8cff',
padding: 16,
borderRadius: 12,
backgroundColor: '#161616',
marginBottom: 12,
Comment on lines +12 to +18
type ProfileLinkProps = {
platform: string;
username: string;
url: string;
onPress?: () => void;
};

Comment on lines +19 to +42
export default function ProfileLink({
platform,
username,
url,
onPress,
}: ProfileLinkProps) {
const handlePress = () => {
if (onPress) {
onPress();
return;
}

Linking.openURL(url);
};

return (
<Pressable style={styles.card} onPress={handlePress}>
<View>
<Text style={styles.platform}>{platform}</Text>
<Text style={styles.username}>{username}</Text>
</View>

<Text style={styles.link}>Open</Text>
</Pressable>
@Harxhit Harxhit added the gssoc:approved Required label for every approved PR. Gives the base +50 points and enables contribution tracking. label May 21, 2026
Signed-off-by: Saurav Suman <134825390+Saurav09s@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 23, 2026 03:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (1)

apps/mobile/src/screens/HomeScreen.tsx:47

  • searchUsername/setSearchUsername is now unused (the search UI block was removed), which will trigger the RN ESLint no-unused-vars rule and fail lint/typecheck. Either restore the search UI or remove this state (and any related imports).
  const [showQR, setShowQR] = useState(false);
  const [refreshing, setRefreshing] = useState(false);
  const [searchUsername, setSearchUsername] = useState('');

  const profileUrl = user?.defaultCardId
    ? `${APP_URL}/devcard/${user.defaultCardId}`
    : `${APP_URL}/u/${user?.username}`;

Comment thread apps/mobile/src/screens/HomeScreen.tsx Outdated
Comment on lines +184 to +190
</TouchableOpacity>

{/* QR Code Section */}
{/* Action Buttons */}
<View style={styles.actions}>
<TouchableOpacity
style={styles.qrSection}
onPress={() => setShowQR(!showQR)}
style={styles.actionButton}
onPress={handleShare}
Comment on lines +310 to +312
marginTop: SPACING.md,
gap: SPACING.sm,
},
Comment thread apps/mobile/src/screens/HomeScreen.tsx Outdated
</SafeAreaView>
</View>
</ScrollView>
</SafeAreaView >
Comment on lines +12 to +25
type ProfileLinkProps = {
platform: string;
username: string;
url: string;
onPress?: () => void;
};
import {
COLORS,
SPACING,
FONT_SIZE,
BORDER_RADIUS,
} from '../theme/tokens';
export default function ProfileLink({
platform,
Comment on lines +30 to +36
const handlePress = () => {
if (onPress) {
onPress();
return;
}

Linking.openURL(url);
padding: SPACING.md,
borderRadius: BORDER_RADIUS.md,
backgroundColor: COLORS.bgCard,
marginBottom: SPACING.sm,
Comment thread apps/mobile/src/screens/HomeScreen.tsx Outdated
Comment on lines +19 to +23
import { APP_URL, API_BASE_URL } from '../config';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import type { RootStackParamList } from '../navigation/MainTabs';
import ProfileLink from '../components/ProfileLink';

@ShantKhatri

Copy link
Copy Markdown
Contributor

@Saurav09s There are some reviews from co-pilote, could you please look into this?

@Saurav09s

Copy link
Copy Markdown
Author

@ShantKhatri can you please tell me about 1st review "The “View a DevCard” search/lookup section appears to have been removed as part of this refactor, but the PR description only mentions swapping platform badges for ProfileLink. If this feature removal is intentional, update the PR description; otherwise, please restore the search section." I think I didn't remove it. Rest all I would fix and again push

Saurav09s and others added 2 commits May 23, 2026 22:28
Signed-off-by: Saurav Suman <134825390+Saurav09s@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 23, 2026 17:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

Comment thread apps/mobile/src/screens/HomeScreen.tsx Outdated
Comment on lines 329 to 333
linksContainer: {
marginTop: SPACING.md,
gap: SPACING.sm,
},
loadingRoot: {
Comment on lines +160 to 174
{/* Platform Links */}
<View style={styles.linksContainer}>
{links.slice(0, 4).map(link => {
const platform = PLATFORMS[link.platform];

return (
<ProfileLink
key={link.id}
platform={platform?.name || link.platform}
username={link.username}
url={link.url}
/>
);
})}
</View>
Comment thread apps/mobile/src/screens/HomeScreen.tsx Outdated
Comment on lines +233 to +251
{/* Stats */}
<View style={styles.stats}>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{links.length}</Text>
<Text style={styles.statLabel}>Links</Text>
</View>

{/* Search / Lookup */}
<View style={styles.searchSection}>
<Text style={styles.searchLabel}>🔍 View a DevCard</Text>
<View style={styles.searchRow}>
<TextInput
style={styles.searchInput}
placeholder="Enter username..."
placeholderTextColor={COLORS.textMuted}
value={searchUsername}
onChangeText={setSearchUsername}
autoCapitalize="none"
autoCorrect={false}
returnKeyType="go"
onSubmitEditing={() => {
const u = searchUsername.trim();
if (u) (navigation as any).navigate('DevCardView', { username: u });
}}
/>
<TouchableOpacity
style={styles.searchBtn}
onPress={() => {
const u = searchUsername.trim();
if (u) (navigation as any).navigate('DevCardView', { username: u });
}}
>
<Text style={styles.searchBtnText}>Go →</Text>
</TouchableOpacity>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statNumber}>{analytics?.totalViews || 0}</Text>
<Text style={styles.statLabel}>Views</Text>
</View>

{/* Stats */}
<View style={styles.stats}>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{links.length}</Text>
<Text style={styles.statLabel}>Links</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statNumber}>{analytics?.totalViews || 0}</Text>
<Text style={styles.statLabel}>Views</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statNumber}>{analytics?.followsCount || 0}</Text>
<Text style={styles.statLabel}>Follows</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statNumber}>{analytics?.followsCount || 0}</Text>
<Text style={styles.statLabel}>Follows</Text>
</View>
</ScrollView>
</SafeAreaView>
</View>
</ScrollView>
</SafeAreaView >
Comment on lines +30 to +41
const handlePress = async () => {
if (onPress) {
onPress();
return;
}

try {
await Linking.openURL(url);
} catch (error) {
console.warn('Failed to open profile link:', error);
}
};
Comment on lines +36 to +41
try {
await Linking.openURL(url);
} catch (error) {
console.warn('Failed to open profile link:', error);
}
};
Comment on lines +24 to +51
export default function ProfileLink({
platform,
username,
url,
onPress,
}: ProfileLinkProps) {
const handlePress = async () => {
if (onPress) {
onPress();
return;
}

try {
await Linking.openURL(url);
} catch (error) {
console.warn('Failed to open profile link:', error);
}
};

return (
<Pressable style={styles.card} onPress={handlePress}>
<View>
<Text style={styles.platform}>{platform}</Text>
<Text style={styles.username}>{username}</Text>
</View>

<Text style={styles.link}>Open</Text>
</Pressable>
Copilot AI review requested due to automatic review settings May 23, 2026 17:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.

Comments suppressed due to low confidence (1)

apps/mobile/src/screens/HomeScreen.tsx:57

  • fetchData uses token for Authorization but the initial useEffect has an empty dependency array and fetchData doesn’t guard against token being null. If token is set after the first render (e.g., after login), HomeScreen won’t refetch and may send requests with Bearer null. Consider returning early when !token and rerunning the effect when token changes (or memoizing fetchData with [token] and depending on it).
  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    try {
      const [profileRes, analyticsRes] = await Promise.all([
        fetch(`${API_BASE_URL}/api/profiles/me`, {
          headers: { Authorization: `Bearer ${token}` },
        }),
        fetch(`${API_BASE_URL}/api/analytics/overview`, {
          headers: { Authorization: `Bearer ${token}` },
        })

Comment on lines 34 to 40
export default function HomeScreen({ navigation }: Props) {
const { user, token } = useAuth();
const [links, setLinks] = useState<PlatformLink[]>([]);
const [analytics, setAnalytics] = useState<any>(null);
const [showQR, setShowQR] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(true);
const [searchUsername, setSearchUsername] = useState('');

Comment on lines +138 to 151
<View style={styles.linksContainer}>
{links.slice(0, 4).map(link => {
const platform = PLATFORMS[link.platform];

return (
<ProfileLink
key={link.id}
platform={platform?.name || link.platform}
username={link.username}
url={link.url}
/>
);
})}
</View>
Comment on lines 203 to 213
<TouchableOpacity
style={styles.actionButton}
onPress={() => (navigation as any).navigate('DevCardView', { username: user?.username || '' })}
activeOpacity={0.85}>
<Text style={styles.actionEmoji}>👁️</Text>
<Text style={styles.actionText}>Preview</Text>
</TouchableOpacity>
</View>

{/* Search / Lookup */}
<View style={styles.searchSection}>
<Text style={styles.searchLabel}>🔍 View a DevCard</Text>
<View style={styles.searchRow}>
<TextInput
style={styles.searchInput}
placeholder="Enter username..."
placeholderTextColor={COLORS.textMuted}
value={searchUsername}
onChangeText={setSearchUsername}
autoCapitalize="none"
autoCorrect={false}
returnKeyType="go"
onSubmitEditing={() => {
const u = searchUsername.trim();
if (u) (navigation as any).navigate('DevCardView', { username: u });
}}
/>
<TouchableOpacity
style={styles.searchBtn}
onPress={() => {
const u = searchUsername.trim();
if (u) (navigation as any).navigate('DevCardView', { username: u });
}}
>
<Text style={styles.searchBtnText}>Go →</Text>
</TouchableOpacity>
</View>
</View>

{/* Stats */}
<View style={styles.stats}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -156,29 +134,29 @@
{user?.bio && <Text style={styles.bio}>{user.bio}</Text>}

{/* Platform Links Summary */}
Comment on lines +309 to +311
marginTop: SPACING.md,
gap: SPACING.sm,
},
Comment on lines +30 to +40
const handlePress = async () => {
if (onPress) {
onPress();
return;
}

try {
await Linking.openURL(url);
} catch (error) {
console.warn('Failed to open profile link:', error);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +24 to +52
export default function ProfileLink({
platform,
username,
url,
onPress,
}: ProfileLinkProps) {
const handlePress = async () => {
if (onPress) {
onPress();
return;
}

try {
await Linking.openURL(url);
} catch (error) {
console.warn('Failed to open profile link:', error);
}
};

return (
<Pressable style={styles.card} onPress={handlePress}>
<View>
<Text style={styles.platform}>{platform}</Text>
<Text style={styles.username}>{username}</Text>
</View>

<Text style={styles.link}>Open</Text>
</Pressable>
);
Comment on lines +142 to +149
return (
<ProfileLink
key={link.id}
platform={platform?.name || link.platform}
username={link.username}
url={link.url}
/>
);
Comment on lines +45 to 50
useEffect(() => {
fetchData();
}, []);

const fetchData = async () => {
try {
Comment on lines 203 to 213
<TouchableOpacity
style={styles.actionButton}
onPress={() => (navigation as any).navigate('DevCardView', { username: user?.username || '' })}
activeOpacity={0.85}>
<Text style={styles.actionEmoji}>👁️</Text>
<Text style={styles.actionText}>Preview</Text>
</TouchableOpacity>
</View>

{/* Search / Lookup */}
<View style={styles.searchSection}>
<Text style={styles.searchLabel}>🔍 View a DevCard</Text>
<View style={styles.searchRow}>
<TextInput
style={styles.searchInput}
placeholder="Enter username..."
placeholderTextColor={COLORS.textMuted}
value={searchUsername}
onChangeText={setSearchUsername}
autoCapitalize="none"
autoCorrect={false}
returnKeyType="go"
onSubmitEditing={() => {
const u = searchUsername.trim();
if (u) (navigation as any).navigate('DevCardView', { username: u });
}}
/>
<TouchableOpacity
style={styles.searchBtn}
onPress={() => {
const u = searchUsername.trim();
if (u) (navigation as any).navigate('DevCardView', { username: u });
}}
>
<Text style={styles.searchBtnText}>Go →</Text>
</TouchableOpacity>
</View>
</View>

{/* Stats */}
<View style={styles.stats}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +309 to +311
marginTop: SPACING.md,
gap: SPACING.sm,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +30 to +40
const handlePress = async () => {
if (onPress) {
onPress();
return;
}

try {
await Linking.openURL(url);
} catch (error) {
console.warn('Failed to open profile link:', error);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ShantKhatri ShantKhatri requested a review from blankirigaya June 6, 2026 16:49
@ShantKhatri

Copy link
Copy Markdown
Contributor

No updates for the last few weeks, so closing this PR according to policy.

@ShantKhatri ShantKhatri closed this Jun 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved Required label for every approved PR. Gives the base +50 points and enables contribution tracking.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants