Ana içeriğe geç

Registration

Durum: Adım 3 tamamlandı. CreateUser endpoint (backend + Flutter) tamamlandı. register_details_screen.dart tam işlevsel hale getirildi. Şifre hash'leme (Argon2id), username/password validasyonu, transaction'lı kullanıcı oluşturma ve başarı ekranı tamamlandı. IsEmailRegisteredInUsers kontrolü eklendi — kayıtlı email RegisterEmail aşamasında ErrEmailAlreadyRegistered ile reddedilir, doğrulama koduna ulaşılmaz. Son güncelleme: 6 Mart 2026


User Flow

Bölüm 1: Kullanıcı adımları, ekranlar, senaryo. Teknik detay yok.

Ekran 1: Email Input

  • Kullanıcı üniversite e-posta adresini girer
  • "Devam Et" butonuna basar
  • E-posta doğrulanır ve kod gönderilir
  • Başarı: Ekran 2'ye yönlendir (10 dakika timer başlar)
  • Hata: Jenerik mesaj göster ve tekrar dene

Ekran 2: Email Verification Code

  • 10 dakikalık timer gösterilir
  • Kullanıcı email'den aldığı 6 haneli kodu girer
  • "Doğrula" butonuna basar
  • Başarı: Ekran 3'e yönlendir (önceki kodlar silinir)
  • Hata (yanlış kod): "Kod yanlış. Tekrar deneyin." (max 5 deneme)
  • Max deneme aşıldıysa: "Çok fazla deneme. Yeni kod isteyin."
  • "Yeni Kod Al" butonu: 10 dakika timer sıfırlanır, yeni kod gönderilir
  • Resend max 3 kez / 10 dakika (spam koruması)
  • "Geri" butonu: Ekran 1'e dönüş (yeni kod gönderilir, oturum iptal)

Ekran 3: Username & Password

  • Kullanıcı adı (3-20 karakter, harf/rakam/alt çizgi)
  • Şifre (8+ karakter, tüm karakterler)
  • Şifre baştan yazı (confirm)
  • "Kullanım Koşulları" checkbox ✓ (zorunlu)
  • "Gizlilik Politikası" checkbox ✓ (zorunlu)
  • Her iki checkbox işaretlenince "Kayıt Ol" butonu aktif olur
  • Başarı: Ekran 4'e yönlendir
  • Hata: SnackBar ile hata mesajı göster
  • Geri tuşu: Engellendi (PopScope(canPop: false)) — geri dönüş yok

Ekran 4: Kayıt Tamamlandı

  • "Kayıt başarılı! Giriş yap ekranına yönlendiriliyorsun..."
  • 2 saniye sonra automatic olarak Login ekranına yönlendir
  • NOT: Auto-login yapılmaz (güvenlik nedeniyle)

Edge Cases

Bölüm 2: Tüm "ya şöyle olursa" durumları. Normal akış dışındaki senaryolar.

