Files
FrenoCorp/Lendair/Models/FamilyPlan.swift
Michael Freno 57a460761a FRE-4665: Implement Phase 3 AI training plans and premium features
- Models: TrainingPlan, Race, FamilyPlan, BeginnerMode, CommunityEvent
- Services: 5 service layers with protocol-based architecture
- ViewModels: 5 view models with @MainActor ObservableObject pattern
- Views: 10 SwiftUI views for all Phase 3 features
- Updated README with full Phase 3 documentation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-03 15:21:01 -04:00

218 lines
4.9 KiB
Swift

import Foundation
import SwiftUI
// MARK: - Family Plan
struct FamilyPlan: Identifiable, Equatable, Codable {
let id: String
let ownerId: String
let ownerName: String
let members: [FamilyMember]
let maxMembers: Int
let subscriptionStatus: SubscriptionStatus
let renewalDate: Date?
let createdAt: Date
enum CodingKeys: String, CodingKey {
case id, ownerId, ownerName, members, maxMembers, subscriptionStatus, renewalDate, createdAt
}
init(
id: String,
ownerId: String,
ownerName: String,
members: [FamilyMember],
maxMembers: Int,
subscriptionStatus: SubscriptionStatus,
renewalDate: Date?,
createdAt: Date
) {
self.id = id
self.ownerId = ownerId
self.ownerName = ownerName
self.members = members
self.maxMembers = maxMembers
self.subscriptionStatus = subscriptionStatus
self.renewalDate = renewalDate
self.createdAt = createdAt
}
static func == (lhs: FamilyPlan, rhs: FamilyPlan) -> Bool {
lhs.id == rhs.id
}
var availableSlots: Int {
maxMembers - members.count
}
var isActive: Bool {
subscriptionStatus == .active
}
}
// MARK: - Family Member
struct FamilyMember: Identifiable, Equatable, Codable {
let id: String
let name: String
let email: String
let role: MemberRole
let joinedAt: Date
let avatarUrl: String?
var isPrimary: Bool
var totalDistanceKm: Double
var totalWorkouts: Int
var weeklyDistanceKm: Double
var weeklyWorkouts: Int
enum CodingKeys: String, CodingKey {
case id, name, email, role, joinedAt, avatarUrl, isPrimary, totalDistanceKm, totalWorkouts, weeklyDistanceKm, weeklyWorkouts
}
init(
id: String,
name: String,
email: String,
role: MemberRole,
joinedAt: Date,
avatarUrl: String?,
isPrimary: Bool,
totalDistanceKm: Double,
totalWorkouts: Int,
weeklyDistanceKm: Double,
weeklyWorkouts: Int
) {
self.id = id
self.name = name
self.email = email
self.role = role
self.joinedAt = joinedAt
self.avatarUrl = avatarUrl
self.isPrimary = isPrimary
self.totalDistanceKm = totalDistanceKm
self.totalWorkouts = totalWorkouts
self.weeklyDistanceKm = weeklyDistanceKm
self.weeklyWorkouts = weeklyWorkouts
}
static func == (lhs: FamilyMember, rhs: FamilyMember) -> Bool {
lhs.id == rhs.id
}
}
// MARK: - Member Role
enum MemberRole: String, CaseIterable, Codable {
case owner
case member
case pending
var displayName: String {
switch self {
case .owner: return "Owner"
case .member: return "Member"
case .pending: return "Pending"
}
}
var icon: String {
switch self {
case .owner: return "star.fill"
case .member: return "person.fill"
case .pending: return "person.crop.circle.badge.exclamationmark"
}
}
}
// MARK: - Subscription Status
enum SubscriptionStatus: String, CaseIterable, Codable {
case active
case expired
case cancelled
case pending
var displayName: String {
switch self {
case .active: return "Active"
case .expired: return "Expired"
case .cancelled: return "Cancelled"
case .pending: return "Pending"
}
}
var color: Color {
switch self {
case .active: return .green
case .expired: return .red
case .cancelled: return .orange
case .pending: return .yellow
}
}
}
// MARK: - Family Leaderboard Entry
struct FamilyLeaderboardEntry: Identifiable, Codable {
let id: String
let memberId: String
let memberName: String
let avatarUrl: String?
let metric: LeaderboardMetric
let value: Double
let rank: Int
}
// MARK: - Leaderboard Metric
enum LeaderboardMetric: String, CaseIterable, Codable {
case distance
case workouts
case streak
var displayName: String {
switch self {
case .distance: return "Distance"
case .workouts: return "Workouts"
case .streak: return "Streak"
}
}
var unit: String {
switch self {
case .distance: return "km"
case .workouts: return ""
case .streak: return "days"
}
}
}
// MARK: - Invite Member Request
struct InviteMemberRequest: Encodable {
let email: String
let name: String
}
// MARK: - API Response Types
struct FamilyPlanDetailResponse: Decodable {
let plan: FamilyPlan
}
struct InviteMemberResponse: Decodable {
let success: Bool
let invitationId: String
let memberEmail: String
}
struct RemoveMemberResponse: Decodable {
let success: Bool
let memberId: String
}
struct FamilyLeaderboardResponse: Decodable {
let entries: [FamilyLeaderboardEntry]
let metric: LeaderboardMetric
}