Files
pos-system/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift/Views/Auth/RegisterView.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ý vi 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 vi 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 vi 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 điu khon và điu kin
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("")
.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
/// Phn link đăng nhp
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
/// Thc hin đăng ký
private func register() {
focusedField = nil
Task {
await viewModel.register()
}
}
}
// MARK: - Preview
#Preview {
AuthContainerView()
}