Senaryo Davranış
Email Ekranında:
Email zaten users tablosunda kayıtlı (tam kayıt tamamlanmış) codes.AlreadyExistsErrEmailAlreadyRegistered — RegisterEmail aşamasında erken yakalanır, doğrulama koduna ulaşılmaz
Üniversite emaili değil (domain veya subdomaini universities tablosunda yok) Hata: codes.InvalidArgument — "universite domaini degil: "
Boş email Client-side: "Bu alan zorunludur"
Doğrulama Kodu Ekranında:
Kod yanlış girilirse (1-4. deneme) "Kod yanlış. Kalan deneme: X"
Max 5 deneme aşılırsa Kod iptal: "Çok fazla deneme. 'Yeni Kod Al' butonuna basın"
Doğrulama kodu süresi dolduysa (10 dakika) Otom. "Kodunuz süresi doldu. Yeni kod isteyin"
"Yeni Kod Al" 3 kez çıkartıldıktan sonra Bekleme süresi: "10 dakika içinde tekrar deneyin"
Uygulama kapanıp tekrar açılırsa Oturum devam: Aynı kod ekranında (kalan süre korunur)
Geri tuşuna basıp email girerse Yeni kod gönderilir, oturum sıfırlanır, timer sıfırlanır
Username/Password Ekranında:
Username zaten alınmışsa codes.AlreadyExists — "Bu kullanıcı adı kullanılıyor. Başka bir tane deneyin"
Username kuralları (3-20 harf/rakam/alt çizgi) ihlal codes.InvalidArgument — "Kullanıcı adı 3-20 karakter olmalı, sadece harf/rakam/alt çizgi"
Şifre confirm eşleşmezse Client-side SnackBar — "Şifreler eşleşmiyor"
Şifre kuralları (min 8 karakter) ihlal codes.InvalidArgument — "Şifre en az 8 karakter olmalı"
KVKK checkbox işaretlemezse Kayıt butonu disabled kalır
Ekran 3'te sistem geri tuşuna basılırsa PopScope(canPop: false) — geri git engellendi, kullanıcı Ekran 3'te kalır
Email doğrulanmamışken CreateUser çağrılırsa codes.FailedPreconditionErrEmailNotVerified
Email email_verification tablosunda yoksa codes.NotFoundErrEmailNotFound
Email zaten users tablosundaysa codes.AlreadyExistsErrEmailAlreadyRegistered
Üniversite domain eşleşmezse codes.NotFoundErrUniversityNotFound
Network/System:
Internet kesilirse (herhangi bir adımda) Hata: "Bağlantı hatası. Lütfen tekrar deneyin"
Backend timeout Hata: "İstek zaman aşımına uğradı. Tekrar deneyin"
Uygulama crash/kill olursa Tekrar açılınca: En son seçilen email adımından başla
2 cihazdan aynı emaille aynı anda kayıt Son gelen kod geçerli, önceki iptal

Implementation

Durum: Adım 3 tamamlandı. Adım 1 tamamlandı: Email kayıt (domain doğrulama + subdomain desteği). ON CONFLICT DO UPDATE: aynı email tekrar gelirse kayıt üstüne yazılır, yeni kod gönderilir. Adım 2 tamamlandı: VerifyCode proto, backend ve Flutter tarafı tamamen çalışıyor. remaining_attempts Flutter'a doğru aktarılıyor. code_validation.go eklendi (backend girdi validasyonu). Frontend validasyonu geçici olarak kaldırıldı (backend testi için); yayına alınmadan önce tekrar eklenecek. Countdown Timer tamamlandı: RegisterEmailResponse'a int64 expires_at_unix eklendi. Backend her RegisterEmail çağrısında expiresAt = time.Now() + verificationCodeTTL hesaplar, Flutter'a döndürür. Flutter DateTime.fromMillisecondsSinceEpoch(expiresAtUnix * 1000) ile Timer.periodic(1s) countdown başlatır. Yeni Kod Al (Resend) tamamlandı: Süre dolunca "Yeni kod almak için geri dönün." text'i kaldırıldı, "Yeni Kod Al" butonu eklendi. ResendCodeLoading/Success/Failure state'leri ve resendCode() cubit metodu eklendi. Backend RegisterEmail endpoint'i ON CONFLICT DO UPDATE ile kod/süre/attempt'i sıfırlar — ayrı bir RPC gerekmez. Adım 3 tamamlandı: CreateUser RPC eklendi (proto + backend + Flutter). register_details_screen.dart tam işlevsel hale getirildi — form validasyonu, gRPC çağrısı, başarı ekranına yönlendirme tamamlandı. Şifre Argon2id ile hash'leniyor (password_hash.go). username_validation.go ve password_validation.go eklendi. repository.go'ya GetUniversityIDByDomain + transaction'lı CreateUser metodları eklendi. email_verification kaydı kullanıcı oluşturulduktan sonra transaction içinde siliniyor. IsEmailRegisteredInUsers eklendi: repository.go'ya SELECT EXISTS(...FROM users...) sorgusu, service.go'da RegisterEmail akışına domain kontrolünden sonra users tablosu kontrolü eklendi. Kayıtlı email artık ErrEmailAlreadyRegisteredcodes.AlreadyExists döndürür; silent success kaldırıldı. Son güncelleme: 6 Mart 2026

