diff --git a/iosApp/DESIGN_SYSTEM.md b/iosApp/DESIGN_SYSTEM.md new file mode 100644 index 0000000..ae092a4 --- /dev/null +++ b/iosApp/DESIGN_SYSTEM.md @@ -0,0 +1,205 @@ +# MyCrib iOS Design System + +## Overview +This document outlines the modern, sleek design system implemented for the MyCrib iOS app. + +## Design Philosophy +- **Modern & Clean**: Minimalist approach with ample white space +- **Consistent**: Reusable components with unified styling +- **Accessible**: High contrast ratios and readable typography +- **Delightful**: Subtle animations and gradient accents + +## Color Palette + +### Primary Colors +- **Primary**: `#2563EB` - Modern blue for primary actions +- **Primary Light**: `#3B82F6` - Lighter variant for gradients +- **Primary Dark**: `#1E40AF` - Darker variant for pressed states + +### Accent Colors +- **Accent**: `#8B5CF6` - Purple for special highlights +- **Accent Light**: `#A78BFA` - Lighter purple for gradients + +### Semantic Colors +- **Success**: `#10B981` - Green for completed/success states +- **Warning**: `#F59E0B` - Orange for in-progress/warning states +- **Error**: `#EF4444` - Red for errors and destructive actions +- **Info**: `#3B82F6` - Blue for informational content + +### Neutral Colors +- **Background**: `#F9FAFB` - Light gray for app background +- **Surface**: `#FFFFFF` - White for cards and surfaces +- **Surface Secondary**: `#F3F4F6` - Light gray for secondary surfaces +- **Text Primary**: `#111827` - Near black for primary text +- **Text Secondary**: `#6B7280` - Medium gray for secondary text +- **Text Tertiary**: `#9CA3AF` - Light gray for tertiary text +- **Border**: `#E5E7EB` - Light gray for borders +- **Border Light**: `#F3F4F6` - Very light gray for subtle borders + +## Typography + +### Display (Hero Sections) +- **Display Large**: 57pt, Bold, Rounded +- **Display Medium**: 45pt, Bold, Rounded +- **Display Small**: 36pt, Bold, Rounded + +### Headline (Section Headers) +- **Headline Large**: 32pt, Bold, Rounded +- **Headline Medium**: 28pt, Semibold, Rounded +- **Headline Small**: 24pt, Semibold, Rounded + +### Title (Card Titles) +- **Title Large**: 22pt, Semibold +- **Title Medium**: 18pt, Semibold +- **Title Small**: 16pt, Semibold + +### Body (Main Content) +- **Body Large**: 17pt, Regular +- **Body Medium**: 15pt, Regular +- **Body Small**: 13pt, Regular + +### Label (Labels & Captions) +- **Label Large**: 14pt, Medium +- **Label Medium**: 12pt, Medium +- **Label Small**: 11pt, Medium + +### Caption +- **Caption**: 12pt, Regular + +## Spacing Scale +- **XXS**: 4pt +- **XS**: 8pt +- **SM**: 12pt +- **MD**: 16pt +- **LG**: 24pt +- **XL**: 32pt +- **XXL**: 48pt +- **XXXL**: 64pt + +## Border Radius +- **XS**: 4pt +- **SM**: 8pt +- **MD**: 12pt +- **LG**: 16pt +- **XL**: 20pt +- **XXL**: 24pt +- **Full**: 9999pt (Circular) + +## Shadows +- **SM**: rgba(0,0,0,0.05), radius: 2, y: 1 +- **MD**: rgba(0,0,0,0.1), radius: 4, y: 2 +- **LG**: rgba(0,0,0,0.1), radius: 8, y: 4 +- **XL**: rgba(0,0,0,0.15), radius: 16, y: 8 + +## Component Patterns + +### Cards +- White background (`AppColors.surface`) +- Rounded corners (16pt or 20pt) +- Subtle shadow (`AppShadow.md` or `AppShadow.lg`) +- Padding: 16-32pt depending on content + +### Buttons +- **Primary**: Gradient background, white text, 56pt height +- **Secondary**: Light gray background, primary color text, 56pt height +- Rounded corners: 12pt +- Pressed state: Scale to 0.98 + +### Text Fields +- Light gray background (`AppColors.surfaceSecondary`) +- 16pt padding +- 12pt rounded corners +- Focused state: Primary color border + subtle shadow +- Icon prefix for visual context + +### Navigation Cards +- Gradient icon background in rounded rectangle +- Clear hierarchy: Title (semibold) + Subtitle (secondary color) +- Chevron indicator +- Hover/tap feedback + +## Modernization Highlights + +### Login Screen +- Circular gradient app icon with shadow +- Card-based form layout +- Animated focus states on input fields +- Gradient button with disabled states +- Clean error messaging + +### Home Screen +- Personalized greeting header +- Stats overview card with icon badges +- Modern navigation cards with gradient icons +- Smooth scroll experience + +### Components +- **OverviewCard**: Stats grid with dividers and icon badges +- **StatView**: Circular icon backgrounds with modern typography +- **HomeNavigationCard**: Gradient icons with clean layout +- **Design System**: Centralized colors, typography, and spacing + +## Usage + +### Importing the Design System +```swift +import SwiftUI + +// Colors +.foregroundColor(AppColors.primary) +.background(AppColors.surface) + +// Typography +.font(AppTypography.headlineSmall) + +// Spacing +.padding(AppSpacing.md) + +// Radius +.cornerRadius(AppRadius.lg) + +// Shadows +.shadow(color: AppShadow.lg.color, radius: AppShadow.lg.radius, y: AppShadow.lg.y) + +// Gradients +.fill(AppColors.primaryGradient) +``` + +### Button Styles +```swift +Button("Action") { + // action +} +.buttonStyle(PrimaryButtonStyle()) + +Button("Cancel") { + // action +} +.buttonStyle(SecondaryButtonStyle()) +``` + +### Card Style +```swift +VStack { + // content +} +.cardStyle() // Applies background, corners, and shadow +``` + +## Future Enhancements +- Dark mode support with adaptive colors +- Additional component styles (chips, badges, alerts) +- Animation utilities for transitions +- Accessibility utilities (dynamic type, VoiceOver) +- Custom SF Symbols integration + +## Migration Guide +When updating existing views: + +1. Replace hardcoded colors with `AppColors.*` +2. Replace hardcoded fonts with `AppTypography.*` +3. Replace hardcoded spacing with `AppSpacing.*` +4. Use `cardStyle()` modifier for cards +5. Apply button styles for consistency +6. Add gradients to prominent UI elements +7. Ensure proper spacing hierarchy diff --git a/iosApp/iosApp/Design/DesignSystem.swift b/iosApp/iosApp/Design/DesignSystem.swift new file mode 100644 index 0000000..4bc4cbb --- /dev/null +++ b/iosApp/iosApp/Design/DesignSystem.swift @@ -0,0 +1,225 @@ +import SwiftUI + +// MARK: - Design System +// Modern, sleek design system for MyCrib + +struct AppColors { + // Primary Colors - Modern blue gradient + static let primary = Color(hex: "2563EB") ?? .blue + static let primaryLight = Color(hex: "3B82F6") ?? .blue + static let primaryDark = Color(hex: "1E40AF") ?? .blue + + // Accent Colors + static let accent = Color(hex: "8B5CF6") ?? .purple + static let accentLight = Color(hex: "A78BFA") ?? .purple + + // Semantic Colors + static let success = Color(hex: "10B981") ?? .green + static let warning = Color(hex: "F59E0B") ?? .orange + static let error = Color(hex: "EF4444") ?? .red + static let info = Color(hex: "3B82F6") ?? .blue + + // Neutral Colors - Modern grays + static let background = Color(hex: "F9FAFB") ?? Color(.systemGroupedBackground) + static let surface = Color.white + static let surfaceSecondary = Color(hex: "F3F4F6") ?? Color(.secondarySystemGroupedBackground) + + static let textPrimary = Color(hex: "111827") ?? Color(.label) + static let textSecondary = Color(hex: "6B7280") ?? Color(.secondaryLabel) + static let textTertiary = Color(hex: "9CA3AF") ?? Color(.tertiaryLabel) + + static let border = Color(hex: "E5E7EB") ?? Color(.separator) + static let borderLight = Color(hex: "F3F4F6") ?? Color(.separator) + + // Task Status Colors + static let taskUpcoming = Color(hex: "3B82F6") ?? .blue + static let taskInProgress = Color(hex: "F59E0B") ?? .orange + static let taskCompleted = Color(hex: "10B981") ?? .green + static let taskCanceled = Color(hex: "6B7280") ?? .gray + static let taskArchived = Color(hex: "9CA3AF") ?? .gray + + // Gradient + static let primaryGradient = LinearGradient( + colors: [primary, primaryLight], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + + static let accentGradient = LinearGradient( + colors: [accent, accentLight], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) +} + +struct AppTypography { + // Display - For hero sections + static let displayLarge = Font.system(size: 57, weight: .bold, design: .rounded) + static let displayMedium = Font.system(size: 45, weight: .bold, design: .rounded) + static let displaySmall = Font.system(size: 36, weight: .bold, design: .rounded) + + // Headline - For section headers + static let headlineLarge = Font.system(size: 32, weight: .bold, design: .rounded) + static let headlineMedium = Font.system(size: 28, weight: .semibold, design: .rounded) + static let headlineSmall = Font.system(size: 24, weight: .semibold, design: .rounded) + + // Title - For card titles + static let titleLarge = Font.system(size: 22, weight: .semibold, design: .default) + static let titleMedium = Font.system(size: 18, weight: .semibold, design: .default) + static let titleSmall = Font.system(size: 16, weight: .semibold, design: .default) + + // Body - For main content + static let bodyLarge = Font.system(size: 17, weight: .regular, design: .default) + static let bodyMedium = Font.system(size: 15, weight: .regular, design: .default) + static let bodySmall = Font.system(size: 13, weight: .regular, design: .default) + + // Label - For labels and captions + static let labelLarge = Font.system(size: 14, weight: .medium, design: .default) + static let labelMedium = Font.system(size: 12, weight: .medium, design: .default) + static let labelSmall = Font.system(size: 11, weight: .medium, design: .default) + + // Caption + static let caption = Font.system(size: 12, weight: .regular, design: .default) +} + +struct AppSpacing { + static let xxs: CGFloat = 4 + static let xs: CGFloat = 8 + static let sm: CGFloat = 12 + static let md: CGFloat = 16 + static let lg: CGFloat = 24 + static let xl: CGFloat = 32 + static let xxl: CGFloat = 48 + static let xxxl: CGFloat = 64 +} + +struct AppRadius { + static let xs: CGFloat = 4 + static let sm: CGFloat = 8 + static let md: CGFloat = 12 + static let lg: CGFloat = 16 + static let xl: CGFloat = 20 + static let xxl: CGFloat = 24 + static let full: CGFloat = 9999 +} + +struct AppShadow { + static let sm = Shadow(color: .black.opacity(0.05), radius: 2, y: 1) + static let md = Shadow(color: .black.opacity(0.1), radius: 4, y: 2) + static let lg = Shadow(color: .black.opacity(0.1), radius: 8, y: 4) + static let xl = Shadow(color: .black.opacity(0.15), radius: 16, y: 8) + + struct Shadow { + let color: Color + let radius: CGFloat + let x: CGFloat + let y: CGFloat + + init(color: Color, radius: CGFloat, x: CGFloat = 0, y: CGFloat) { + self.color = color + self.radius = radius + self.x = x + self.y = y + } + } +} + +// MARK: - View Modifiers + +struct CardStyle: ViewModifier { + var shadow: AppShadow.Shadow = AppShadow.md + var padding: CGFloat = AppSpacing.md + + func body(content: Content) -> some View { + content + .background(AppColors.surface) + .cornerRadius(AppRadius.lg) + .shadow(color: shadow.color, radius: shadow.radius, x: shadow.x, y: shadow.y) + } +} + +struct PrimaryButtonStyle: ButtonStyle { + var isLoading: Bool = false + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(AppTypography.titleSmall) + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .frame(height: 56) + .background( + configuration.isPressed ? AppColors.primaryDark : AppColors.primary + ) + .cornerRadius(AppRadius.md) + .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + .animation(.easeInOut(duration: 0.1), value: configuration.isPressed) + } +} + +struct SecondaryButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(AppTypography.titleSmall) + .foregroundColor(AppColors.primary) + .frame(maxWidth: .infinity) + .frame(height: 56) + .background(AppColors.surfaceSecondary) + .cornerRadius(AppRadius.md) + .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + .animation(.easeInOut(duration: 0.1), value: configuration.isPressed) + } +} + +struct TextFieldStyle: ViewModifier { + func body(content: Content) -> some View { + content + .padding(AppSpacing.md) + .background(AppColors.surfaceSecondary) + .cornerRadius(AppRadius.md) + .overlay( + RoundedRectangle(cornerRadius: AppRadius.md) + .stroke(AppColors.borderLight, lineWidth: 1) + ) + } +} + +// MARK: - View Extensions + +extension View { + func cardStyle(shadow: AppShadow.Shadow = AppShadow.md) -> some View { + modifier(CardStyle(shadow: shadow)) + } + + func textFieldStyle() -> some View { + modifier(TextFieldStyle()) + } +} + +// MARK: - Color Extension for Hex Support + +extension Color { + init?(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + let a, r, g, b: UInt64 + switch hex.count { + case 3: // RGB (12-bit) + (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + case 6: // RGB (24-bit) + (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) + case 8: // ARGB (32-bit) + (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + default: + return nil + } + + self.init( + .sRGB, + red: Double(r) / 255, + green: Double(g) / 255, + blue: Double(b) / 255, + opacity: Double(a) / 255 + ) + } +} diff --git a/iosApp/iosApp/HomeScreenView.swift b/iosApp/iosApp/HomeScreenView.swift index cace404..f2a4189 100644 --- a/iosApp/iosApp/HomeScreenView.swift +++ b/iosApp/iosApp/HomeScreenView.swift @@ -8,21 +8,43 @@ struct HomeScreenView: View { var body: some View { NavigationView { ZStack { - Color(.systemGroupedBackground) + AppColors.background .ignoresSafeArea() if viewModel.isLoading { - ProgressView() + VStack(spacing: AppSpacing.lg) { + ProgressView() + .scaleEffect(1.2) + Text("Loading...") + .font(AppTypography.bodyMedium) + .foregroundColor(AppColors.textSecondary) + } } else { - ScrollView { - VStack(spacing: 20) { + ScrollView(showsIndicators: false) { + VStack(spacing: AppSpacing.xl) { + // Greeting Header + VStack(alignment: .leading, spacing: AppSpacing.xs) { + Text("Hello!") + .font(AppTypography.headlineLarge) + .fontWeight(.bold) + .foregroundColor(AppColors.textPrimary) + + Text("Welcome to MyCrib") + .font(AppTypography.bodyLarge) + .foregroundColor(AppColors.textSecondary) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, AppSpacing.md) + .padding(.top, AppSpacing.md) + // Overview Card if let summary = viewModel.residenceSummary { OverviewCard(summary: summary.summary) + .transition(.scale.combined(with: .opacity)) } // Navigation Cards - VStack(spacing: 16) { + VStack(spacing: AppSpacing.md) { NavigationLink(destination: ResidencesListView()) { HomeNavigationCard( icon: "house.fill", @@ -30,6 +52,7 @@ struct HomeScreenView: View { subtitle: "Manage your properties" ) } + .buttonStyle(PlainButtonStyle()) NavigationLink(destination: AllTasksView()) { HomeNavigationCard( @@ -38,20 +61,26 @@ struct HomeScreenView: View { subtitle: "View and manage all tasks" ) } + .buttonStyle(PlainButtonStyle()) } - .padding(.horizontal) + .padding(.horizontal, AppSpacing.md) } - .padding(.vertical) + .padding(.vertical, AppSpacing.md) } } } .navigationTitle("MyCrib") + .navigationBarTitleDisplayMode(.large) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button(action: { loginViewModel.logout() }) { - Image(systemName: "rectangle.portrait.and.arrow.right") + HStack(spacing: AppSpacing.xs) { + Image(systemName: "rectangle.portrait.and.arrow.right") + .font(.system(size: 18, weight: .semibold)) + } + .foregroundColor(AppColors.error) } } } diff --git a/iosApp/iosApp/Login/LoginView.swift b/iosApp/iosApp/Login/LoginView.swift index 98f59c8..7b417cb 100644 --- a/iosApp/iosApp/Login/LoginView.swift +++ b/iosApp/iosApp/Login/LoginView.swift @@ -20,131 +20,209 @@ struct LoginView: View { var body: some View { NavigationView { - Form { - Section { - VStack(spacing: 16) { - Image(systemName: "house.fill") - .font(.system(size: 60)) - .foregroundStyle(.blue.gradient) + ZStack { + // Background gradient + AppColors.background + .ignoresSafeArea() - Text("MyCrib") - .font(.largeTitle) - .fontWeight(.bold) + ScrollView { + VStack(spacing: AppSpacing.xl) { + Spacer() + .frame(height: AppSpacing.xxxl) - Text("Manage your properties with ease") - .font(.subheadline) - .foregroundColor(.secondary) - } - .frame(maxWidth: .infinity) - .padding(.vertical) - } - .listRowBackground(Color.clear) + // Hero Section + VStack(spacing: AppSpacing.lg) { + // App Icon with gradient + ZStack { + Circle() + .fill(AppColors.primaryGradient) + .frame(width: 100, height: 100) + .shadow(color: AppColors.primary.opacity(0.3), radius: 20, y: 10) - Section { - TextField("Username or Email", text: $viewModel.username) - .textInputAutocapitalization(.never) - .autocorrectionDisabled() - .keyboardType(.emailAddress) - .focused($focusedField, equals: .username) - .submitLabel(.next) - .onSubmit { - focusedField = .password - } - .onChange(of: viewModel.username) { _, _ in - viewModel.clearError() - } - - HStack { - if isPasswordVisible { - TextField("Password", text: $viewModel.password) - .textInputAutocapitalization(.never) - .autocorrectionDisabled() - .focused($focusedField, equals: .password) - .submitLabel(.go) - .onSubmit { - viewModel.login() - } - } else { - SecureField("Password", text: $viewModel.password) - .focused($focusedField, equals: .password) - .submitLabel(.go) - .onSubmit { - viewModel.login() - } - } - - Button(action: { - isPasswordVisible.toggle() - }) { - Image(systemName: isPasswordVisible ? "eye.slash.fill" : "eye.fill") - .foregroundColor(.secondary) - } - .buttonStyle(.plain) - } - .onChange(of: viewModel.password) { _, _ in - viewModel.clearError() - } - } header: { - Text("Account Information") - } - - if let errorMessage = viewModel.errorMessage { - Section { - HStack { - Image(systemName: "exclamationmark.triangle.fill") - .foregroundColor(.red) - Text(errorMessage) - .foregroundColor(.red) - .font(.subheadline) - } - } - } - - Section { - Button(action: viewModel.login) { - HStack { - Spacer() - if viewModel.isLoading { - ProgressView() - } else { - Text("Login") - .fontWeight(.semibold) + Image(systemName: "house.fill") + .font(.system(size: 50, weight: .semibold)) + .foregroundStyle(.white) } - Spacer() - } - } - .disabled(viewModel.isLoading) - } - Section { - HStack { - Spacer() - Button("Forgot Password?") { - showPasswordReset = true - } - .font(.subheadline) - .fontWeight(.semibold) - Spacer() - } - } - .listRowBackground(Color.clear) + VStack(spacing: AppSpacing.xs) { + Text("Welcome Back") + .font(AppTypography.displaySmall) + .foregroundColor(AppColors.textPrimary) - Section { - HStack { - Spacer() - Text("Don't have an account?") - .font(.subheadline) - .foregroundColor(.secondary) - Button("Sign Up") { - showingRegister = true + Text("Sign in to manage your properties") + .font(AppTypography.bodyMedium) + .foregroundColor(AppColors.textSecondary) + } } - .font(.subheadline) - .fontWeight(.semibold) + + // Login Card + VStack(spacing: AppSpacing.lg) { + // Username Field + VStack(alignment: .leading, spacing: AppSpacing.xs) { + Text("Email or Username") + .font(AppTypography.labelLarge) + .foregroundColor(AppColors.textSecondary) + + HStack(spacing: AppSpacing.sm) { + Image(systemName: "envelope.fill") + .foregroundColor(AppColors.textTertiary) + .frame(width: 20) + + TextField("Enter your email", text: $viewModel.username) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .keyboardType(.emailAddress) + .focused($focusedField, equals: .username) + .submitLabel(.next) + .onSubmit { + focusedField = .password + } + .onChange(of: viewModel.username) { _, _ in + viewModel.clearError() + } + } + .padding(AppSpacing.md) + .background(AppColors.surface) + .cornerRadius(AppRadius.md) + .overlay( + RoundedRectangle(cornerRadius: AppRadius.md) + .stroke(focusedField == .username ? AppColors.primary : AppColors.border, lineWidth: 1.5) + ) + .shadow(color: focusedField == .username ? AppColors.primary.opacity(0.1) : .clear, radius: 8) + .animation(.easeInOut(duration: 0.2), value: focusedField) + } + + // Password Field + VStack(alignment: .leading, spacing: AppSpacing.xs) { + Text("Password") + .font(AppTypography.labelLarge) + .foregroundColor(AppColors.textSecondary) + + HStack(spacing: AppSpacing.sm) { + Image(systemName: "lock.fill") + .foregroundColor(AppColors.textTertiary) + .frame(width: 20) + + Group { + if isPasswordVisible { + TextField("Enter your password", text: $viewModel.password) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .focused($focusedField, equals: .password) + .submitLabel(.go) + .onSubmit { + viewModel.login() + } + } else { + SecureField("Enter your password", text: $viewModel.password) + .focused($focusedField, equals: .password) + .submitLabel(.go) + .onSubmit { + viewModel.login() + } + } + } + + Button(action: { + isPasswordVisible.toggle() + }) { + Image(systemName: isPasswordVisible ? "eye.slash.fill" : "eye.fill") + .foregroundColor(AppColors.textTertiary) + .frame(width: 20) + } + } + .padding(AppSpacing.md) + .background(AppColors.surface) + .cornerRadius(AppRadius.md) + .overlay( + RoundedRectangle(cornerRadius: AppRadius.md) + .stroke(focusedField == .password ? AppColors.primary : AppColors.border, lineWidth: 1.5) + ) + .shadow(color: focusedField == .password ? AppColors.primary.opacity(0.1) : .clear, radius: 8) + .animation(.easeInOut(duration: 0.2), value: focusedField) + .onChange(of: viewModel.password) { _, _ in + viewModel.clearError() + } + } + + // Forgot Password + HStack { + Spacer() + Button("Forgot Password?") { + showPasswordReset = true + } + .font(AppTypography.labelLarge) + .foregroundColor(AppColors.primary) + } + + // Error Message + if let errorMessage = viewModel.errorMessage { + HStack(spacing: AppSpacing.sm) { + Image(systemName: "exclamationmark.circle.fill") + .foregroundColor(AppColors.error) + Text(errorMessage) + .font(AppTypography.bodySmall) + .foregroundColor(AppColors.error) + Spacer() + } + .padding(AppSpacing.md) + .background(AppColors.error.opacity(0.1)) + .cornerRadius(AppRadius.md) + } + + // Login Button + Button(action: viewModel.login) { + HStack(spacing: AppSpacing.sm) { + if viewModel.isLoading { + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: .white)) + } + Text(viewModel.isLoading ? "Signing In..." : "Sign In") + .font(AppTypography.titleSmall) + .fontWeight(.semibold) + } + .frame(maxWidth: .infinity) + .frame(height: 56) + .foregroundColor(.white) + .background( + viewModel.isLoading || viewModel.username.isEmpty || viewModel.password.isEmpty + ? AppColors.textTertiary + : AppColors.primaryGradient + ) + .cornerRadius(AppRadius.md) + .shadow( + color: viewModel.username.isEmpty || viewModel.password.isEmpty ? .clear : AppColors.primary.opacity(0.3), + radius: 10, + y: 5 + ) + } + .disabled(viewModel.isLoading || viewModel.username.isEmpty || viewModel.password.isEmpty) + + // Sign Up Link + HStack(spacing: AppSpacing.xs) { + Text("Don't have an account?") + .font(AppTypography.bodyMedium) + .foregroundColor(AppColors.textSecondary) + + Button("Sign Up") { + showingRegister = true + } + .font(AppTypography.bodyMedium) + .fontWeight(.semibold) + .foregroundColor(AppColors.primary) + } + } + .padding(AppSpacing.xl) + .background(AppColors.surface) + .cornerRadius(AppRadius.xxl) + .shadow(color: .black.opacity(0.08), radius: 20, y: 10) + .padding(.horizontal, AppSpacing.lg) + Spacer() } } - .listRowBackground(Color.clear) } + .navigationBarHidden(true) .onChange(of: viewModel.isAuthenticated) { _, isAuth in if isAuth { print("isAuthenticated changed to true, isVerified = \(viewModel.isVerified)") @@ -154,7 +232,6 @@ struct LoginView: View { showVerification = true } } else { - // User logged out, dismiss main tab print("isAuthenticated changed to false, dismissing main tab") showMainTab = false showVerification = false @@ -174,11 +251,9 @@ struct LoginView: View { .fullScreenCover(isPresented: $showVerification) { VerifyEmailView( onVerifySuccess: { - // After verification, show main tab view viewModel.isVerified = true }, onLogout: { - // Logout and dismiss verification screen viewModel.logout() showVerification = false showMainTab = false @@ -192,7 +267,6 @@ struct LoginView: View { PasswordResetFlow(resetToken: resetToken) } .onChange(of: resetToken) { _, token in - // When deep link token arrives, show password reset if token != nil { showPasswordReset = true } diff --git a/iosApp/iosApp/Subviews/Common/HomeNavigationCard.swift b/iosApp/iosApp/Subviews/Common/HomeNavigationCard.swift index 5997946..eb62879 100644 --- a/iosApp/iosApp/Subviews/Common/HomeNavigationCard.swift +++ b/iosApp/iosApp/Subviews/Common/HomeNavigationCard.swift @@ -6,31 +6,41 @@ struct HomeNavigationCard: View { let subtitle: String var body: some View { - HStack(spacing: 16) { - Image(systemName: icon) - .font(.system(size: 36)) - .foregroundColor(.blue) - .frame(width: 60) + HStack(spacing: AppSpacing.md) { + // Icon with gradient background + ZStack { + RoundedRectangle(cornerRadius: AppRadius.md) + .fill(AppColors.primaryGradient) + .frame(width: 60, height: 60) + .shadow(color: AppColors.primary.opacity(0.3), radius: 8, y: 4) - VStack(alignment: .leading, spacing: 4) { + Image(systemName: icon) + .font(.system(size: 28, weight: .semibold)) + .foregroundColor(.white) + } + + // Text Content + VStack(alignment: .leading, spacing: AppSpacing.xxs) { Text(title) - .font(.title3) + .font(AppTypography.titleMedium) .fontWeight(.semibold) - .foregroundColor(.primary) + .foregroundColor(AppColors.textPrimary) Text(subtitle) - .font(.subheadline) - .foregroundColor(.secondary) + .font(AppTypography.bodySmall) + .foregroundColor(AppColors.textSecondary) } Spacer() + // Chevron Image(systemName: "chevron.right") - .foregroundColor(.secondary) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(AppColors.textTertiary) } - .padding(20) - .background(Color(.systemBackground)) - .cornerRadius(12) - .shadow(color: Color.black.opacity(0.05), radius: 5, x: 0, y: 2) + .padding(AppSpacing.lg) + .background(AppColors.surface) + .cornerRadius(AppRadius.lg) + .shadow(color: AppShadow.md.color, radius: AppShadow.md.radius, x: AppShadow.md.x, y: AppShadow.md.y) } } diff --git a/iosApp/iosApp/Subviews/Common/OverviewCard.swift b/iosApp/iosApp/Subviews/Common/OverviewCard.swift index 3ac53e8..4e17809 100644 --- a/iosApp/iosApp/Subviews/Common/OverviewCard.swift +++ b/iosApp/iosApp/Subviews/Common/OverviewCard.swift @@ -5,39 +5,61 @@ struct OverviewCard: View { let summary: OverallSummary var body: some View { - VStack(spacing: 16) { + VStack(spacing: AppSpacing.lg) { + // Header HStack { - Image(systemName: "chart.bar.fill") - .font(.title3) - Text("Overview") - .font(.title2) - .fontWeight(.bold) + HStack(spacing: AppSpacing.sm) { + ZStack { + Circle() + .fill(AppColors.primaryGradient) + .frame(width: 44, height: 44) + + Image(systemName: "chart.bar.fill") + .font(.system(size: 20, weight: .semibold)) + .foregroundColor(.white) + } + + Text("Overview") + .font(AppTypography.headlineSmall) + .foregroundColor(AppColors.textPrimary) + } Spacer() } - HStack(spacing: 40) { + // Stats Grid + HStack(spacing: AppSpacing.md) { StatView( icon: "house.fill", value: "\(summary.totalResidences)", - label: "Properties" + label: "Properties", + color: AppColors.primary ) + Divider() + .frame(height: 60) + StatView( icon: "list.bullet", value: "\(summary.totalTasks)", - label: "Total Tasks" + label: "Total Tasks", + color: AppColors.info ) + Divider() + .frame(height: 60) + StatView( icon: "clock.fill", value: "\(summary.totalPending)", - label: "Pending" + label: "Pending", + color: AppColors.warning ) } } - .padding(20) - .background(Color.blue.opacity(0.1)) - .cornerRadius(16) - .padding(.horizontal) + .padding(AppSpacing.xl) + .background(AppColors.surface) + .cornerRadius(AppRadius.xl) + .shadow(color: AppShadow.lg.color, radius: AppShadow.lg.radius, x: AppShadow.lg.x, y: AppShadow.lg.y) + .padding(.horizontal, AppSpacing.md) } } diff --git a/iosApp/iosApp/Subviews/Common/StatView.swift b/iosApp/iosApp/Subviews/Common/StatView.swift index ca94712..d6107e4 100644 --- a/iosApp/iosApp/Subviews/Common/StatView.swift +++ b/iosApp/iosApp/Subviews/Common/StatView.swift @@ -4,20 +4,30 @@ struct StatView: View { let icon: String let value: String let label: String + var color: Color = AppColors.primary var body: some View { - VStack(spacing: 8) { - Image(systemName: icon) - .font(.title2) - .foregroundColor(.blue) + VStack(spacing: AppSpacing.sm) { + ZStack { + Circle() + .fill(color.opacity(0.1)) + .frame(width: 48, height: 48) + + Image(systemName: icon) + .font(.system(size: 22, weight: .semibold)) + .foregroundColor(color) + } Text(value) - .font(.title) + .font(AppTypography.headlineMedium) .fontWeight(.bold) + .foregroundColor(AppColors.textPrimary) Text(label) - .font(.caption) - .foregroundColor(.secondary) + .font(AppTypography.labelMedium) + .foregroundColor(AppColors.textSecondary) + .multilineTextAlignment(.center) } + .frame(maxWidth: .infinity) } }