DEV Community

Cover image for # Phase 7: Building the Profile Area - Patient Details, Clinical Records, Payments & Regimen
Md Enayetur Rahman
Md Enayetur Rahman

Posted on

# Phase 7: Building the Profile Area - Patient Details, Clinical Records, Payments & Regimen

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:

  1. Profile Screen with patient info card and navigation menu
  2. Extended User type with health metrics (age, weight, height, blood group)
  3. Clinical Records screen with visit summaries and status badges
  4. Payments screen with saved methods and transaction history
  5. Regimen Tab with exercise programs and progress tracking
  6. Nested navigation using ProfileStackNavigator
  7. Component reuse - Timeline reused for "My Appointments"
  8. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Key Differences:

  1. Dedicated stack navigator: Profile features get their own navigation stack
  2. Extended user type: Health metrics stored in auth context
  3. Regimen as main tab: Exercise programs accessible from bottom navigation
  4. 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,
  },
];
Enter fullscreen mode Exit fullscreen mode

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";
}
Enter fullscreen mode Exit fullscreen mode
// 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";
}
Enter fullscreen mode Exit fullscreen mode
// 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";
Enter fullscreen mode Exit fullscreen mode
// 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;
}
Enter fullscreen mode Exit fullscreen mode

Benefits of These Types:

  1. Payment flexibility: Union types handle cards, UPI, and netbanking
  2. Optional fields: last4 for cards, upiId for UPI payments
  3. Status tracking: Three-level status for regimens and transactions
  4. Exercise structure: Sets, reps, and optional weight/duration
  5. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode
/* 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;
}
Enter fullscreen mode Exit fullscreen mode

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",
  },
});
Enter fullscreen mode Exit fullscreen mode

Key Differences:

  1. Alert.alert for confirmation: Native confirmation dialog for logout
  2. Destructive style: iOS-specific red button for dangerous actions
  3. Avatar from initial: Generate avatar from first letter of name
  4. Menu with subtitles: Two-line menu items for better context
  5. Arrow indicator: Visual cue that items are tappable
  6. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

Key Differences:

  1. Status color coding: Green for completed, orange for ongoing
  2. Date formatting: Using toLocaleDateString with Indian locale
  3. Visual hierarchy: Consultant name highlighted in blue
  4. Treatment plan section: Labeled clearly for medical context
  5. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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",
  },
});
Enter fullscreen mode Exit fullscreen mode

Key Differences:

  1. Payment type mapping: Convert type codes to readable labels
  2. Conditional rendering: Show last4 for cards, upiId for UPI
  3. Color-coded amounts: Red for debits, green for credits
  4. Rupee symbol: Using ₹ for Indian currency
  5. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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",
  },
});
Enter fullscreen mode Exit fullscreen mode

Key Differences:

  1. Three-way tabs: Not Started, In Progress, Completed for exercise programs
  2. Progress bars: Visual progress using nested Views with percentage width
  3. Status color system: Gray (not started), Orange (in progress), Green (completed)
  4. Date range display: Show program start and end dates
  5. Exercise summary: Format exercise details (sets × reps @ weight) in alert
  6. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode
// 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>;
Enter fullscreen mode Exit fullscreen mode

Key Differences:

  1. Component reuse: TimelineTab component reused for "My Appointments"
  2. Type-safe navigation: TypeScript enforces correct screen names
  3. Nested in tab: Profile stack lives inside MainTabNavigator
  4. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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} />
Enter fullscreen mode Exit fullscreen mode

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>
);
Enter fullscreen mode Exit fullscreen mode

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>
);
Enter fullscreen mode Exit fullscreen mode

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

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),
    },
  ]);
};
Enter fullscreen mode Exit fullscreen mode

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>
  ));
}
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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 });
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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 }),
}));
Enter fullscreen mode Exit fullscreen mode

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",
});
Enter fullscreen mode Exit fullscreen mode

Resources That Helped Me


Code Repository

All the code from Phase 7 is available on GitHub:


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)