Önemli mimari not — gRPC nil error kuralı: Backend, yanlış kod için error != nil döndürmüşse gRPC framework response body'yi silerdi; Flutter remaining_attempts'ı hiç alamazdı. Bu nedenle yanlış kod durumunda service.go nil error ile {success:false, remaining_attempts:N} döndürür. Gerçek hatalar (süre doldu, kilitlendi vb.) hala error != nil olarak döner.

Önemli mimari not — sentinel error + wrap pattern: Backend'de her hata durumu için sentinel error tanımlı (ErrInvalidUsername, ErrInvalidPassword, vb.). Validation hataları özel struct'lara (usernameFormatErr, passwordFormatErr) wrap'leniyor. Bu struct'lar .Error() ile kullanıcıya gösterilecek mesajı, .Unwrap() ile sentinel'i döndürür. Handler errors.Is() ile sentinel'i kontrol eder, gRPC status kodu belirler; mesajı ise err.Error() ile direkt alır.

Akış

Ekran 1: RegisterEmail

"Devam Et" butonu
  → EmailInputScreen
  → RegisterCubit.submitEmail(email)
  → RegisterRepository.registerEmail(email)
  → DefaultRegisterGrpcClient → GrpcClient.stub
  ─── gRPC / TCP :50051 ───
  → EmailHandler.RegisterEmail()
  → EmailService.RegisterEmail()
  → validation.ValidateEmailFormat(req.Email)   ← pure format kontrolü (RFC 5322)
  → domain = strings.Split(email, "@")[1]       ← @ sonrası domain
  → EmailRepository.IsUniversityDomain(domain)  ← DB sorgusu
    SELECT EXISTS(
      SELECT 1 FROM universities
      WHERE ($1 = domain OR $1 LIKE '%.' || domain)
      AND is_active = true
    )
  → EmailRepository.IsEmailRegisteredInUsers(email)  ← DB sorgusu
    SELECT EXISTS(SELECT 1 FROM users WHERE email = $1)
    → kayıtlıysa: ErrEmailAlreadyRegistered  (codes.AlreadyExists)
  → EmailRepository.SaveEmail()
  → INSERT INTO email_verification (email, verification_code, expires_at)
    VALUES ($1, $2, $3)
    ON CONFLICT (email) DO UPDATE SET
        verification_code = EXCLUDED.verification_code,
        expires_at        = EXCLUDED.expires_at,
        attempt_count     = 0,
        is_verified       = FALSE

Başarı: {success: true, expires_at_unix: <unix_timestamp>}RegisterSuccess(expiresAtUnix: expiresAtUnix) emit edilir → Navigator.pushVerificationCodeScreen(email: email, expiresAtUnix: expiresAtUnix) Hata (geçersiz format): codes.InvalidArgument → SnackBar hata mesajı Hata (geçersiz domain): codes.InvalidArgument → SnackBar "universite domaini degil: " Hata (kayıtlı email): codes.AlreadyExists → SnackBar hata mesajı (doğrulama koduna erişilmez) Hata (DB/sistem hatası): codes.Internal → SnackBar hata mesajı

Not: DO UPDATE kullanıldığı için aynı email tekrar gönderilirse AlreadyExists hatası dönmez; kayıt sessizce güncellenir ve yeni kod gönderilir. Bu davranış Resend akışında da kullanılır.

Ekran 2: VerifyCode

