The Journey Continues
In Phase 6, I built the support system with chat screens, video consultation access, and ticket history. Now it's time to complete the patient-facing experience with the Profile area—a comprehensive hub for patient information, medical history, financial records, and exercise programs.
Coming from React web development, I was curious: How do you structure complex account areas in mobile? How do you display nested information like clinical records? What's different about building payment UIs for mobile vs web? Let me walk you through what I learned, comparing everything to what you already know from React.
What We're Building
In Phase 7, I implemented:
- Profile Screen with patient info card and navigation menu
- Extended User type with health metrics (age, weight, height, blood group)
- Clinical Records screen with visit summaries and status badges
- Payments screen with saved methods and transaction history
- Regimen Tab with exercise programs and progress tracking
- Nested navigation using ProfileStackNavigator
- Component reuse - Timeline reused for "My Appointments"
- Dynamic progress bars for regimen completion
Everything uses mock data to simulate real patient records.
Step 1: Project Structure - Adding Profile Types
React (Web) - Typical Profile Structure:
my-react-app/
├── src/
│ ├── components/
│ │ ├── Profile.js
│ │ ├── ClinicalRecords.js
│ │ ├── Payments.js
│ │ └── Regimen.js
│ └── data/
│ ├── clinical.js
│ ├── payments.js
│ └── regimen.js
React Native (What I Built):
physio-care/
├── src/
│ ├── components/
│ │ ├── screens/
│ │ │ ├── ProfileScreen.tsx
│ │ │ ├── ClinicalRecordsScreen.tsx
│ │ │ └── PaymentScreen.tsx
│ │ └── ui-molecules/
│ │ └── RegimenTab.tsx
│ ├── data/
│ │ ├── mockClinicalRecords.ts
│ │ ├── mockPaymentRecords.ts
│ │ └── mockRegimen.ts
│ ├── types/
│ │ ├── clinical.ts
│ │ ├── payment.ts
│ │ ├── regimen.ts
│ │ └── user.ts
│ └── navigation/
│ └── ProfileStackNavigator.tsx
Key Differences:
- Dedicated stack navigator: Profile features get their own navigation stack
- Extended user type: Health metrics stored in auth context
- Regimen as main tab: Exercise programs accessible from bottom navigation
- Component reuse: Timeline component reused for appointment history
Takeaway: Mobile profile areas need careful information hierarchy. Show summary cards upfront, let users drill down into details. Don't overwhelm with all information at once.
Step 2: TypeScript Types for Profile System
This was crucial for managing complex healthcare data! Let me show you the types I created.
React (Web) - JavaScript Objects:
// clinical.js
export const clinicalRecords = [
{
id: "1",
visitSummary: "Initial consultation",
consultantName: "Dr. Sarah Johnson",
date: "2024-10-15",
},
];
// payments.js
export const paymentMethods = [
{
id: "1",
type: "credit",
last4: "4321",
isDefault: true,
},
];
React Native - TypeScript Types:
// src/types/clinical.ts
export interface ClinicalRecord {
id: string;
visitSummary: string;
consultantName: string;
centerName: string;
date: string;
treatmentPlan: string;
clinicalFeedback: string;
status: "completed" | "ongoing";
}
// src/types/payment.ts
export interface PaymentMethod {
id: string;
type: "credit" | "debit" | "upi" | "netbanking";
last4?: string;
bankName?: string;
upiId?: string;
isDefault: boolean;
}
export interface PaymentTransaction {
id: string;
date: string;
amount: number;
description: string;
paymentMethod: string;
status: "success" | "pending" | "failed";
type: "credit" | "debit";
}
// src/types/regimen.ts
export interface Exercise {
id: string;
name: string;
sets: number;
reps: number;
weight?: number;
duration?: string;
instructions: string;
}
export interface Regimen {
id: string;
name: string;
status: "not-started" | "in-progress" | "completed";
totalExercises: number;
completedExercises: number;
startDate?: string;
endDate?: string;
exercises: Exercise[];
}
export type RegimenTabType = "not-started" | "in-progress" | "completed";
// src/types/user.ts (extended)
export interface User {
id?: string;
name: string;
mobile: string;
email: string;
dateOfBirth?: string;
gender?: "male" | "female" | "other";
age?: number;
weight?: number;
height?: number;
bloodGroup?: string;
}
Benefits of These Types:
- Payment flexibility: Union types handle cards, UPI, and netbanking
-
Optional fields:
last4for cards,upiIdfor UPI payments - Status tracking: Three-level status for regimens and transactions
- Exercise structure: Sets, reps, and optional weight/duration
- Health metrics: Age, weight, height, blood group in user type
Takeaway: Healthcare apps need comprehensive type definitions. Payment types must handle multiple methods (cards, UPI, etc.). Regimen types need to track both program status and individual exercise details.
Step 3: Building the Profile Landing Screen
React (Web) - Dashboard Layout:
// Profile.js
function Profile({ user }) {
return (
<div className="profile">
<div className="user-card">
<div className="avatar">{user.name.charAt(0)}</div>
<div className="info">
<h2>{user.name}</h2>
<p>Age: {user.age}</p>
<p>Weight: {user.weight} kg</p>
<p>Height: {user.height} cm</p>
<p>Blood Group: {user.bloodGroup}</p>
</div>
</div>
<nav className="profile-menu">
<a href="/clinical-records">Clinical Records</a>
<a href="/appointments">My Appointments</a>
<a href="/payments">Payments</a>
</nav>
<button onClick={handleLogout} className="logout-btn">
Logout
</button>
</div>
);
}
/* Profile.css */
.profile {
padding: 20px;
max-width: 600px;
margin: 0 auto;
}
.user-card {
display: flex;
align-items: center;
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.avatar {
width: 60px;
height: 60px;
border-radius: 50%;
background: #007aff;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: bold;
margin-right: 16px;
}
.profile-menu {
background: white;
border-radius: 12px;
overflow: hidden;
}
.profile-menu a {
display: block;
padding: 16px;
border-bottom: 1px solid #f0f0f0;
text-decoration: none;
color: #333;
}
.logout-btn {
width: 100%;
padding: 16px;
background: #ff3b30;
color: white;
border: none;
border-radius: 12px;
margin-top: 20px;
cursor: pointer;
}
React Native - ScrollView with Info Card and Menu:
// src/components/screens/ProfileScreen.tsx
import React from "react";
import {
View,
Text,
ScrollView,
StyleSheet,
TouchableOpacity,
Alert,
} from "react-native";
import { useNavigation } from "@react-navigation/native";
import { useAuth } from "../../context/AuthContext";
import { ProfileStackNavigationProp } from "../../types/navigation";
export default function ProfileScreen() {
const { user, setUser } = useAuth();
const navigation = useNavigation<ProfileStackNavigationProp>();
const handleLogout = () => {
Alert.alert("Logout", "Are you sure you want to log out?", [
{ text: "Cancel", style: "cancel" },
{
text: "Logout",
style: "destructive",
onPress: () => setUser(null),
},
]);
};
const menuItems = [
{
title: "Clinical Records",
subtitle: "View your treatment history",
onPress: () => navigation.navigate("ClinicalRecords"),
},
{
title: "My Appointments",
subtitle: "View and manage appointments",
onPress: () => navigation.navigate("MyAppointments"),
},
{
title: "Payments",
subtitle: "Payment methods and history",
onPress: () => navigation.navigate("Payments"),
},
];
return (
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
{/* Patient Info Card */}
<View style={styles.infoCard}>
<View style={styles.avatar}>
<Text style={styles.avatarText}>
{user?.name?.charAt(0)?.toUpperCase() || "U"}
</Text>
</View>
<View style={styles.infoContent}>
<Text style={styles.name}>{user?.name || "User"}</Text>
<Text style={styles.detail}>Age: {user?.age || "Not specified"}</Text>
<Text style={styles.detail}>
Weight: {user?.weight ? `${user.weight} kg` : "Not specified"}
</Text>
<Text style={styles.detail}>
Height: {user?.height ? `${user.height} cm` : "Not specified"}
</Text>
<Text style={styles.detail}>
Blood Group: {user?.bloodGroup || "Not specified"}
</Text>
</View>
</View>
{/* Menu Items */}
<View style={styles.menuContainer}>
{menuItems.map((item, index) => (
<TouchableOpacity
key={index}
style={styles.menuItem}
onPress={item.onPress}
>
<View style={styles.menuContent}>
<Text style={styles.menuTitle}>{item.title}</Text>
<Text style={styles.menuSubtitle}>{item.subtitle}</Text>
</View>
<Text style={styles.arrow}>›</Text>
</TouchableOpacity>
))}
</View>
{/* Logout Button */}
<TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
<Text style={styles.logoutText}>Logout</Text>
</TouchableOpacity>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f5f5f5",
},
infoCard: {
backgroundColor: "#fff",
margin: 20,
padding: 20,
borderRadius: 12,
flexDirection: "row",
alignItems: "center",
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
marginTop: 60,
},
avatar: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: "#007AFF",
justifyContent: "center",
alignItems: "center",
marginRight: 16,
},
avatarText: {
color: "#fff",
fontSize: 24,
fontWeight: "bold",
},
infoContent: {
flex: 1,
},
name: {
fontSize: 20,
fontWeight: "bold",
color: "#333",
marginBottom: 8,
},
detail: {
fontSize: 14,
color: "#666",
marginBottom: 2,
},
menuContainer: {
backgroundColor: "#fff",
marginHorizontal: 20,
borderRadius: 12,
overflow: "hidden",
},
menuItem: {
flexDirection: "row",
alignItems: "center",
padding: 16,
borderBottomWidth: 1,
borderBottomColor: "#f0f0f0",
},
menuContent: {
flex: 1,
},
menuTitle: {
fontSize: 16,
fontWeight: "600",
color: "#333",
marginBottom: 4,
},
menuSubtitle: {
fontSize: 14,
color: "#666",
},
arrow: {
fontSize: 20,
color: "#ccc",
fontWeight: "bold",
},
logoutButton: {
backgroundColor: "#FF3B30",
marginHorizontal: 20,
marginTop: 20,
marginBottom: 40,
padding: 16,
borderRadius: 12,
alignItems: "center",
},
logoutText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
});
Key Differences:
- Alert.alert for confirmation: Native confirmation dialog for logout
- Destructive style: iOS-specific red button for dangerous actions
- Avatar from initial: Generate avatar from first letter of name
- Menu with subtitles: Two-line menu items for better context
- Arrow indicator: Visual cue that items are tappable
- Elevation + shadow: Cross-platform shadow effect
Takeaway: Profile screens need clear visual hierarchy: user info at top, navigation menu in middle, logout at bottom. Using Alert.alert with destructive style gives native iOS/Android confirmation dialogs.
Step 4: Building the Clinical Records Screen
React (Web) - Record List:
// ClinicalRecords.js
function ClinicalRecords() {
return (
<div className="clinical-records">
<header>
<button onClick={() => history.back()}>← Back</button>
<h2>Clinical Records</h2>
</header>
<div className="records-list">
{records.map((record) => (
<div key={record.id} className="record-card">
<div className="record-header">
<h3>{record.visitSummary}</h3>
<span className={`status ${record.status}`}>{record.status}</span>
</div>
<p className="consultant">{record.consultantName}</p>
<p className="center">{record.centerName}</p>
<p className="date">{formatDate(record.date)}</p>
<div className="treatment">
<strong>Treatment Plan:</strong>
<p>{record.treatmentPlan}</p>
</div>
</div>
))}
</div>
</div>
);
}
React Native - ScrollView with Record Cards:
// src/components/screens/ClinicalRecordsScreen.tsx
import React from "react";
import {
View,
Text,
ScrollView,
StyleSheet,
TouchableOpacity,
} from "react-native";
import { useNavigation } from "@react-navigation/native";
import { mockClinicalRecords } from "../../data/mockClinicalRecords";
import { ProfileStackNavigationProp } from "../../types/navigation";
export default function ClinicalRecordsScreen() {
const navigation = useNavigation<ProfileStackNavigationProp>();
const handleRecordPress = (record: any) => {
alert(
`Clinical Record Details:\n\n${record.visitSummary}\n\nConsultant: ${record.consultantName}\nCenter: ${record.centerName}\nDate: ${record.date}\n\nTreatment Plan: ${record.treatmentPlan}\n\nFeedback: ${record.clinicalFeedback}`
);
};
return (
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
<View style={styles.header}>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
>
<Text style={styles.backArrow}>‹</Text>
</TouchableOpacity>
<Text style={styles.title}>Clinical Records</Text>
</View>
<View style={styles.recordsList}>
{mockClinicalRecords.map((record) => (
<TouchableOpacity
key={record.id}
style={styles.recordCard}
onPress={() => handleRecordPress(record)}
>
<View style={styles.recordHeader}>
<Text style={styles.visitSummary}>{record.visitSummary}</Text>
<View
style={[
styles.statusBadge,
{
backgroundColor:
record.status === "completed" ? "#34C759" : "#FF9500",
},
]}
>
<Text style={styles.statusText}>{record.status}</Text>
</View>
</View>
<Text style={styles.consultant}>{record.consultantName}</Text>
<Text style={styles.center}>{record.centerName}</Text>
<Text style={styles.date}>
{new Date(record.date).toLocaleDateString("en-IN", {
day: "numeric",
month: "long",
year: "numeric",
})}
</Text>
<Text style={styles.treatmentLabel}>Treatment Plan:</Text>
<Text style={styles.treatmentText}>{record.treatmentPlan}</Text>
</TouchableOpacity>
))}
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f5f5f5",
},
header: {
flexDirection: "row",
alignItems: "center",
padding: 20,
backgroundColor: "#fff",
paddingTop: 40,
},
backButton: {
marginRight: 16,
},
backArrow: {
fontSize: 28,
color: "#007AFF",
fontWeight: "bold",
},
title: {
fontSize: 20,
fontWeight: "bold",
color: "#333",
},
recordsList: {
padding: 20,
},
recordCard: {
backgroundColor: "#fff",
padding: 16,
borderRadius: 12,
marginBottom: 16,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
recordHeader: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "flex-start",
marginBottom: 12,
},
visitSummary: {
fontSize: 16,
fontWeight: "600",
color: "#333",
flex: 1,
marginRight: 12,
},
statusBadge: {
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
},
statusText: {
color: "#fff",
fontSize: 12,
fontWeight: "600",
textTransform: "capitalize",
},
consultant: {
fontSize: 14,
fontWeight: "500",
color: "#007AFF",
marginBottom: 4,
},
center: {
fontSize: 14,
color: "#666",
marginBottom: 4,
},
date: {
fontSize: 12,
color: "#999",
marginBottom: 12,
},
treatmentLabel: {
fontSize: 14,
fontWeight: "600",
color: "#333",
marginBottom: 4,
},
treatmentText: {
fontSize: 14,
color: "#666",
lineHeight: 20,
},
});
Key Differences:
- Status color coding: Green for completed, orange for ongoing
-
Date formatting: Using
toLocaleDateStringwith Indian locale - Visual hierarchy: Consultant name highlighted in blue
- Treatment plan section: Labeled clearly for medical context
- Card-based layout: Each record is a distinct, tappable card
Takeaway: Medical records need clear status indicators and proper information hierarchy. Consultant names should stand out, and treatment plans need proper labeling for quick scanning.
Step 5: Building the Payments Screen
React (Web) - Payment Methods and History:
// Payments.js
function Payments() {
return (
<div className="payments">
<header>
<button onClick={() => history.back()}>← Back</button>
<h2>Payments</h2>
</header>
<section className="payment-methods">
<h3>Saved Payment Methods</h3>
{paymentMethods.map((method) => (
<div key={method.id} className="method-card">
<div className="method-info">
<span className="type">{method.type}</span>
<span className="details">****{method.last4}</span>
{method.isDefault && <span className="default">Default</span>}
</div>
<button onClick={() => handleDelete(method.id)}>Delete</button>
</div>
))}
</section>
<section className="transactions">
<h3>Previous Payments</h3>
{transactions.map((tx) => (
<div key={tx.id} className="transaction-card">
<div className="tx-info">
<p className="description">{tx.description}</p>
<p className="date">{formatDate(tx.date)}</p>
<p className="method">{tx.paymentMethod}</p>
</div>
<div className="tx-amount">
<span className={tx.type}>
{tx.type === "debit" ? "-" : "+"}₹{tx.amount}
</span>
<span className="status">{tx.status}</span>
</div>
</div>
))}
</section>
</div>
);
}
React Native - Sectioned ScrollView:
// src/components/screens/PaymentScreen.tsx
import React from "react";
import {
View,
Text,
ScrollView,
StyleSheet,
TouchableOpacity,
} from "react-native";
import { useNavigation } from "@react-navigation/native";
import {
mockPaymentMethods,
mockPaymentTransactions,
} from "../../data/mockPaymentRecords";
import { ProfileStackNavigationProp } from "../../types/navigation";
export default function PaymentsScreen() {
const navigation = useNavigation<ProfileStackNavigationProp>();
const handleDeletePaymentMethod = (id: string) => {
alert("Payment method deleted (mock action)");
};
return (
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
<View style={styles.header}>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
>
<Text style={styles.backArrow}>‹</Text>
</TouchableOpacity>
<Text style={styles.title}>Payments</Text>
</View>
{/* Saved Payment Methods */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Saved Payment Methods</Text>
<View style={styles.methodsList}>
{mockPaymentMethods.map((method) => (
<View key={method.id} style={styles.methodCard}>
<View style={styles.methodInfo}>
<Text style={styles.methodType}>
{method.type === "credit" && "Credit Card"}
{method.type === "debit" && "Debit Card"}
{method.type === "upi" && "UPI"}
{method.type === "netbanking" && "Net Banking"}
</Text>
<Text style={styles.methodDetails}>
{method.last4 && `****${method.last4}`}
{method.upiId && method.upiId}
{method.bankName && ` • ${method.bankName}`}
</Text>
{method.isDefault && (
<Text style={styles.defaultBadge}>Default</Text>
)}
</View>
<TouchableOpacity
style={styles.deleteButton}
onPress={() => handleDeletePaymentMethod(method.id)}
>
<Text style={styles.deleteText}>Delete</Text>
</TouchableOpacity>
</View>
))}
</View>
</View>
{/* Previous Payments */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Previous Payments</Text>
<View style={styles.transactionsList}>
{mockPaymentTransactions.map((transaction) => (
<View key={transaction.id} style={styles.transactionCard}>
<View style={styles.transactionInfo}>
<Text style={styles.transactionDescription}>
{transaction.description}
</Text>
<Text style={styles.transactionDate}>
{new Date(transaction.date).toLocaleDateString("en-IN")}
</Text>
<Text style={styles.transactionMethod}>
{transaction.paymentMethod}
</Text>
</View>
<View style={styles.transactionAmount}>
<Text
style={[
styles.amountText,
{
color:
transaction.type === "debit" ? "#FF3B30" : "#34C759",
},
]}
>
{transaction.type === "debit" ? "-" : "+"}₹
{transaction.amount}
</Text>
<Text style={styles.statusText}>{transaction.status}</Text>
</View>
</View>
))}
</View>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f5f5f5",
},
header: {
flexDirection: "row",
alignItems: "center",
padding: 20,
paddingTop: 40,
backgroundColor: "#fff",
},
backButton: {
marginRight: 16,
},
backArrow: {
fontSize: 28,
color: "#007AFF",
fontWeight: "bold",
},
title: {
fontSize: 20,
fontWeight: "bold",
color: "#333",
},
section: {
marginTop: 20,
paddingHorizontal: 20,
marginBottom: 20,
},
sectionTitle: {
fontSize: 18,
fontWeight: "bold",
color: "#333",
marginBottom: 16,
},
methodsList: {
backgroundColor: "#fff",
borderRadius: 12,
overflow: "hidden",
},
methodCard: {
flexDirection: "row",
alignItems: "center",
padding: 16,
borderBottomWidth: 1,
borderBottomColor: "#f0f0f0",
},
methodInfo: {
flex: 1,
},
methodType: {
fontSize: 16,
fontWeight: "600",
color: "#333",
marginBottom: 4,
},
methodDetails: {
fontSize: 14,
color: "#666",
marginBottom: 4,
},
defaultBadge: {
fontSize: 12,
color: "#007AFF",
fontWeight: "600",
},
deleteButton: {
padding: 8,
},
deleteText: {
color: "#FF3B30",
fontSize: 14,
fontWeight: "600",
},
transactionsList: {
gap: 12,
},
transactionCard: {
backgroundColor: "#fff",
padding: 16,
borderRadius: 12,
flexDirection: "row",
alignItems: "center",
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
transactionInfo: {
flex: 1,
},
transactionDescription: {
fontSize: 16,
fontWeight: "600",
color: "#333",
marginBottom: 4,
},
transactionDate: {
fontSize: 14,
color: "#666",
marginBottom: 2,
},
transactionMethod: {
fontSize: 12,
color: "#999",
},
transactionAmount: {
alignItems: "flex-end",
},
amountText: {
fontSize: 16,
fontWeight: "bold",
marginBottom: 4,
},
statusText: {
fontSize: 12,
color: "#666",
textTransform: "capitalize",
},
});
Key Differences:
- Payment type mapping: Convert type codes to readable labels
- Conditional rendering: Show last4 for cards, upiId for UPI
- Color-coded amounts: Red for debits, green for credits
- Rupee symbol: Using ₹ for Indian currency
- Two-section layout: Methods in grouped list, transactions as cards
Takeaway: Payment UIs need clear visual distinction between payment methods and transaction history. Color-coding amounts (red for money out, green for money in) is a universal pattern users understand instantly.
Step 6: Building the Regimen Tab with Progress Tracking
React (Web) - Exercise Programs:
// Regimen.js
function Regimen() {
const [activeTab, setActiveTab] = useState("in-progress");
const filtered = regimens.filter((r) => r.status === activeTab);
return (
<div className="regimen">
<h2>My Regimen List</h2>
<div className="tabs">
<button
className={activeTab === "not-started" ? "active" : ""}
onClick={() => setActiveTab("not-started")}
>
Not Started
</button>
<button
className={activeTab === "in-progress" ? "active" : ""}
onClick={() => setActiveTab("in-progress")}
>
In Progress
</button>
<button
className={activeTab === "completed" ? "active" : ""}
onClick={() => setActiveTab("completed")}
>
Completed
</button>
</div>
<div className="regimen-list">
{filtered.map((regimen) => (
<div key={regimen.id} className="regimen-card">
<div className="header">
<h3>{regimen.name}</h3>
<span className={`status ${regimen.status}`}>
{regimen.status}
</span>
</div>
<p>
{regimen.completedExercises} of {regimen.totalExercises} exercises
</p>
<div className="progress-bar">
<div
className="fill"
style={{
width: `${
(regimen.completedExercises / regimen.totalExercises) * 100
}%`,
}}
/>
</div>
</div>
))}
</div>
</div>
);
}
React Native - ScrollView with Progress Bars:
// src/components/ui-molecules/RegimenTab.tsx
import React, { useState } from "react";
import {
View,
Text,
ScrollView,
StyleSheet,
TouchableOpacity,
} from "react-native";
import { useNavigation } from "@react-navigation/native";
import { mockRegimens } from "../../data/mockRegimen";
import { Regimen, RegimenTabType } from "../../types/regimen";
import { MainTabNavigationProp } from "../../types/navigation";
export default function RegimenTab() {
const navigation = useNavigation<MainTabNavigationProp>();
const [activeTab, setActiveTab] = useState<RegimenTabType>("in-progress");
const filteredRegimens = mockRegimens.filter(
(regimen) => regimen.status === activeTab
);
const handleRegimenPress = (regimen: Regimen) => {
const exercises = regimen.exercises
.map(
(ex) =>
`${ex.name}: ${ex.sets} sets × ${ex.reps} reps${
ex.weight ? ` @ ${ex.weight}kg` : ""
}`
)
.join("\n");
alert(
`Regimen Details: ${regimen.name}\n\nProgress: ${regimen.completedExercises}/${regimen.totalExercises} exercises\n\nExercises:\n${exercises}`
);
};
const tabs = [
{ key: "not-started", label: "Not Started" },
{ key: "in-progress", label: "In Progress" },
{ key: "completed", label: "Completed" },
];
return (
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
<View style={styles.header}>
<Text style={styles.title}>My Regimen List</Text>
</View>
{/* Tabs */}
<View style={styles.tabs}>
{tabs.map((tab) => (
<TouchableOpacity
key={tab.key}
style={[styles.tab, activeTab === tab.key && styles.activeTab]}
onPress={() => setActiveTab(tab.key as RegimenTabType)}
>
<Text
style={[
styles.tabText,
activeTab === tab.key && styles.activeTabText,
]}
>
{tab.label}
</Text>
</TouchableOpacity>
))}
</View>
{/* Regimen List */}
<View style={styles.regimenList}>
{filteredRegimens.length > 0 ? (
filteredRegimens.map((regimen) => (
<TouchableOpacity
key={regimen.id}
style={styles.regimenCard}
onPress={() => handleRegimenPress(regimen)}
>
<View style={styles.regimenHeader}>
<Text style={styles.regimenName}>{regimen.name}</Text>
<View
style={[
styles.statusBadge,
{
backgroundColor:
regimen.status === "completed"
? "#34C759"
: regimen.status === "in-progress"
? "#FF9500"
: "#8E8E93",
},
]}
>
<Text style={styles.statusText}>
{regimen.status === "not-started"
? "Not Started"
: regimen.status === "in-progress"
? "In Progress"
: "Completed"}
</Text>
</View>
</View>
<Text style={styles.progressText}>
{regimen.completedExercises} of {regimen.totalExercises}{" "}
exercises completed
</Text>
<View style={styles.progressBar}>
<View
style={[
styles.progressFill,
{
width: `${
(regimen.completedExercises / regimen.totalExercises) *
100
}%`,
},
]}
/>
</View>
{regimen.startDate && regimen.endDate && (
<Text style={styles.dateText}>
{new Date(regimen.startDate).toLocaleDateString()} -{" "}
{new Date(regimen.endDate).toLocaleDateString()}
</Text>
)}
</TouchableOpacity>
))
) : (
<View style={styles.emptyState}>
<Text style={styles.emptyStateText}>
No{" "}
{activeTab === "not-started"
? "upcoming"
: activeTab === "in-progress"
? "active"
: "completed"}{" "}
regimens
</Text>
</View>
)}
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f5f5f5",
},
header: {
padding: 20,
paddingTop: 50,
backgroundColor: "#fff",
},
title: {
fontSize: 20,
fontWeight: "bold",
color: "#333",
},
tabs: {
flexDirection: "row",
paddingHorizontal: 20,
marginBottom: 20,
gap: 8,
marginTop: 20,
},
tab: {
flex: 1,
paddingVertical: 12,
paddingHorizontal: 16,
borderRadius: 8,
backgroundColor: "#fff",
alignItems: "center",
borderWidth: 1,
borderColor: "#E5E5EA",
},
activeTab: {
backgroundColor: "#007AFF",
borderColor: "#007AFF",
},
tabText: {
fontSize: 14,
fontWeight: "600",
color: "#666",
},
activeTabText: {
color: "#fff",
},
regimenList: {
paddingHorizontal: 20,
},
regimenCard: {
backgroundColor: "#fff",
padding: 16,
borderRadius: 12,
marginBottom: 16,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
regimenHeader: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "flex-start",
marginBottom: 12,
},
regimenName: {
fontSize: 16,
fontWeight: "600",
color: "#333",
flex: 1,
marginRight: 12,
},
statusBadge: {
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
},
statusText: {
color: "#fff",
fontSize: 12,
fontWeight: "600",
},
progressText: {
fontSize: 14,
color: "#666",
marginBottom: 8,
},
progressBar: {
height: 6,
backgroundColor: "#E5E5EA",
borderRadius: 3,
marginBottom: 12,
},
progressFill: {
height: "100%",
backgroundColor: "#007AFF",
borderRadius: 3,
},
dateText: {
fontSize: 12,
color: "#999",
},
emptyState: {
padding: 40,
alignItems: "center",
},
emptyStateText: {
fontSize: 14,
color: "#999",
},
});
Key Differences:
- Three-way tabs: Not Started, In Progress, Completed for exercise programs
- Progress bars: Visual progress using nested Views with percentage width
- Status color system: Gray (not started), Orange (in progress), Green (completed)
- Date range display: Show program start and end dates
- Exercise summary: Format exercise details (sets × reps @ weight) in alert
- Empty state handling: Context-aware empty messages per tab
Takeaway: Regimen/exercise tracking needs clear progress visualization. Progress bars with percentage calculation give users immediate feedback on completion status. Three-tab structure (not started, in progress, completed) is a common pattern for task-based UIs.
Step 7: Nested Navigation with ProfileStackNavigator
React (Web) - React Router:
// App.js
import { BrowserRouter, Routes, Route } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<MainLayout />}>
<Route path="profile" element={<ProfileScreen />} />
<Route path="profile/clinical" element={<ClinicalRecords />} />
<Route path="profile/payments" element={<Payments />} />
<Route path="profile/appointments" element={<Appointments />} />
</Route>
</Routes>
</BrowserRouter>
);
}
React Native - Stack Navigator:
// src/navigation/ProfileStackNavigator.tsx
import React from "react";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { ProfileStackParamList } from "../types/navigation";
import ProfileScreen from "../components/screens/ProfileScreen";
import ClinicalRecordsScreen from "../components/screens/ClinicalRecordsScreen";
import TimelineTab from "../components/screens/TimelineTab";
import PaymentsScreen from "../components/screens/PaymentScreen";
const Stack = createNativeStackNavigator<ProfileStackParamList>();
export default function ProfileStackNavigator() {
return (
<Stack.Navigator
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="ProfileMain" component={ProfileScreen} />
<Stack.Screen name="ClinicalRecords" component={ClinicalRecordsScreen} />
<Stack.Screen name="MyAppointments" component={TimelineTab} />
<Stack.Screen name="Payments" component={PaymentsScreen} />
</Stack.Navigator>
);
}
// src/types/navigation.ts (additions)
export type ProfileStackParamList = {
ProfileMain: undefined;
ClinicalRecords: undefined;
MyAppointments: undefined;
Payments: undefined;
};
export type ProfileStackNavigationProp =
import("@react-navigation/native-stack").NativeStackNavigationProp<ProfileStackParamList>;
Key Differences:
- Component reuse: TimelineTab component reused for "My Appointments"
- Type-safe navigation: TypeScript enforces correct screen names
- Nested in tab: Profile stack lives inside MainTabNavigator
- Custom headers: Each screen manages its own header
Takeaway: React Navigation's nested stack pattern lets you create isolated flows within tabs. Reusing components (like TimelineTab for appointments) reduces code duplication and ensures consistent UX.
Step 8: Adding Regimen to Main Tab Navigator
// src/navigation/MainTabNavigator.tsx
import React from "react";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { MainTabParamList } from "../types/navigation";
import HomeStackNavigator from "./HomeStackNavigator";
import TimelineTab from "../components/screens/TimelineTab";
import SupportStackNavigator from "./SupportStackNavigator";
import ProfileStackNavigator from "./ProfileStackNavigator";
import RegimenTab from "../components/ui-molecules/RegimenTab";
const Tab = createBottomTabNavigator<MainTabParamList>();
export default function MainTabNavigator() {
return (
<Tab.Navigator
screenOptions={{
headerShown: false,
tabBarActiveTintColor: "#007AFF",
tabBarInactiveTintColor: "#8E8E93",
}}
>
<Tab.Screen
name="Home"
component={HomeStackNavigator}
options={{ tabBarLabel: "Home" }}
/>
<Tab.Screen
name="Timeline"
component={TimelineTab}
options={{ tabBarLabel: "Timeline" }}
/>
<Tab.Screen
name="Regimen"
component={RegimenTab}
options={{ tabBarLabel: "Regimen" }}
/>
<Tab.Screen
name="Support"
component={SupportStackNavigator}
options={{ tabBarLabel: "Support" }}
/>
<Tab.Screen
name="Profile"
component={ProfileStackNavigator}
options={{ tabBarLabel: "Profile" }}
/>
</Tab.Navigator>
);
}
Takeaway: The MainTabNavigator now has 5 tabs: Home, Timeline, Regimen, Support, and Profile. Each tab can be a simple screen (Timeline, Regimen) or a stack navigator (Home, Support, Profile) depending on the complexity of the flow.
Key Learnings
1. Extended User Types for Healthcare
Healthcare apps need more than basic user info:
export interface User {
id?: string;
name: string;
mobile: string;
email: string;
dateOfBirth?: string;
gender?: "male" | "female" | "other";
age?: number;
weight?: number;
height?: number;
bloodGroup?: string;
}
Store health metrics in auth context for easy access across screens.
2. Progress Bar Pattern
Create visual progress indicators with nested Views:
<View style={styles.progressBar}>
<View
style={[styles.progressFill, { width: `${(completed / total) * 100}%` }]}
/>
</View>;
const styles = StyleSheet.create({
progressBar: {
height: 6,
backgroundColor: "#E5E5EA",
borderRadius: 3,
},
progressFill: {
height: "100%",
backgroundColor: "#007AFF",
borderRadius: 3,
},
});
This creates a simple, effective progress bar without external libraries.
3. Payment Method Type Handling
Handle multiple payment types with conditional rendering:
<Text style={styles.methodType}>
{method.type === "credit" && "Credit Card"}
{method.type === "debit" && "Debit Card"}
{method.type === "upi" && "UPI"}
{method.type === "netbanking" && "Net Banking"}
</Text>
<Text style={styles.methodDetails}>
{method.last4 && `****${method.last4}`}
{method.upiId && method.upiId}
{method.bankName && ` • ${method.bankName}`}
</Text>
Each payment type shows different details (cards show last4, UPI shows ID).
4. Color-Coded Transaction Amounts
Use dynamic colors for financial UIs:
<Text
style={[
styles.amountText,
{ color: transaction.type === "debit" ? "#FF3B30" : "#34C759" },
]}
>
{transaction.type === "debit" ? "-" : "+"}₹{transaction.amount}
</Text>
Red for money out, green for money in—a universal pattern.
5. Three-State Status System
Use consistent color coding for statuses:
const STATUS_COLORS = {
"not-started": "#8E8E93", // Gray
"in-progress": "#FF9500", // Orange
"completed": "#34C759", // Green
};
This pattern works for regimens, appointments, and clinical records.
6. Component Reuse Across Navigators
Reuse components in different contexts:
// In ProfileStackNavigator
<Stack.Screen name="MyAppointments" component={TimelineTab} />
// TimelineTab is also used directly in MainTabNavigator
<Tab.Screen name="Timeline" component={TimelineTab} />
Same component, different navigation contexts.
Common Patterns & Best Practices
1. Info Card Pattern
const InfoCard = ({ avatar, name, details }) => (
<View style={styles.infoCard}>
<View style={styles.avatar}>
<Text style={styles.avatarText}>{avatar}</Text>
</View>
<View style={styles.infoContent}>
<Text style={styles.name}>{name}</Text>
{details.map((detail, index) => (
<Text key={index} style={styles.detail}>
{detail}
</Text>
))}
</View>
</View>
);
When to use: Profile headers, user cards, summary displays
2. Menu List Pattern
const MenuList = ({ items }) => (
<View style={styles.menuContainer}>
{items.map((item, index) => (
<TouchableOpacity
key={index}
style={styles.menuItem}
onPress={item.onPress}
>
<View style={styles.menuContent}>
<Text style={styles.menuTitle}>{item.title}</Text>
<Text style={styles.menuSubtitle}>{item.subtitle}</Text>
</View>
<Text style={styles.arrow}>›</Text>
</TouchableOpacity>
))}
</View>
);
When to use: Settings screens, profile navigation, feature lists
3. Section Header Pattern
<View style={styles.section}>
<Text style={styles.sectionTitle}>Section Title</Text>
{/* Section content */}
</View>;
const styles = StyleSheet.create({
section: {
marginTop: 20,
paddingHorizontal: 20,
},
sectionTitle: {
fontSize: 18,
fontWeight: "bold",
color: "#333",
marginBottom: 16,
},
});
When to use: Grouping related content, payment screens, settings
4. Confirmation Alert Pattern
const handleLogout = () => {
Alert.alert("Logout", "Are you sure you want to log out?", [
{ text: "Cancel", style: "cancel" },
{
text: "Logout",
style: "destructive",
onPress: () => setUser(null),
},
]);
};
When to use: Logout, delete actions, irreversible operations
5. Tab Filter Pattern
const tabs = [
{ key: "not-started", label: "Not Started" },
{ key: "in-progress", label: "In Progress" },
{ key: "completed", label: "Completed" },
];
{
tabs.map((tab) => (
<TouchableOpacity
key={tab.key}
style={[styles.tab, activeTab === tab.key && styles.activeTab]}
onPress={() => setActiveTab(tab.key)}
>
<Text
style={[styles.tabText, activeTab === tab.key && styles.activeTabText]}
>
{tab.label}
</Text>
</TouchableOpacity>
));
}
When to use: Filtering lists, status-based views, category selection
Common Questions (If You're Coming from React)
Q: Should I use a form library for the profile edit screen?
A: For simple forms (4-5 fields), plain useState is fine. For complex forms with validation, consider react-hook-form or formik. The UserDetailsScreen uses plain state since it's straightforward.
Q: How do I handle payment integration in React Native?
A: Use Razorpay, Stripe, or PayU SDKs. For Razorpay: npm install react-native-razorpay. Each has React Native-specific packages. Will cover in backend integration phase.
Q: Can I use charts for health metrics?
A: Yes! react-native-chart-kit or victory-native work great. We used a simple bar chart in Phase 3. For more complex visualizations, consider react-native-gifted-charts.
Q: How do I persist user data between app launches?
A: Use @react-native-async-storage/async-storage. Store user data when logging in, retrieve on app launch:
import AsyncStorage from "@react-native-async-storage/async-storage";
// Save
await AsyncStorage.setItem("user", JSON.stringify(user));
// Load
const stored = await AsyncStorage.getItem("user");
if (stored) setUser(JSON.parse(stored));
Q: Should clinical records have a details screen or just alerts?
A: For production, create a dedicated ClinicalRecordDetailsScreen. I used alerts for simplicity during learning. The pattern would be:
navigation.navigate("RecordDetails", { recordId: record.id });
Q: How do I handle secure storage for payment data?
A: Never store full card numbers client-side. Store only tokens from payment providers. Use react-native-keychain for sensitive data:
import * as Keychain from "react-native-keychain";
await Keychain.setGenericPassword("paymentToken", token);
Q: Can I animate the progress bars?
A: Yes! Use react-native-reanimated for smooth animations:
import Animated, {
useAnimatedStyle,
withTiming,
} from "react-native-reanimated";
const animatedStyle = useAnimatedStyle(() => ({
width: withTiming(`${progress}%`, { duration: 500 }),
}));
Q: How do I handle different date formats?
A: Use toLocaleDateString with locale options:
// Indian format
new Date(date).toLocaleDateString("en-IN");
// Custom format
new Date(date).toLocaleDateString("en-IN", {
day: "numeric",
month: "long",
year: "numeric",
});
Resources That Helped Me
- React Navigation - Nesting Navigators - Nested stack patterns
- React Native - Alert - Native confirmation dialogs
- TypeScript Handbook - Union Types - Payment type patterns
- iOS Human Interface Guidelines - Progress Indicators - Progress bar design
- React Native - ScrollView - Scrollable content areas
Code Repository
All the code from Phase 7 is available on GitHub:
- physio-care-react-native-first-project - Complete source code
Final Thoughts
Phase 7 was about completing the patient-facing experience—building out the profile, medical records, payments, and exercise programs. This phase taught me how to structure complex account areas in mobile apps and handle diverse data types (clinical records, payment methods, exercise regimens).
The biggest learning was understanding information hierarchy in mobile. On web, we might show everything on one dashboard. On mobile, we show a summary card with user info, then let users drill into specific sections (clinical records, payments, etc.). This "progressive disclosure" pattern keeps screens focused and scannable.
What surprised me most was how much the extended User type changed the app's feel. Adding age, weight, height, and blood group transformed the profile from a simple account page to a proper healthcare patient portal. These small additions make the app feel domain-specific and professional.
The progress bar pattern for regimens was satisfying to build. Using nested Views with percentage-based widths is simple but effective. No external libraries needed—just CSS-like styling with dynamic calculations.
Reusing the TimelineTab component for "My Appointments" in the Profile section showed the power of React's component model. Same component, same behavior, different navigation context. This keeps the codebase DRY and ensures consistent UX.
Next up: Connecting everything to a real GraphQL backend with Apollo Client (Phase 8). Mock data has been great for learning UI patterns, but seeing real data flow through the app will be incredibly satisfying.
If you're a React developer, the transition to building profile and payment UIs will feel familiar. The patterns are the same—info cards, lists, progress indicators. The challenges are mobile-specific: handling different payment types, creating progress bars without CSS, and managing nested navigation. But once you learn these patterns, you'll find mobile development surprisingly intuitive.
Top comments (0)