import SwiftUI import AuthenticationServices import Alamofire import CryptoKit /// Main login view that handles Apple Sign In struct LoginView: View { // MARK: - Properties @Environment(\.dismiss) private var dismiss @State private var isLoading = false @State private var showError = false @State private var errorMessage = "" @State private var currentNonce: String? // MARK: - Body var body: some View { ZStack { // Background Color(.systemBackground) .edgesIgnoringSafeArea(.all) // Main content VStack(spacing: 24) { Spacer() appHeaderView() signInButton() Spacer() termsAndPrivacyView() } .padding() .alert(isPresented: $showError) { Alert( title: Text("Error"), message: Text(errorMessage), dismissButton: .default(Text("OK")) ) } // Loading indicator if isLoading { loadingView() } } .navigationBarHidden(true) } // MARK: - View Components /// App header with icon and welcome text private func appHeaderView() -> some View { VStack(spacing: 16) { Image(systemName: "person.circle.fill") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 80, height: 80) .foregroundColor(.blue) Text("Welcome to Wake") .font(.largeTitle) .fontWeight(.bold) Text("Sign in to continue") .font(.subheadline) .foregroundColor(.secondary) } .padding(.bottom, 40) } /// Apple Sign In button private func signInButton() -> some View { SignInWithAppleButton( onRequest: { request in // Generate nonce for security let nonce = String.randomURLSafeString(length: 32) self.currentNonce = nonce // Configure the request request.requestedScopes = [.fullName, .email] request.nonce = self.sha256(nonce) }, onCompletion: handleAppleSignIn ) .signInWithAppleButtonStyle(.black) .frame(height: 50) .padding(.horizontal, 40) .cornerRadius(10) } /// Terms and Privacy policy links private func termsAndPrivacyView() -> some View { VStack(spacing: 8) { Text("By continuing, you agree to our") .font(.caption) .foregroundColor(.secondary) HStack(spacing: 16) { Button("Terms of Service") { openURL("https://yourwebsite.com/terms") } .font(.caption) .foregroundColor(.blue) Text("•") .foregroundColor(.secondary) Button("Privacy Policy") { openURL("https://yourwebsite.com/privacy") } .font(.caption) .foregroundColor(.blue) } } .padding(.bottom, 24) } /// Loading overlay view private func loadingView() -> some View { return ZStack { Color.black.opacity(0.4) .edgesIgnoringSafeArea(.all) ProgressView() .scaleEffect(1.5) .progressViewStyle(CircularProgressViewStyle(tint: .white)) } } // MARK: - Apple Sign In Handlers /// Handles the result of Apple Sign In private func handleAppleSignIn(result: Result) { switch result { case .success(let authResults): processAppleIDCredential(authResults.credential) case .failure(let error): handleSignInError(error) } } /// Processes the Apple ID credential private func processAppleIDCredential(_ credential: ASAuthorizationCredential) { guard let appleIDCredential = credential as? ASAuthorizationAppleIDCredential else { showError(message: "Unable to process Apple ID credentials") return } // Get user data let userId = appleIDCredential.user let email = appleIDCredential.email ?? "" let fullName = [ appleIDCredential.fullName?.givenName, appleIDCredential.fullName?.familyName ] .compactMap { $0 } .joined(separator: " ") // Get the identity token guard let identityTokenData = appleIDCredential.identityToken, let identityToken = String(data: identityTokenData, encoding: .utf8) else { showError(message: "Unable to fetch identity token") return } // Get the authorization code (optional) var authCode: String? = nil if let authCodeData = appleIDCredential.authorizationCode { authCode = String(data: authCodeData, encoding: .utf8) } // Proceed with backend authentication authenticateWithBackend( userId: userId, email: email, name: fullName, identityToken: identityToken, authCode: authCode ) } // MARK: - Network Operations /// Authenticates the user with the backend server private func authenticateWithBackend( userId: String, email: String, name: String, identityToken: String, authCode: String? ) { isLoading = true let url = "https://your-api-endpoint.com/api/auth/apple" var parameters: [String: Any] = [ "appleUserId": userId, "email": email, "name": name, "identityToken": identityToken ] // Add authorization code if available if let authCode = authCode { parameters["authorizationCode"] = authCode } AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default) .validate() .responseJSON { response in self.isLoading = false switch response.result { case .success(let value): print("Authentication successful: \(value)") self.handleSuccessfulAuthentication() case .failure(let error): self.handleAuthenticationError(error) } } } // MARK: - Helper Methods /// Handles successful authentication private func handleSuccessfulAuthentication() { DispatchQueue.main.async { self.dismiss() } } /// Handles sign in errors private func handleSignInError(_ error: Error) { let errorMessage = (error as NSError).localizedDescription print("Apple Sign In failed: \(errorMessage)") showError(message: "Sign in failed: \(error.localizedDescription)") } /// Handles authentication errors private func handleAuthenticationError(_ error: AFError) { let errorMessage = error.localizedDescription print("API Error: \(errorMessage)") showError(message: "Failed to sign in: \(errorMessage)") } /// Shows error message to the user private func showError(message: String) { DispatchQueue.main.async { self.errorMessage = message self.showError = true } } /// Opens a URL in Safari private func openURL(_ string: String) { guard let url = URL(string: string) else { return } UIApplication.shared.open(url) } private func sha256(_ input: String) -> String { let inputData = Data(input.utf8) let hashedData = SHA256.hash(data: inputData) let hashString = hashedData.compactMap { String(format: "%02x", $0) }.joined() return hashString } } // MARK: - String Extension for Random String Generation extension String { /// Generates a random string of the specified length using URL-safe characters /// - Parameter length: The length of the random string to generate /// - Returns: A random string containing URL-safe characters static func randomURLSafeString(length: Int) -> String { let characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" var randomString = "" for _ in 0..