"Doğrula" butonu
  → VerificationCodeScreen
  → RegisterCubit.submitCode(email, code)
  → RegisterRepository.verifyCode(email, code)   ← Future<int?> döndürür
  → DefaultRegisterGrpcClient → GrpcClient.stub
  ─── gRPC / TCP :50051 ───
  → EmailHandler.VerifyCode()
  → EmailService.VerifyCode()
  → validation.ValidateCodeFormat(req.Code)         ← 6 hane, sadece rakam (pure function)
      → Hata: fmt.Errorf("%w", ErrInvalidCodeFormat) ← handler: codes.InvalidArgument
  → EmailRepository.GetVerificationRecord(email)    ← DB'den kaydı oku
  → is_verified == true?   → ErrAlreadyVerified  (codes.AlreadyExists)
  → expires_at geçti?     → ErrCodeExpired      (codes.DeadlineExceeded)
  → attempt_count >= 5?   → ErrMaxAttemptsExceeded (codes.ResourceExhausted)
  → code eşleşmedi?
      → IncrementAttemptCount()
      → remaining = 5 - (attemptCount + 1)
      → return {success:false, remaining_attempts:N}, nil   ← nil error! (gRPC kuralı)
  → code eşleşti?
      → MarkEmailVerified()
      → return {success:true, remaining_attempts:0}, nil

Flutter tarafında verifyCode dönüş değerine göre cubit davranışı:

Dönen Değer Durum Cubit Davranışı
null Başarılı CodeVerificationSuccess emit et
int > 0 (ör. 3) Yanlış kod CodeVerificationFailure('Yanlış kod. 3 deneme hakkınız kaldı.')
int == 0 Son hak da tukendi CodeVerificationFailure('Maksimum deneme sayısına ulaştınız.')
exception Gerçek hata CodeVerificationFailure(mesaj) — remainingAttempts null

Başarı: CodeVerificationSuccessNavigator.pushRegisterDetailsScreen(email: email) Hata (yanlış kod): CodeVerificationFailure → SnackBar "Yanlış kod. N deneme hakkınız kaldı." Hata (0 hak): CodeVerificationFailure → SnackBar "Maksimum deneme sayısına ulaştınız." Hata (süre doldu): CodeVerificationFailure → SnackBar backend mesajı Hata (format): codes.InvalidArgument → SnackBar backend mesajı (attempt_count artmaz)

Ekran 2: Countdown Timer

Backend RegisterEmail her çağrıldığında (ilk kayıt veya resend) expiresAt = time.Now().Add(verificationCodeTTL) hesaplar ve RegisterEmailResponse.expires_at_unix (Unix saniye) olarak Flutter'a döndürür.

Flutter VerificationCodeScreen._startTimer():

expiresAt = DateTime.fromMillisecondsSinceEpoch(_expiresAtUnix * 1000)
Timer.periodic(1s) → diff = expiresAt.difference(DateTime.now())
  diff > 0 → setState(_remaining = diff)   → MM:SS göster
  diff ≤ 0 → setState(_expired = true)       → timer cancel, TextField disabled, verifyButton disabled

_expiresAtUnix neden late int field'a taşındı: - widget.expiresAtUnix final — widget parametresi değiştirilemiyor - Resend gelince BlocListener state.expiresAtUnix'i _expiresAtUnix'e yazar ve _startTimer() yeniden çağrılır

Ekran 2: Yeni Kod Al (Resend)

Süre dolunca _expired = trueTextField ve verifyButton devre dışı kalır → resendButton ekranda belirir.

"Yeni Kod Al" butonuna basılır
  → RegisterCubit.resendCode(email)
  → emit(ResendCodeLoading)   → resendButton alanına CircularProgressIndicator
  → RegisterRepository.registerEmail(email)   ← submitEmail ile AYNI metod
  → DefaultRegisterGrpcClient → GrpcClient.stub
  ─── gRPC / TCP :50051 ───
  → EmailHandler.RegisterEmail()
  → EmailService.RegisterEmail()    ← yeni crypto/rand kodu + yeni expiresAt
  → SaveEmail() → ON CONFLICT DO UPDATE:
      verification_code = yeni kod
      expires_at        = şimdi + verificationCodeTTL
      attempt_count     = 0
      is_verified       = FALSE
  ← {success:true, expires_at_unix: yeni_timestamp}
  → emit(ResendCodeSuccess(expiresAtUnix: yeniDeger))
  → BlocListener:
      _timer?.cancel()
      setState: _expiresAtUnix = state.expiresAtUnix, _expired = false
      _startTimer()         ← yeni süre başlar
      _codeController.clear()

