chore: overhaul onboarding steps

This commit is contained in:
juancwu 2026-04-11 16:18:46 +00:00
commit d413193366
12 changed files with 358 additions and 186 deletions

View file

@ -168,13 +168,11 @@ func (h *authHandler) completeLogin(w http.ResponseWriter, r *http.Request, user
}
func (h *authHandler) OnboardingPage(w http.ResponseWriter, r *http.Request) {
step := r.URL.Query().Get("step")
switch step {
case "2":
if r.URL.Query().Get("step") == "name" {
ui.Render(w, r, pages.OnboardingName(""))
default:
ui.Render(w, r, pages.OnboardingWelcome())
return
}
ui.Render(w, r, pages.OnboardingWelcome())
}
func (h *authHandler) CompleteOnboarding(w http.ResponseWriter, r *http.Request) {
@ -184,50 +182,19 @@ func (h *authHandler) CompleteOnboarding(w http.ResponseWriter, r *http.Request)
return
}
step := r.FormValue("step")
switch step {
case "2":
name := strings.TrimSpace(r.FormValue("name"))
if name == "" {
ui.Render(w, r, pages.OnboardingName("Please enter your name"))
return
}
ui.Render(w, r, pages.OnboardingSpace(name, ""))
case "3":
name := strings.TrimSpace(r.FormValue("name"))
spaceName := strings.TrimSpace(r.FormValue("space_name"))
if name == "" {
ui.Render(w, r, pages.OnboardingName("Please enter your name"))
return
}
if spaceName == "" {
ui.Render(w, r, pages.OnboardingSpace(name, "Please enter a space name"))
return
}
err := h.authService.CompleteOnboarding(user.ID, name)
if err != nil {
slog.Error("onboarding failed", "error", err, "user_id", user.ID)
ui.Render(w, r, pages.OnboardingName("Please enter a valid name"))
return
}
_, err = h.spaceService.CreateSpace(spaceName, user.ID)
if err != nil {
slog.Error("failed to create space during onboarding", "error", err, "user_id", user.ID)
ui.Render(w, r, pages.OnboardingSpace(name, "Failed to create space. Please try again."))
return
}
slog.Info("onboarding completed", "user_id", user.ID, "name", name, "space", spaceName)
http.Redirect(w, r, "/app/dashboard", http.StatusSeeOther)
default:
ui.Render(w, r, pages.OnboardingWelcome())
name := strings.TrimSpace(r.FormValue("name"))
if name == "" {
ui.Render(w, r, pages.OnboardingName("Please enter your name"))
return
}
if err := h.authService.CompleteOnboarding(user.ID, name); err != nil {
slog.Error("onboarding failed", "error", err, "user_id", user.ID)
ui.Render(w, r, pages.OnboardingName("We couldn't finish setting you up. Please try again."))
return
}
http.Redirect(w, r, "/app/dashboard", http.StatusSeeOther)
}
func (h *authHandler) JoinSpace(w http.ResponseWriter, r *http.Request) {

View file

@ -19,10 +19,12 @@ func newTestAuthHandler(dbi testutil.DBInfo) *authHandler {
userRepo := repository.NewUserRepository(dbi.DB)
tokenRepo := repository.NewTokenRepository(dbi.DB)
spaceRepo := repository.NewSpaceRepository(dbi.DB)
accountRepo := repository.NewAccountRepository(dbi.DB)
inviteRepo := repository.NewInvitationRepository(dbi.DB)
spaceSvc := service.NewSpaceService(spaceRepo)
accountSvc := service.NewAccountService(accountRepo)
emailSvc := service.NewEmailService(nil, "test@example.com", "http://localhost:9999", "Budgit Test", false)
authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, accountSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
inviteSvc := service.NewInviteService(inviteRepo, spaceRepo, userRepo, emailSvc)
return NewAuthHandler(authSvc, inviteSvc, spaceSvc)
}
@ -95,40 +97,57 @@ func TestAuthHandler_Logout(t *testing.T) {
})
}
func TestAuthHandler_CompleteOnboarding_Step2(t *testing.T) {
func TestAuthHandler_CompleteOnboarding_Success(t *testing.T) {
testutil.ForEachDB(t, func(t *testing.T, dbi testutil.DBInfo) {
h := newTestAuthHandler(dbi)
user := testutil.CreateTestUser(t, dbi.DB, "test@example.com", nil)
req := testutil.NewAuthenticatedRequest(t, http.MethodPost, "/auth/onboarding", user, url.Values{
"step": {"2"},
"name": {"John"},
})
w := httptest.NewRecorder()
h.CompleteOnboarding(w, req)
assert.Equal(t, http.StatusOK, w.Code)
})
}
func TestAuthHandler_CompleteOnboarding_Step3(t *testing.T) {
testutil.ForEachDB(t, func(t *testing.T, dbi testutil.DBInfo) {
h := newTestAuthHandler(dbi)
user := testutil.CreateTestUser(t, dbi.DB, "test@example.com", nil)
req := testutil.NewAuthenticatedRequest(t, http.MethodPost, "/auth/onboarding", user, url.Values{
"step": {"3"},
"name": {"John"},
"space_name": {"My Space"},
})
w := httptest.NewRecorder()
h.CompleteOnboarding(w, req)
assert.Equal(t, http.StatusSeeOther, w.Code)
assert.Equal(t, "/app/dashboard", w.Header().Get("Location"))
// Space "John's Space" with a default account should now exist
spaceRepo := repository.NewSpaceRepository(dbi.DB)
spaces, err := spaceRepo.ByUserID(user.ID)
assert.NoError(t, err)
assert.Len(t, spaces, 1)
assert.Equal(t, "John's Space", spaces[0].Name)
accountRepo := repository.NewAccountRepository(dbi.DB)
accounts, err := accountRepo.BySpaceID(spaces[0].ID)
assert.NoError(t, err)
assert.Len(t, accounts, 1)
assert.Equal(t, service.DefaultAccountName, accounts[0].Name)
})
}
func TestAuthHandler_CompleteOnboarding_EmptyName(t *testing.T) {
testutil.ForEachDB(t, func(t *testing.T, dbi testutil.DBInfo) {
h := newTestAuthHandler(dbi)
user := testutil.CreateTestUser(t, dbi.DB, "empty@example.com", nil)
req := testutil.NewAuthenticatedRequest(t, http.MethodPost, "/auth/onboarding", user, url.Values{
"name": {" "},
})
w := httptest.NewRecorder()
h.CompleteOnboarding(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "Please enter your name")
// No space should have been created
spaceRepo := repository.NewSpaceRepository(dbi.DB)
spaces, err := spaceRepo.ByUserID(user.ID)
assert.NoError(t, err)
assert.Empty(t, spaces)
})
}

View file

@ -17,9 +17,11 @@ func newTestSettingsHandler(dbi testutil.DBInfo) (*settingsHandler, *service.Aut
userRepo := repository.NewUserRepository(dbi.DB)
tokenRepo := repository.NewTokenRepository(dbi.DB)
spaceRepo := repository.NewSpaceRepository(dbi.DB)
accountRepo := repository.NewAccountRepository(dbi.DB)
spaceSvc := service.NewSpaceService(spaceRepo)
accountSvc := service.NewAccountService(accountRepo)
emailSvc := service.NewEmailService(nil, "test@example.com", "http://localhost:9999", "Budgit Test", false)
authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, accountSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
userSvc := service.NewUserService(userRepo)
return NewSettingsHandler(authSvc, userSvc), authSvc
}