293 lines
9.3 KiB
Swift
293 lines
9.3 KiB
Swift
//
|
|
// RegisterView.swift
|
|
// AppClientBaseSwift
|
|
//
|
|
// Registration screen view - Pastel Yellow Theme
|
|
// View màn hình đăng ký - Theme Pastel Vàng
|
|
//
|
|
|
|
import SwiftUI
|
|
import Combine
|
|
|
|
// MARK: - Register View
|
|
// View Đăng ký
|
|
|
|
/// Registration screen with form validation
|
|
/// Màn hình đăng ký với validation form
|
|
struct RegisterView: View {
|
|
|
|
// MARK: - Properties
|
|
|
|
/// Auth view model
|
|
/// ViewModel Auth
|
|
@ObservedObject var viewModel: AuthViewModel
|
|
|
|
/// Focus state for text fields
|
|
/// State focus cho các text field
|
|
@FocusState private var focusedField: Field?
|
|
|
|
enum Field {
|
|
case name
|
|
case email
|
|
case password
|
|
case confirmPassword
|
|
}
|
|
|
|
// MARK: - Body
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(spacing: DesignSystem.spacingXL) {
|
|
// Back button + Header
|
|
headerSection
|
|
.padding(.top, 20)
|
|
|
|
// Form
|
|
formSection
|
|
.padding(.horizontal, DesignSystem.spacingLG)
|
|
|
|
// Register button
|
|
registerButton
|
|
.padding(.horizontal, DesignSystem.spacingLG)
|
|
|
|
// Login link
|
|
loginSection
|
|
.padding(.top, DesignSystem.spacingMD)
|
|
|
|
Spacer(minLength: 40)
|
|
}
|
|
}
|
|
.scrollDismissesKeyboard(.interactively)
|
|
.alert("Lỗi", isPresented: .init(
|
|
get: { viewModel.errorMessage != nil },
|
|
set: { if !$0 { viewModel.errorMessage = nil } }
|
|
)) {
|
|
Button("OK", role: .cancel) {}
|
|
} message: {
|
|
Text(viewModel.errorMessage ?? "")
|
|
}
|
|
}
|
|
|
|
// MARK: - Subviews
|
|
|
|
/// Header with back button and title
|
|
/// Header với nút back và tiêu đề
|
|
private var headerSection: some View {
|
|
VStack(spacing: DesignSystem.spacingLG) {
|
|
// Back button
|
|
HStack {
|
|
Button {
|
|
viewModel.navigateTo(.login)
|
|
} label: {
|
|
HStack(spacing: DesignSystem.spacingXS) {
|
|
Image(systemName: "chevron.left")
|
|
Text("Quay lại")
|
|
}
|
|
.font(.subheadline)
|
|
.foregroundStyle(Color.textSecondary)
|
|
}
|
|
Spacer()
|
|
}
|
|
.padding(.horizontal, DesignSystem.spacingLG)
|
|
|
|
// Title
|
|
VStack(spacing: DesignSystem.spacingXS) {
|
|
Text("Tạo tài khoản")
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
.foregroundStyle(Color.textPrimary)
|
|
|
|
Text("Điền thông tin để đăng ký")
|
|
.font(.subheadline)
|
|
.foregroundStyle(Color.textSecondary)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Form with registration fields
|
|
/// Form với các field đăng ký
|
|
private var formSection: some View {
|
|
VStack(spacing: DesignSystem.spacingMD) {
|
|
// Name field
|
|
AuthTextField(
|
|
icon: "person.fill",
|
|
placeholder: "Họ và tên",
|
|
text: $viewModel.registerName
|
|
)
|
|
.focused($focusedField, equals: .name)
|
|
.submitLabel(.next)
|
|
.onSubmit { focusedField = .email }
|
|
|
|
// Email field
|
|
AuthTextField(
|
|
icon: "envelope.fill",
|
|
placeholder: "Email",
|
|
text: $viewModel.registerEmail,
|
|
keyboardType: .emailAddress
|
|
)
|
|
.focused($focusedField, equals: .email)
|
|
.submitLabel(.next)
|
|
.onSubmit { focusedField = .password }
|
|
|
|
// Password field with strength indicator
|
|
VStack(alignment: .leading, spacing: DesignSystem.spacingSM) {
|
|
AuthSecureField(
|
|
icon: "lock.fill",
|
|
placeholder: "Mật khẩu",
|
|
text: $viewModel.registerPassword
|
|
)
|
|
.focused($focusedField, equals: .password)
|
|
.submitLabel(.next)
|
|
.onSubmit { focusedField = .confirmPassword }
|
|
|
|
// Password strength indicator
|
|
if !viewModel.registerPassword.isEmpty {
|
|
HStack(spacing: DesignSystem.spacingSM) {
|
|
ForEach(0..<4, id: \.self) { index in
|
|
RoundedRectangle(cornerRadius: 2)
|
|
.fill(index < viewModel.passwordStrength
|
|
? viewModel.passwordStrengthColor
|
|
: Color.textSecondary.opacity(0.2))
|
|
.frame(height: 4)
|
|
}
|
|
}
|
|
|
|
Text(viewModel.passwordStrengthText)
|
|
.font(.caption)
|
|
.foregroundStyle(viewModel.passwordStrengthColor)
|
|
}
|
|
}
|
|
|
|
// Confirm password field
|
|
AuthSecureField(
|
|
icon: "lock.fill",
|
|
placeholder: "Xác nhận mật khẩu",
|
|
text: $viewModel.registerConfirmPassword
|
|
)
|
|
.focused($focusedField, equals: .confirmPassword)
|
|
.submitLabel(.go)
|
|
.onSubmit { register() }
|
|
|
|
// Password match indicator
|
|
if !viewModel.registerConfirmPassword.isEmpty {
|
|
HStack(spacing: DesignSystem.spacingXS) {
|
|
Image(systemName: viewModel.registerPassword == viewModel.registerConfirmPassword
|
|
? "checkmark.circle.fill"
|
|
: "xmark.circle.fill")
|
|
Text(viewModel.registerPassword == viewModel.registerConfirmPassword
|
|
? "Mật khẩu khớp"
|
|
: "Mật khẩu không khớp")
|
|
}
|
|
.font(.caption)
|
|
.foregroundStyle(viewModel.registerPassword == viewModel.registerConfirmPassword
|
|
? Color.success
|
|
: Color.error)
|
|
}
|
|
|
|
// Terms checkbox
|
|
termsSection
|
|
.padding(.top, DesignSystem.spacingSM)
|
|
}
|
|
}
|
|
|
|
/// Terms and conditions checkbox
|
|
/// Checkbox điều khoản và điều kiện
|
|
private var termsSection: some View {
|
|
Button {
|
|
viewModel.agreedToTerms.toggle()
|
|
} label: {
|
|
HStack(alignment: .top, spacing: DesignSystem.spacingSM) {
|
|
Image(systemName: viewModel.agreedToTerms ? "checkmark.square.fill" : "square")
|
|
.foregroundStyle(viewModel.agreedToTerms ? Color.brandAccent : Color.textSecondary)
|
|
|
|
Text("Tôi đồng ý với ")
|
|
.foregroundStyle(Color.textSecondary)
|
|
+
|
|
Text("Điều khoản sử dụng")
|
|
.foregroundStyle(Color.brandAccent)
|
|
+
|
|
Text(" và ")
|
|
.foregroundStyle(Color.textSecondary)
|
|
+
|
|
Text("Chính sách bảo mật")
|
|
.foregroundStyle(Color.brandAccent)
|
|
}
|
|
.font(.subheadline)
|
|
.multilineTextAlignment(.leading)
|
|
}
|
|
}
|
|
|
|
/// Register button
|
|
/// Nút đăng ký
|
|
private var registerButton: some View {
|
|
Button(action: register) {
|
|
HStack(spacing: DesignSystem.spacingSM) {
|
|
Text("Đăng ký")
|
|
.fontWeight(.semibold)
|
|
|
|
Image(systemName: "arrow.right")
|
|
}
|
|
.font(.headline)
|
|
.foregroundStyle(Color.textPrimary)
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, DesignSystem.spacingMD)
|
|
.background(
|
|
Group {
|
|
if viewModel.isRegisterValid {
|
|
AppTheme.warmGradient
|
|
} else {
|
|
LinearGradient(
|
|
colors: [Color.gray.opacity(0.3), Color.gray.opacity(0.2)],
|
|
startPoint: .leading,
|
|
endPoint: .trailing
|
|
)
|
|
}
|
|
}
|
|
)
|
|
.cornerRadius(DesignSystem.cornerRadiusMD)
|
|
.shadow(
|
|
color: viewModel.isRegisterValid ? AppTheme.buttonShadow : .clear,
|
|
radius: 15,
|
|
x: 0,
|
|
y: 8
|
|
)
|
|
}
|
|
.disabled(!viewModel.isRegisterValid)
|
|
}
|
|
|
|
/// Login link section
|
|
/// Phần link đăng nhập
|
|
private var loginSection: some View {
|
|
HStack(spacing: DesignSystem.spacingXS) {
|
|
Text("Đã có tài khoản?")
|
|
.foregroundStyle(Color.textSecondary)
|
|
|
|
Button {
|
|
viewModel.navigateTo(.login)
|
|
} label: {
|
|
Text("Đăng nhập")
|
|
.fontWeight(.semibold)
|
|
.foregroundStyle(Color.brandAccent)
|
|
}
|
|
}
|
|
.font(.subheadline)
|
|
}
|
|
|
|
// MARK: - Methods
|
|
|
|
/// Perform registration
|
|
/// Thực hiện đăng ký
|
|
private func register() {
|
|
focusedField = nil
|
|
Task {
|
|
await viewModel.register()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview {
|
|
AuthContainerView()
|
|
}
|