diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 0392a1d8..66677ee4 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -567,6 +567,7 @@ func main() { // Signup Routes signup := auth.Group("/signup") + signup.Get("/tenants", authHandler.GetActiveTenants) signup.Post("/check-email", authHandler.CheckEmail) signup.Post("/check-login-id", authHandler.CheckLoginID) signup.Post("/send-email-code", authHandler.SendSignupEmailCode) diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go index 27d1efa1..22fdab4a 100644 --- a/backend/internal/handler/auth_handler.go +++ b/backend/internal/handler/auth_handler.go @@ -463,6 +463,45 @@ func (h *AuthHandler) VerifySignupCode(c *fiber.Ctx) error { } // Signup - Finalize registration +func (h *AuthHandler) GetActiveTenants(c *fiber.Ctx) error { + if h.TenantService == nil { + return errorJSON(c, fiber.StatusServiceUnavailable, "Tenant service unavailable") + } + + // List all tenants (we use a large limit for now to get all affiliates) + tenants, _, err := h.TenantService.ListTenants(c.Context(), 1000, 0, "") + if err != nil { + return errorJSON(c, fiber.StatusInternalServerError, "Failed to fetch tenants") + } + + type tenantResp struct { + ID string `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` + Type string `json:"type"` + Domains []string `json:"domains"` + } + + var results []tenantResp + for _, t := range tenants { + if t.Status == domain.TenantStatusActive && (t.Type == domain.TenantTypeCompany || t.Type == domain.TenantTypeCompanyGroup) { + var domains []string + for _, d := range t.Domains { + domains = append(domains, d.Domain) + } + results = append(results, tenantResp{ + ID: t.ID, + Name: t.Name, + Slug: t.Slug, + Type: t.Type, + Domains: domains, + }) + } + } + + return c.JSON(results) +} + func (h *AuthHandler) Signup(c *fiber.Ctx) error { var req domain.SignupRequest if err := c.BodyParser(&req); err != nil { diff --git a/userfront/lib/core/services/auth_proxy_service.dart b/userfront/lib/core/services/auth_proxy_service.dart index e651b4ae..5867402d 100644 --- a/userfront/lib/core/services/auth_proxy_service.dart +++ b/userfront/lib/core/services/auth_proxy_service.dart @@ -923,6 +923,17 @@ class AuthProxyService { } } + static Future>> getActiveTenants() async { + final url = Uri.parse('$_baseUrl/api/v1/auth/signup/tenants'); + final response = await http.get(url); + + if (response.statusCode == 200) { + final List data = jsonDecode(response.body); + return data.cast>(); + } + return []; + } + static Future sendSignupCode(String target, String type) async { final path = type == 'email' ? 'send-email-code' : 'send-sms-code'; final url = Uri.parse('$_baseUrl/api/v1/auth/signup/$path'); diff --git a/userfront/lib/features/auth/presentation/signup_screen.dart b/userfront/lib/features/auth/presentation/signup_screen.dart index bde6317f..047799f1 100644 --- a/userfront/lib/features/auth/presentation/signup_screen.dart +++ b/userfront/lib/features/auth/presentation/signup_screen.dart @@ -54,6 +54,10 @@ class _SignupScreenState extends State { bool _isPasswordObscured = true; bool _isConfirmPasswordObscured = true; + // Dynamic Tenants + List> _tenants = []; + final Map _affiliateDomains = {}; + // Inline Errors String? _emailError; String? _phoneError; @@ -66,20 +70,32 @@ class _SignupScreenState extends State { Timer? _phoneTimer; int _phoneSeconds = 0; - // 가족사 도메인 맵 - final Map _affiliateDomains = { - 'hanmaceng.co.kr': 'HANMAC', - 'samaneng.com': 'SAMAN', - 'jangheon.co.kr': 'JANGHEON', - 'hallasanup.com': 'HALLA', - 'pre-cast.co.kr': 'PTC', - 'baroncs.co.kr': 'BARON', - }; - @override void initState() { super.initState(); _loadPolicy(); + _fetchTenants(); + } + + Future _fetchTenants() async { + try { + final tenants = await AuthProxyService.getActiveTenants(); + if (mounted) { + setState(() { + _tenants = tenants; + _affiliateDomains.clear(); + for (var t in tenants) { + if (t['domains'] != null) { + for (var d in (t['domains'] as List)) { + _affiliateDomains[d.toString().toLowerCase()] = t['slug']; + } + } + } + }); + } + } catch (e) { + debugPrint('Failed to load tenants: $e'); + } } Future _loadPolicy() async { @@ -1505,47 +1521,12 @@ class _SignupScreenState extends State { ), border: const OutlineInputBorder(), ), - items: [ - DropdownMenuItem( - value: 'HANMAC', - child: Text( - tr('domain.company.hanmac'), - ), - ), - DropdownMenuItem( - value: 'SAMAN', - child: Text( - tr('domain.company.saman'), - ), - ), - DropdownMenuItem( - value: 'PTC', - child: Text( - tr( - 'domain.company.ptc', - fallback: 'PTC', - ), - ), - ), - DropdownMenuItem( - value: 'JANGHEON', - child: Text( - tr('domain.company.jangheon'), - ), - ), - DropdownMenuItem( - value: 'BARON', - child: Text( - tr('domain.company.baron'), - ), - ), - DropdownMenuItem( - value: 'HALLA', - child: Text( - tr('domain.company.halla'), - ), - ), - ], + items: _tenants.map((t) { + return DropdownMenuItem( + value: t['slug'], + child: Text(t['name'] ?? t['slug']), + ); + }).toList(), onChanged: _isAffiliateEmail ? null : (val) => setState(