Neden ResendCodeSuccess ayrı state (neden RegisterSuccess emit edilmez): - email_input_screen.dart'daki BlocListener RegisterSuccess dinler → Navigator.push tetiklenir → Ekran 2 üstüne tekrar Ekran 2 açılırdı (hatalı navigasyon) - ResendCodeSuccess yalnızca VerificationCodeScreen'in kendi BlocListener'ı tarafından işlenir

Ekran 3: CreateUser

"Kayıt Ol" butonu
  → RegisterDetailsScreen
  → (client-side) şifre eşleşmiyorsa → SnackBar "Şifreler eşleşmiyor", backend'e gidilmez
  → RegisterCubit.submitDetails(email, username, password, termsAccepted, privacyAccepted)
  → emit(CreateUserLoading)
  → RegisterRepository.createUser(email, username, password, termsAccepted, privacyAccepted)
  → DefaultRegisterGrpcClient.createUser() → GrpcClient.stub
  ─── gRPC / TCP :50051 ───
  → EmailHandler.CreateUser()
  → EmailService.CreateUser()
  → validation.ValidateUsernameFormat(req.Username)   ← 3-20 karakter, harf/rakam/alt çizgi
      → Hata: usernameFormatErr{} wraps ErrInvalidUsername → handler: codes.InvalidArgument
  → validation.ValidatePasswordFormat(req.Password)   ← min 8 karakter, boşluk yasak
      → Hata: passwordFormatErr{} wraps ErrInvalidPassword → handler: codes.InvalidArgument
  → req.TermsAccepted == false?  → ErrTermsNotAccepted    (codes.InvalidArgument)
  → req.PrivacyAccepted == false? → ErrPrivacyNotAccepted  (codes.InvalidArgument)
  → EmailRepository.GetUniversityIDByDomain(domain)
      SELECT id FROM universities
      WHERE ($1 = domain OR $1 LIKE '%.' || domain)
      AND is_active = true
      → bulunamazsa: ErrUniversityNotFound (codes.NotFound)
  → hashPassword(req.Password)   ← Argon2id (m=19456, t=2, p=1)
  → EmailRepository.CreateUser(ctx, email, username, hashedPassword, universityID)
      BEGIN TRANSACTION
        SELECT id, is_verified FROM email_verification WHERE email = $1 FOR UPDATE
        → kayıt yoksa:    ErrEmailNotFound        (codes.NotFound)
        → is_verified=false: ErrEmailNotVerified  (codes.FailedPrecondition)
        INSERT INTO users (email, username, password_hash, university_id)
        VALUES ($1, $2, $3, $4)
        → email çakışması (23505):    ErrEmailAlreadyRegistered   (codes.AlreadyExists)
        → username çakışması (23505): ErrUsernameAlreadyExists    (codes.AlreadyExists)
        DELETE FROM email_verification WHERE email = $1
      COMMIT
  ← {success:true, message:"Kayit basarili"}
  → emit(CreateUserSuccess)

Başarı: CreateUserSuccessNavigator.pushReplacementRegisterSuccessScreen Hata: CreateUserFailure(message) → SnackBar hata mesajı

Handler error mapping (CreateUser):

Sentinel Error gRPC Code
ErrInvalidUsername codes.InvalidArgument
ErrInvalidPassword codes.InvalidArgument
ErrTermsNotAccepted codes.InvalidArgument
ErrPrivacyNotAccepted codes.InvalidArgument
ErrEmailNotVerified codes.FailedPrecondition
ErrUniversityNotFound codes.NotFound
ErrEmailNotFound codes.NotFound
ErrEmailAlreadyRegistered codes.AlreadyExists
ErrUsernameAlreadyExists codes.AlreadyExists
diğer codes.Internal

Dosyalar

Proto: - proto/yackr/v1/yackr.protoRegisterEmail, VerifyCode, CreateUser RPC tanımları - RegisterEmailResponse: success, int64 expires_at_unix - VerifyCodeRequest: email, code - VerifyCodeResponse: success, remaining_attempts - CreateUserRequest: email, username, password, terms_accepted, privacy_accepted (YENİ) - CreateUserResponse: success, message (YENİ) - Generated: backend/gen/go/yackr/v1/ (Go), app/lib/gen/yackr/v1/ (Dart)

Backend: - backend/cmd/server/main.go — sunucuyu başlatır - backend/internal/server/server.go — handler'ı oluşturur ve kaydeder - backend/internal/auth/register/handler.go — gRPC isteğini karşılar; RegisterEmail + VerifyCode + CreateUser handler'ları; sentinel error → gRPC status code eşlemesi; errors.Is() ile unwrap zinciri kontrol edilir; RegisterEmail'de ErrEmailAlreadyRegisteredcodes.AlreadyExists eşlemesi eklendi - backend/internal/auth/register/service.go — servis katmanı; RegisterEmail, VerifyCode, CreateUser metodları; CreateUser username/password validasyonu + terms/privacy kontrolü + hash + repository çağrısı; RegisterEmail'de domain kontrolünden sonra IsEmailRegisteredInUsers kontrolü eklendi; EmailRepositoryInterface'e IsEmailRegisteredInUsers metodu eklendi - backend/internal/auth/register/repository.go — veritabanı CRUD; GetVerificationRecord, IncrementAttemptCount, MarkEmailVerified, GetUniversityIDByDomain, CreateUser (transaction), IsEmailRegisteredInUsers metodları; son metod users tablosunda email varlığını kontrol eder - backend/internal/auth/register/password_hash.goYENİhashPassword(): Argon2id ile şifre hash'leme (m=19456, t=2, p=1, saltLen=16); PHC string formatı ($argon2id$v=19$...) - backend/internal/auth/register/verification_code.gogenerateVerificationCode() (crypto/rand, 100000-999999) ve verificationCodeTTL - backend/internal/db/db.go — veritabanı bağlantısı - backend/internal/db/migrations/create_email_verification_table.sqlemail_verification tablosu - backend/internal/db/migrations/create_universities_table.sqluniversities tablosu - backend/internal/db/seeds/universities_tr.json — Türkiye üniversiteleri seed verisi - backend/internal/db/seeds/seed_universities.py — seed yükleme scripti - backend/internal/validation/email_validation.go — RFC 5322 email format validator (pure function) - backend/internal/validation/code_validation.go — doğrulama kodu format validator; boş, 6 haneli olmayan, rakam dışı karakter kontrolü - backend/internal/validation/username_validation.goYENİValidateUsernameFormat(): 3-20 karakter, harf/rakam/alt çizgi kuralı; usernameFormatErr struct ile wrap (sentinel: ErrInvalidUsername) - backend/internal/validation/password_validation.goYENİValidatePasswordFormat(): min 8 karakter, boşluk yasak; passwordFormatErr struct ile wrap (sentinel: ErrInvalidPassword)

Backend testler: - backend/internal/validation/email_validation_test.go — email format unit testler (table-driven) - backend/internal/validation/code_validation_test.goValidateCodeFormat unit testler - backend/internal/validation/username_validation_test.goYENİValidateUsernameFormat unit testler (table-driven); geçerli/geçersiz senaryolar, hata mesajı doğrulama; 100% coverage - backend/internal/validation/password_validation_test.goYENİValidatePasswordFormat unit testler (table-driven); geçerli/geçersiz senaryolar, hata mesajı doğrulama; 100% coverage - backend/internal/auth/register/handler_test.go — handler unit; TestHandler_CreateUser_Success, TestHandler_CreateUser_ErrorMapping (9 sentinel → gRPC code tablosu), TestHandler_CreateUser_WrappedUsernameError, TestHandler_CreateUser_WrappedPasswordError eklendi - backend/internal/auth/register/service_test.go — service unit; 11 CreateUser senaryosu eklendi: başarı, geçersiz email/username/password, terms/privacy kabul edilmemesi, üniversite bulunamadı, repository hatası, validation sıralama, wrapped hata mesajları - backend/internal/auth/register/password_hash_test.goYENİhashPassword() unit testler: başarı, PHC format doğrulama, unique salt, farklı şifreler farklı hash, boş şifre, uzun şifre - backend/internal/auth/register/verification_code_test.go — kod üretimi unit (6 hane, 100000–999999 aralığı, sadece rakam, TTL pozitif) - backend/internal/auth/register/repository_integration_test.go — DB integration; TestRepo_CreateUser_Success, TestRepo_CreateUser_EmailNotVerified, TestRepo_CreateUser_EmailNotFound, TestRepo_CreateUser_DuplicateEmail, TestRepo_CreateUser_DuplicateUsername, TestRepo_GetUniversityIDByDomain_Exists, TestRepo_GetUniversityIDByDomain_NotExists, TestRepo_GetUniversityIDByDomain_Subdomain (tüm fonksiyon isimleri TestRepo_ prefix'i aldı — aynı paketteki service_test.go ile isim çakışmasını önlemek için)

Flutter: - app/lib/main.dart — entry point, BlocProvider kurulumu - app/lib/core/config.dart — gRPC bağlantı ayarları - app/lib/core/grpc_client.dart — gRPC client - app/lib/features/auth/register/register_repository.dart — gRPC çağrısı ve hata yönetimi; registerEmail Future<int> döndürür (expiresAtUnix); verifyCode Future<int?> döndürür; createUser YENİFuture<void> döndürür, success=false gelirse exception fırlatır - app/lib/features/auth/register/cubit/register_cubit.dart — state yönetimi; submitEmail, submitCode, resendCode metodları; submitDetails(email, username, password, termsAccepted, privacyAccepted) YENİCreateUserLoading/Success/Failure emit eder - app/lib/features/auth/register/cubit/register_state.dart — state tanımları; RegisterSuccess(expiresAtUnix), CodeVerificationFailure(remainingAttempts), ResendCodeLoading, ResendCodeSuccess(expiresAtUnix), ResendCodeFailure(message); CreateUserLoading, CreateUserSuccess, CreateUserFailure(message) YENİ - app/lib/features/auth/register/ui/email_input_screen.dart — email giriş ekranı - app/lib/features/auth/register/ui/verification_code_screen.dart — doğrulama kodu ekranı (Ekran 2); resend sistemi dahil - app/lib/features/auth/register/ui/register_details_screen.dartTAM İŞLEVSEL HALE GETİRİLDİ — kullanıcı adı + şifre + şifre tekrar + 2 checkbox; BlocListener ile CreateUserSuccessLoginScreen'e Navigator.pushReplacement; CreateUserFailure → SnackBar; şifre uyuşmazlığı client-side kontrol; PopScope(canPop: false) + AppBar(automaticallyImplyLeading: false) - app/lib/features/auth/register/ui/register_success_screen.dart — Ekran 4 (artık kullanılmıyor); register_details_screen.dart başarı durumunda doğrudan LoginScreen'e yönlendirir

Flutter testler: - app/test/features/auth/register/email_input_screen_test.dart — widget testleri - app/test/features/auth/register/verification_code_screen_test.dart — widget testleri; resend dahil - app/test/features/auth/register/register_repository_test.dart — repository unit; createUser başarılı, createUser hata testleri eklendi - app/test/features/auth/register/register_cubit_test.dart — cubit unit; submitDetails başarılı: CreateUserLoading → CreateUserSuccess, submitDetails hata: CreateUserLoading → CreateUserFailure testleri eklendi - app/test/features/auth/register/register_details_screen_test.dart — widget; mock shouldSucceed parametreli hale getirildi; Şifre eşleşmiyorsa SnackBar gösterir, Başarılı kayıt RegisterSuccessScreen'e yönlendirir, Backend hatası SnackBar gösterir testleri eklendi