Skip to content

Feature/extra registration#478

Merged
NielsPilgaard merged 15 commits into
mainfrom
feature/extra-registration
Dec 27, 2025
Merged

Feature/extra registration#478
NielsPilgaard merged 15 commits into
mainfrom
feature/extra-registration

Conversation

@NielsPilgaard

Copy link
Copy Markdown
Owner

No description provided.

@coderabbitai

coderabbitai Bot commented Dec 26, 2025

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@NielsPilgaard has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 32 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 14aa573 and 445b31d.

📒 Files selected for processing (3)
  • src/web/Jordnaer/Components/Account/Pages/ConfirmEmail.razor
  • src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor
  • src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor

Walkthrough

Adds a profile-completion flow with two new pages and services, enforces unique non-null usernames via an EF Core migration, updates auth flows to redirect incomplete profiles to completion pages, and introduces advertisement domain/service and related UI/DI changes.

Changes

Cohort / File(s) Summary
Model & Migration
src/shared/Jordnaer.Shared/Database/UserProfile.cs, src/web/Jordnaer/Migrations/20251226115122_Unique_Username.*, src/web/Jordnaer/Migrations/JordnaerDbContextModelSnapshot.cs, src/web/Jordnaer/Migrations/migration_script.sql
Made UserName index unique with a filtered index (WHERE [UserName] IS NOT NULL) and added EF migration/snapshot and SQL script. Review DB downtime/rollout and index creation effects.
Profile service & logic
src/web/Jordnaer/Features/Profile/ProfileService.cs
Added IsProfileCompleteAsync and GenerateUniqueUsernameAsync (normalization + suffix loop); ensure username generation/query semantics and race conditions are acceptable.
Auth pages (redirect flow + DI)
src/web/Jordnaer/Components/Account/Pages/ConfirmEmail.razor, src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor, src/web/Jordnaer/Components/Account/Pages/Register.razor
Injected IProfileService; changed post-auth redirects to check profile completeness and redirect incomplete users to /CompleteProfile; ExternalLogin attempts to generate/assign unique username for new external users. Verify error handling and redirect query propagation.
Profile completion UI
src/web/Jordnaer/Pages/Profile/CompleteProfile.razor, src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor
New authorized multi-step pages for basic info and interests with validation, location resolution, username generation, caching, and navigation/ReturnUrl handling. Review UX, validation rules, and LocationService integration.
Database seeding
src/web/Jordnaer/Database/DatabaseInitialyzer.cs
Deduplicate generated test users with DistinctBy(...) and return existing categories when already seeded. Check seeding idempotency.
Ads domain & DI
tasks/03-advertisement-management-system.md, src/shared/.../Ads/*, src/web/Jordnaer/Features/Ads/*, Program.cs, UserSearchResultComponent (references)
Added Advertisement entity, DTO, IAdvertisementService/AdvertisementService, DbSet, DI and integrations for serving/tracking ads. Review DB migrations, seeding, and ad-rendering integration.
UI tweaks
src/web/Jordnaer/Features/Profile/UserProfileCard.razor, src/web/Jordnaer/Pages/Profile/MyProfile.razor
Removed "Om mig" heading; MyProfile sets a default DOB picker value and avoids persisting it (resets to null on save).
Health check
src/web/Jordnaer/Features/UserSearch/DataForsyningenHealthCheck.cs
Added catch-all exception handler to log and return HealthCheckResult.Unhealthy.
Config
src/web/Jordnaer/appsettings.json
Enabled FeatureManagement.Posts (false → true).
Tests
tests/web/Jordnaer.E2E.Tests/LandingPageTests.cs
Removed Ignore attribute so a posts-related landing-page E2E test now runs.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant AuthPage as Auth (Register/Confirm/ExternalLogin)
    participant ProfileSvc as ProfileService
    participant DB as Database
    participant Complete as CompleteProfile Pages
    participant LocationSvc as LocationService
    participant Nav as NavigationManager

    User->>AuthPage: completes auth (register/confirm/external login)
    AuthPage->>DB: create/confirm user record
    AuthPage->>ProfileSvc: IsProfileCompleteAsync(userId)
    ProfileSvc->>DB: Query UserProfile by userId
    DB-->>ProfileSvc: UserProfile (or null)
    alt profile incomplete
        ProfileSvc-->>AuthPage: false
        AuthPage->>Nav: Navigate to /CompleteProfile?returnUrl=...
        Nav-->>User: Show CompleteProfile
        User->>Complete: Submit basic info (name,DOB,address/ZIP)
        alt address selected
            Complete->>LocationSvc: Resolve location
            LocationSvc-->>Complete: Location result
        end
        Complete->>ProfileSvc: GenerateUniqueUsernameAsync(first,last)
        ProfileSvc->>DB: Query usernames starting with base pattern
        DB-->>ProfileSvc: existing usernames
        ProfileSvc-->>Complete: unique username (or error)
        Complete->>ProfileSvc: UpdateUserProfile (username,location,...)
        ProfileSvc->>DB: Persist profile
        DB-->>ProfileSvc: OK
        Complete->>Nav: Navigate to /CompleteProfileInterests (or returnUrl)
    else profile complete
        ProfileSvc-->>AuthPage: true
        AuthPage->>Nav: Navigate to original ReturnUrl or home
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Feature/metrics #409 — Modifies ExternalLogin.razor and external-login flow; closely related to external user creation/redirect changes.
  • Feature/get launch ready #378 — Changes DatabaseInitialyzer.cs (seeding logic); related to deduplication and category seeding edits.
  • Feature/topology #474 — Related model/migration work and location/NetTopologySuite changes touching UserProfile/migration/model snapshot.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author, making it impossible to assess whether any description relates to the changeset. Add a detailed pull request description explaining the motivation, implementation approach, and key changes introduced by this PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Feature/extra registration' is vague and does not clearly convey the main changes. The PR includes profile completion flow, unique username generation, email confirmation redirect logic, external login handling, database migrations, and feature flag updates—far beyond just 'extra registration'. Use a more specific title that captures the primary objective, such as 'Add profile completion flow and unique username validation' or 'Implement post-registration profile completion requirements'.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tasks/03-advertisement-management-system.md (1)

156-174: Replace Guid.NewGuid() randomization with client-side shuffle after limiting results.

Line 164's .ThenBy(_ => Guid.NewGuid()) translates to SQL ORDER BY NEWID(), which is inefficient because it forces the database to compute a random value and sort by it across the entire filtered result set before taking the top N rows. This is a known anti-pattern and causes poor performance on larger datasets.

Instead, shuffle only the limited result set client-side:

.Take(count)
.AsEnumerable()
.OrderByDescending(ad => ad.Priority)
.ThenBy(_ => Guid.NewGuid())

This applies randomization only to the small result set (10 items by default) rather than the entire filtered table, eliminating the server-side sorting bottleneck.

src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor (1)

178-180: Use GenerateUniqueUsernameAsync for external login users to prevent duplicate username database constraint violations.

Line 178 creates a UserProfile using ToUserProfile(), which generates usernames via simple string manipulation (fullName.Replace(" ", "")) without uniqueness validation. With the unique constraint on UserProfile.UserName (line 9 of UserProfile.cs), two external users with the same full name will cause SaveChangesAsync() at line 180 to throw an unhandled DbUpdateException.

Replace the simple username generation with a call to GenerateUniqueUsernameAsync(user.FirstName, user.LastName) before saving, similar to how CompleteProfile.razor handles it.

🧹 Nitpick comments (9)
tasks/03-advertisement-management-system.md (4)

28-69: Consider adding SponsorId field for multi-tenant extensibility.

The document states the system should be "extensible for future multi-sponsor needs," but the schema lacks a SponsorId field. Adding it now (even if not used immediately) would avoid a migration later:

SponsorId           INT NOT NULL DEFAULT 1,  -- Default to single sponsor initially
FOREIGN KEY (SponsorId) REFERENCES Sponsors(Id),

This allows the system to scale to multiple sponsors without a schema migration, aligning with the stated extensibility goal.


337-383: Clarify acceptance criteria with specific validation rules and test cases.

Several acceptance criteria are too vague for reliable implementation and testing:

  • Line 362 ("Ads display correctly"): Define what "correctly" means (layout, responsive behavior, fallback when no ads exist)
  • Line 383 ("CTR calculated correctly"): Specify where CTR is calculated/displayed and provide test scenarios (e.g., 100 views + 10 clicks = 10%)
  • Lines 370-376 (Admin UI form validation): Define required vs. optional fields, acceptable image formats/sizes, URL validation rules

Suggest expanding these to be testable:

  • ✅ AdCard renders with image, title, and description when ad data is present
  • ✅ UserSearch displays no ad container when AdService returns empty list
  • ✅ CTR formula (clicks / views) * 100 is displayed in backoffice analytics and matches database values

355-355: Explicitly document DI registration pattern.

The spec should mention that service registration follows the project pattern: create AddAdsServices() in a Features/Ads/WebApplicationBuilderExtensions.cs and call it from Program.cs, per project conventions noted in coding guidelines.

I can generate the WebApplicationBuilderExtensions code snippet if you'd like.


312-336: Expand admin UI specification with validation rules and authorization clarity.

The admin interface section is too brief for reliable implementation. Consider adding:

Form Validation:

  • Title: required, max 200 characters
  • Description: optional, max 500 characters
  • ImagePath: required, must be a valid blob path (or upload UI guidance)
  • Link: required, must be a valid URL (http/https)
  • Placement: required, must be a valid AdPlacement constant
  • StartDate/EndDate: optional, EndDate > StartDate if both provided
  • Priority: non-negative integer

Authorization & Security:

  • Confirm [Authorize(Policy = "AdminOnly")] matches the policy defined in Task 02 (Backoffice Claims)
  • Clarify if only admins can manage ads or if sponsor self-service (Task 01) introduces sponsor-specific ad management

Delete Strategy:

  • Hard delete or soft delete (archive)?
  • What happens to analytics if an ad is deleted?
src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor (1)

134-143: Consider propagating ReturnUrl to CompleteProfile for consistency.

Unlike Register.razor which passes ReturnUrl to the CompleteProfile flow, the external login paths (both existing and new users) don't propagate the ReturnUrl. This means users who initiated external login from a specific page will lose their intended destination after completing their profile.

For consistency, consider passing ReturnUrl to /CompleteProfile similar to the Register flow.

🔎 Suggested changes

For existing users (lines 134-143):

 // Check if profile is complete and redirect accordingly
 var isProfileComplete = await ProfileService.IsProfileCompleteAsync(user.Id);
 if (!isProfileComplete)
 {
-    RedirectManager.RedirectTo("/CompleteProfile");
+    if (!string.IsNullOrWhiteSpace(ReturnUrl))
+    {
+        RedirectManager.RedirectTo(
+            "/CompleteProfile",
+            queryParameters: new Dictionary<string, object?> { ["returnUrl"] = ReturnUrl });
+    }
+    else
+    {
+        RedirectManager.RedirectTo("/CompleteProfile");
+    }
 }
 else
 {
     RedirectManager.RedirectTo(FirstLoginReturnUrl);
 }

For new users (lines 200-202):

 await SignInManager.SignInAsync(user, isPersistent: false, _externalLoginInfo.LoginProvider);

 // Redirect new external users to profile completion
-RedirectManager.RedirectTo("/CompleteProfile");
+if (!string.IsNullOrWhiteSpace(ReturnUrl))
+{
+    RedirectManager.RedirectTo(
+        "/CompleteProfile",
+        queryParameters: new Dictionary<string, object?> { ["returnUrl"] = ReturnUrl });
+}
+else
+{
+    RedirectManager.RedirectTo("/CompleteProfile");
+}

Also applies to: 200-202

src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor (1)

121-158: Consider handling the case when result.AsT0.Value is accessed.

The save logic correctly uses the OneOf pattern. However, at Line 151, result.AsT0.Value is accessed after checking result.IsT1 is false, which is correct. The flow looks good.

One minor note: the _input model (Lines 160-163) is currently empty and unused for validation. If validation is planned for the future, consider documenting this or removing the model if not needed.

🔎 Optional: Remove unused InputModel or add a TODO
-    private sealed class InputModel
-    {
-        // Used for validation if needed in the future
-    }
+    // Note: InputModel removed as no form validation is currently needed for category selection

Alternatively, keep it with a clearer comment if future validation is planned.

src/web/Jordnaer/Features/Profile/ProfileService.cs (1)

155-172: Profile completeness check follows guidelines.

The method correctly uses CreateDbContextAsync and AsNoTracking() for read-only queries as per the coding guidelines.

One minor observation: Line 171's check !string.IsNullOrWhiteSpace(profile.ZipCode?.ToString()) could be simplified to profile.ZipCode.HasValue since ZipCode is int?.

🔎 Optional: Simplify ZipCode null check
 		return !string.IsNullOrWhiteSpace(profile.FirstName) &&
 			   !string.IsNullOrWhiteSpace(profile.LastName) &&
 			   profile.Location is not null &&
-			   (!string.IsNullOrWhiteSpace(profile.ZipCode?.ToString()) || !string.IsNullOrWhiteSpace(profile.Address));
+			   (profile.ZipCode.HasValue || !string.IsNullOrWhiteSpace(profile.Address));
src/web/Jordnaer/Pages/Profile/CompleteProfile.razor (2)

115-129: Static _defaultPickerDate could become stale.

The _defaultPickerDate is a static field initialized to DateTime.Today.AddYears(-30). In long-running server scenarios, this date won't update. Consider making it a computed property or instance field.

🔎 Optional: Make default date dynamic
-    private static DateTime _defaultPickerDate = DateTime.Today.AddYears(-30);
+    private static DateTime DefaultPickerDate => DateTime.Today.AddYears(-30);

Then update references to use DefaultPickerDate instead of _defaultPickerDate.

Also note that _generatedUsername (Line 118) is assigned but never displayed in the UI. If this is intentional (preview only for internal use), consider adding a comment or removing it if unused.


171-180: Username preview generation looks correct but is unused in UI.

The UpdateUsernamePreview() method generates a username preview but _generatedUsername is never displayed to the user. Consider either:

  1. Showing the preview to the user in the UI
  2. Removing the preview logic if not needed
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2442bac and a047e97.

📒 Files selected for processing (17)
  • src/shared/Jordnaer.Shared/Database/UserProfile.cs
  • src/web/Jordnaer/Components/Account/Pages/ConfirmEmail.razor
  • src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor
  • src/web/Jordnaer/Components/Account/Pages/Register.razor
  • src/web/Jordnaer/Database/DatabaseInitialyzer.cs
  • src/web/Jordnaer/Features/Profile/ProfileService.cs
  • src/web/Jordnaer/Features/Profile/UserProfileCard.razor
  • src/web/Jordnaer/Features/UserSearch/DataForsyningenHealthCheck.cs
  • src/web/Jordnaer/Migrations/20251226115122_Unique_Username.Designer.cs
  • src/web/Jordnaer/Migrations/20251226115122_Unique_Username.cs
  • src/web/Jordnaer/Migrations/JordnaerDbContextModelSnapshot.cs
  • src/web/Jordnaer/Migrations/migration_script.sql
  • src/web/Jordnaer/Pages/Profile/CompleteProfile.razor
  • src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor
  • src/web/Jordnaer/appsettings.json
  • tasks/03-advertisement-management-system.md
  • tasks/09-onboarding-profile-completion.md
💤 Files with no reviewable changes (1)
  • src/web/Jordnaer/Features/Profile/UserProfileCard.razor
🧰 Additional context used
📓 Path-based instructions (2)
src/web/**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/web/**/*.cs: Always use 'await using var context = await contextFactory.CreateDbContextAsync()' for scoped DbContext access
Use AsNoTracking() for read-only queries in EF Core
Use OneOf<T, TError> for explicit error handling instead of exceptions or null returns
Use IHubContext for broadcasting SignalR messages to specific user groups
Use custom logger extensions like logger.LogFunctionBegan() and include diagnostic context with diagnosticContext.Set()
Publish messages using 'await publishEndpoint.Publish(/* ... */, cancellationToken)'
Use FluentValidation for input validation
Use IFeatureManager for feature flags
Use custom RateLimitExtensions for rate limiting
Apply NetEscapades.AspNetCore.SecurityHeaders for HTTP security headers
Use SixLabors.ImageSharp for image processing
Use Markdig for Markdown parsing
Use Azure Communication Email service for email sending
Use Azure Blob Storage for storing profile pictures and attachments
Use DSFAPI for Danish civil registry search functionality
Support OAuth authentication with Google, Microsoft, and Facebook providers

Files:

  • src/web/Jordnaer/Database/DatabaseInitialyzer.cs
  • src/web/Jordnaer/Migrations/20251226115122_Unique_Username.cs
  • src/web/Jordnaer/Migrations/JordnaerDbContextModelSnapshot.cs
  • src/web/Jordnaer/Migrations/20251226115122_Unique_Username.Designer.cs
  • src/web/Jordnaer/Features/Profile/ProfileService.cs
  • src/web/Jordnaer/Features/UserSearch/DataForsyningenHealthCheck.cs
src/web/**/Components/**/*.razor

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Razor components should use MudBlazor v8.11.0 components for UI

Files:

  • src/web/Jordnaer/Components/Account/Pages/Register.razor
  • src/web/Jordnaer/Components/Account/Pages/ConfirmEmail.razor
  • src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor
🧠 Learnings (8)
📚 Learning: 2025-12-20T19:25:14.348Z
Learnt from: CR
Repo: NielsPilgaard/Jordnaer PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-20T19:25:14.348Z
Learning: Applies to src/web/**/*.cs : Use IFeatureManager for feature flags

Applied to files:

  • src/web/Jordnaer/appsettings.json
📚 Learning: 2025-12-20T19:25:14.348Z
Learnt from: CR
Repo: NielsPilgaard/Jordnaer PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-20T19:25:14.348Z
Learning: Applies to src/web/**/*.cs : Use Azure Communication Email service for email sending

Applied to files:

  • src/web/Jordnaer/Components/Account/Pages/ConfirmEmail.razor
📚 Learning: 2025-12-20T19:25:14.348Z
Learnt from: CR
Repo: NielsPilgaard/Jordnaer PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-20T19:25:14.348Z
Learning: Applies to src/web/**/*.cs : Support OAuth authentication with Google, Microsoft, and Facebook providers

Applied to files:

  • src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor
📚 Learning: 2025-12-20T19:25:14.348Z
Learnt from: CR
Repo: NielsPilgaard/Jordnaer PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-20T19:25:14.348Z
Learning: Applies to src/web/**/Components/**/*.razor : Razor components should use MudBlazor v8.11.0 components for UI

Applied to files:

  • src/web/Jordnaer/Pages/Profile/CompleteProfile.razor
  • tasks/03-advertisement-management-system.md
  • src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor
📚 Learning: 2025-12-20T19:25:14.348Z
Learnt from: CR
Repo: NielsPilgaard/Jordnaer PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-20T19:25:14.348Z
Learning: Applies to src/web/**/*.cs : Use DSFAPI for Danish civil registry search functionality

Applied to files:

  • src/web/Jordnaer/Features/Profile/ProfileService.cs
📚 Learning: 2025-12-25T21:16:38.933Z
Learnt from: NielsPilgaard
Repo: NielsPilgaard/Jordnaer PR: 476
File: src/web/Jordnaer/Features/Ad/AdCard.razor:3-3
Timestamp: 2025-12-25T21:16:38.933Z
Learning: In AdCard.razor and similar ad components, the project intentionally preserves the Referer header when opening ad links in new tabs (Target="_blank") to enable ad referral tracking and analytics.

Applied to files:

  • tasks/03-advertisement-management-system.md
📚 Learning: 2025-12-25T21:51:44.292Z
Learnt from: NielsPilgaard
Repo: NielsPilgaard/Jordnaer PR: 476
File: src/web/Jordnaer/Features/UserSearch/UserSearchResultComponent.razor:70-87
Timestamp: 2025-12-25T21:51:44.292Z
Learning: In UserSearchResultComponent.razor, the GetItemsWithAds() method has a bug where it adds both an ad and a user at the same index position, resulting in 13 items instead of 12. Currently, this is worked around by passing only 11 users to the component (11 users + 1 ad = 12 items total), but the underlying logic should be fixed to use `else if` or a separate user index counter like in GroupSearchResultComponent.

Applied to files:

  • tasks/03-advertisement-management-system.md
📚 Learning: 2025-12-20T19:25:14.348Z
Learnt from: CR
Repo: NielsPilgaard/Jordnaer PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-20T19:25:14.348Z
Learning: Applies to **/Features/**/WebApplicationBuilderExtensions.cs : DI setup must be in WebApplicationBuilderExtensions (named AddYourFeatureServices()) called from Program.cs

Applied to files:

  • tasks/03-advertisement-management-system.md
🧬 Code graph analysis (2)
src/web/Jordnaer/Database/DatabaseInitialyzer.cs (1)
src/shared/Jordnaer.Shared/Database/Category.cs (1)
  • Category (6-19)
src/web/Jordnaer/Migrations/20251226115122_Unique_Username.Designer.cs (2)
src/web/Jordnaer/Migrations/JordnaerDbContextModelSnapshot.cs (1)
  • DbContext (14-893)
src/web/Jordnaer/Migrations/20251226115122_Unique_Username.cs (1)
  • Unique_Username (8-37)
🔇 Additional comments (21)
tasks/03-advertisement-management-system.md (1)

1-13: Clarify PR title and scope mismatch.

The PR is titled "Feature/extra registration" and the objectives mention profile completion enforcement and username uniqueness, but this specification document is entirely about advertisement management. Please confirm:

  1. Is this the correct file for this PR, or is there another file (e.g., tasks/02-profile-completion.md) that should be reviewed?
  2. Are both profile registration and ad management features being implemented in this PR?

This will help ensure the review is focused on the correct implementation scope.

src/web/Jordnaer/Database/DatabaseInitialyzer.cs (1)

79-79: LGTM!

Returning the existing categories when they're already present is more useful and consistent than returning an empty collection. This ensures callers always receive the category list they need.

src/web/Jordnaer/Features/UserSearch/DataForsyningenHealthCheck.cs (1)

36-40: LGTM!

Adding a general exception handler improves the health check's resilience. The positioning after specific exception handlers is correct, and returning Unhealthy with exception details is appropriate for unexpected failures.

src/shared/Jordnaer.Shared/Database/UserProfile.cs (1)

9-9: LGTM!

The unique constraint on UserName is properly implemented. Since UserName is nullable, the migration correctly includes a filter to enforce uniqueness only for non-null values, allowing multiple users without usernames during the registration/onboarding process.

tasks/09-onboarding-profile-completion.md (1)

150-150: LGTM!

The domain name update from jordnaer.dk to mini-moeder.dk is consistently applied across the documentation. This appears to be a rebranding or domain change with no functional impact.

Also applies to: 460-460

src/web/Jordnaer/Migrations/JordnaerDbContextModelSnapshot.cs (1)

517-519: LGTM!

The model snapshot correctly reflects the unique username constraint with a filter for non-null values, consistent with the migration and the UserProfile model changes.

src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor (1)

6-6: LGTM!

Clean addition of the IProfileService dependency to support profile completeness checks.

Also applies to: 17-17

src/web/Jordnaer/Components/Account/Pages/Register.razor (1)

182-192: Open redirect protection and CompleteProfile flow are properly implemented.

The redirect flow is secure:

  • IdentityRedirectManager.RedirectTo() validates URIs with Uri.IsWellFormedUriString(uri, UriKind.Relative) before redirecting, preventing open redirect vulnerabilities.
  • The query parameter overload uses navigationManager.GetUriWithQueryParameters() for safe parameter construction.

CompleteProfile.razor correctly handles the returnUrl parameter:

  • Captures it via [SupplyParameterFromQuery] (line 127).
  • Safely propagates it to the next step using Uri.EscapeDataString() (line 310).
src/web/Jordnaer/Migrations/20251226115122_Unique_Username.cs (2)

25-36: LGTM!

The Down() method correctly reverts the unique constraint by recreating the original non-unique index.


11-23: Ensure no duplicate non-null usernames exist before applying this migration.

The CREATE UNIQUE INDEX will fail at runtime if UserProfiles contains duplicate non-null UserName values. Although the entity already declares a unique index on UserName, consider validating existing data or adding a pre-migration cleanup step to handle any potential duplicates before deploying.

src/web/Jordnaer/Migrations/migration_script.sql (1)

760-783: LGTM!

The SQL migration script correctly implements the idempotent unique username index creation with proper guards and matches the EF Core migration logic.

src/web/Jordnaer/Components/Account/Pages/ConfirmEmail.razor (2)

5-9: LGTM!

The dependency injection for IProfileService is correctly added to support the profile completion check. As per coding guidelines, the service follows the expected pattern for profile operations.


59-69: Profile completion redirect logic looks correct.

The flow properly checks profile completeness after sign-in and redirects accordingly. One minor UX consideration: the success message at Line 55 may not be visible since the redirect happens immediately. Consider if users need to see this confirmation before redirecting.

src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor (3)

1-14: LGTM!

The page setup correctly uses MudBlazor components and follows the authorization pattern. Dependencies are appropriately injected.


89-110: LGTM!

The initialization logic properly handles the non-interactive render check, loads profile data from cache, and pre-populates categories. Error handling with Snackbar feedback is appropriate.


166-249: LGTM!

The CSS styling provides a clean progress indicator with appropriate responsive breakpoints. The use of CSS custom properties (--mud-palette-*) ensures consistency with the MudBlazor theme.

src/web/Jordnaer/Migrations/20251226115122_Unique_Username.Designer.cs (1)

1-897: LGTM!

This is an auto-generated EF Core migration designer file. The UserProfile entity configuration at Lines 520-522 correctly reflects the unique filtered index on UserName, which aligns with the migration logic.

src/web/Jordnaer/Features/Profile/ProfileService.cs (1)

30-46: LGTM!

The new interface methods are well-documented with XML comments and follow the OneOf pattern for explicit error handling, as recommended in the coding guidelines.

src/web/Jordnaer/Pages/Profile/CompleteProfile.razor (3)

1-14: LGTM!

The page setup correctly uses MudBlazor components with proper dependency injection. The [Authorize] attribute ensures only authenticated users can access the profile completion flow.


216-313: Save logic is well-structured with proper error handling.

The SaveBasicInfo method correctly:

  • Validates required fields
  • Generates unique username
  • Resolves location data with appropriate error messages
  • Updates profile and cache
  • Navigates to step 2

The OneOf pattern is used correctly for error handling as per coding guidelines.


315-326: LGTM!

The InputModel validation attributes correctly enforce required fields with appropriate max lengths matching the database schema (FirstName max 100, LastName max 250).

Comment thread src/web/Jordnaer/appsettings.json
Comment thread src/web/Jordnaer/Database/DatabaseInitialyzer.cs
Comment thread src/web/Jordnaer/Features/Profile/ProfileService.cs
@github-project-automation github-project-automation Bot moved this from Todo to In Progress in Jordnaer Community Website Dec 26, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a047e97 and 4e3be71.

📒 Files selected for processing (3)
  • src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor
  • src/web/Jordnaer/Database/DatabaseInitialyzer.cs
  • src/web/Jordnaer/Features/Profile/ProfileService.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/web/Jordnaer/Database/DatabaseInitialyzer.cs
🧰 Additional context used
📓 Path-based instructions (2)
src/web/**/Components/**/*.razor

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Razor components should use MudBlazor v8.11.0 components for UI

Files:

  • src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor
src/web/**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/web/**/*.cs: Always use 'await using var context = await contextFactory.CreateDbContextAsync()' for scoped DbContext access
Use AsNoTracking() for read-only queries in EF Core
Use OneOf<T, TError> for explicit error handling instead of exceptions or null returns
Use IHubContext for broadcasting SignalR messages to specific user groups
Use custom logger extensions like logger.LogFunctionBegan() and include diagnostic context with diagnosticContext.Set()
Publish messages using 'await publishEndpoint.Publish(/* ... */, cancellationToken)'
Use FluentValidation for input validation
Use IFeatureManager for feature flags
Use custom RateLimitExtensions for rate limiting
Apply NetEscapades.AspNetCore.SecurityHeaders for HTTP security headers
Use SixLabors.ImageSharp for image processing
Use Markdig for Markdown parsing
Use Azure Communication Email service for email sending
Use Azure Blob Storage for storing profile pictures and attachments
Use DSFAPI for Danish civil registry search functionality
Support OAuth authentication with Google, Microsoft, and Facebook providers

Files:

  • src/web/Jordnaer/Features/Profile/ProfileService.cs
🧠 Learnings (1)
📚 Learning: 2025-12-20T19:25:14.348Z
Learnt from: CR
Repo: NielsPilgaard/Jordnaer PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-20T19:25:14.348Z
Learning: Applies to src/web/**/*.cs : Support OAuth authentication with Google, Microsoft, and Facebook providers

Applied to files:

  • src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor
🔇 Additional comments (6)
src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor (3)

6-6: LGTM! Proper dependency injection.

The ProfileService injection follows standard patterns and enables the profile completion workflow.

Also applies to: 17-17


134-152: LGTM! Profile completeness check correctly gates access.

The implementation properly checks profile completeness and redirects to /CompleteProfile when needed, preserving the returnUrl parameter for post-completion navigation.


222-232: LGTM! Consistent redirect pattern for new users.

The redirect to /CompleteProfile correctly preserves the returnUrl parameter and aligns with the profile completion workflow.

src/web/Jordnaer/Features/Profile/ProfileService.cs (3)

31-46: LGTM! Well-documented interface extensions.

The new methods are clearly documented with XML comments and use appropriate OneOf return types for explicit error handling.


155-172: Profile completeness criteria are reasonable.

The implementation correctly:

  • Uses scoped DbContext with CreateDbContextAsync as per guidelines
  • Applies AsNoTracking() for read-only queries
  • Checks essential fields: FirstName, LastName, Location, and (ZipCode OR Address)

The logic assumes location information (either ZipCode or Address) is required for profile completion, which aligns with the profile completion workflow.


174-216: Case-sensitivity concern is unfounded; code is already safe as-is.

SQL Server's default collation is case-insensitive (CI_AS), so the unique index on UserName prevents both "test123" and "Test123" from existing simultaneously. No changes needed to the code. The suggested EF.Functions.Like fix is unnecessary.

Likely an incorrect or invalid review comment.

Comment thread src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/web/Jordnaer/Pages/Profile/MyProfile.razor (1)

192-193: Potential edge case with DateTime.Today across midnight.

The DefaultPickerDate property uses DateTime.Today, which is evaluated each time it's accessed. If a user opens the profile editor before midnight and saves after midnight, DateTime.Today.AddYears(-30) will return different values, potentially preventing the null-reset logic from working correctly and persisting an unintended default date.

Consider caching the default date on initialization:

🔎 Proposed fix
-    private static DateTime DefaultPickerDate => DateTime.Today.AddYears(-30);
+    private DateTime _defaultPickerDate;

     protected override async Task OnInitializedAsync()
     {
         if (!RendererInfo.IsInteractive)
         {
             return;
         }

+        _defaultPickerDate = DateTime.Today.AddYears(-30);
+
         var userProfile = await ProfileCache.GetProfileAsync();
         // ... rest of initialization

Then use _defaultPickerDate in both the init and save logic instead of DefaultPickerDate.

Also applies to: 212-216, 313-317

src/web/Jordnaer/Pages/Profile/CompleteProfile.razor (1)

164-179: Location is fetched twice - in handler and again in save.

OnAddressLocationChanged and OnZipCodeLocationChanged fetch location data to validate and show errors, but the results aren't cached. The SaveBasicInfo method then re-fetches the same data (lines 227, 237), resulting in duplicate API calls.

🔎 Consider caching the location result
+    private LocationResult? _cachedLocationResult;
+
     private async Task OnAddressLocationChanged(string address)
     {
         _addressLocation = address;
+        _cachedLocationResult = null;

         if (string.IsNullOrWhiteSpace(address))
         {
             return;
         }

         var result = await LocationService.GetLocationFromAddressAsync(address);
         if (result is null)
         {
             Snackbar.Add("Kunne ikke hente lokationsdata. Prøv venligst igen om et par minutter.",
             Severity.Warning);
         }
+        else
+        {
+            _cachedLocationResult = result;
+        }
     }

Then in SaveBasicInfo, use _cachedLocationResult if available, or fetch as fallback.

Also applies to: 181-196, 223-244

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e3be71 and f66f1cb.

📒 Files selected for processing (9)
  • src/web/Jordnaer/Components/Account/Pages/ConfirmEmail.razor
  • src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor
  • src/web/Jordnaer/Database/DatabaseInitialyzer.cs
  • src/web/Jordnaer/Features/Profile/ProfileService.cs
  • src/web/Jordnaer/Pages/Profile/CompleteProfile.razor
  • src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor
  • src/web/Jordnaer/Pages/Profile/MyProfile.razor
  • tasks/03-advertisement-management-system.md
  • tests/web/Jordnaer.E2E.Tests/LandingPageTests.cs
💤 Files with no reviewable changes (1)
  • tests/web/Jordnaer.E2E.Tests/LandingPageTests.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/web/Jordnaer/Features/Profile/ProfileService.cs
🧰 Additional context used
📓 Path-based instructions (2)
src/web/**/Components/**/*.razor

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Razor components should use MudBlazor v8.11.0 components for UI

Files:

  • src/web/Jordnaer/Components/Account/Pages/ConfirmEmail.razor
  • src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor
src/web/**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/web/**/*.cs: Always use 'await using var context = await contextFactory.CreateDbContextAsync()' for scoped DbContext access
Use AsNoTracking() for read-only queries in EF Core
Use OneOf<T, TError> for explicit error handling instead of exceptions or null returns
Use IHubContext for broadcasting SignalR messages to specific user groups
Use custom logger extensions like logger.LogFunctionBegan() and include diagnostic context with diagnosticContext.Set()
Publish messages using 'await publishEndpoint.Publish(/* ... */, cancellationToken)'
Use FluentValidation for input validation
Use IFeatureManager for feature flags
Use custom RateLimitExtensions for rate limiting
Apply NetEscapades.AspNetCore.SecurityHeaders for HTTP security headers
Use SixLabors.ImageSharp for image processing
Use Markdig for Markdown parsing
Use Azure Communication Email service for email sending
Use Azure Blob Storage for storing profile pictures and attachments
Use DSFAPI for Danish civil registry search functionality
Support OAuth authentication with Google, Microsoft, and Facebook providers

Files:

  • src/web/Jordnaer/Database/DatabaseInitialyzer.cs
🧠 Learnings (6)
📚 Learning: 2025-12-20T19:25:14.348Z
Learnt from: CR
Repo: NielsPilgaard/Jordnaer PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-20T19:25:14.348Z
Learning: Applies to src/web/**/Components/**/*.razor : Razor components should use MudBlazor v8.11.0 components for UI

Applied to files:

  • src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor
  • tasks/03-advertisement-management-system.md
  • src/web/Jordnaer/Pages/Profile/CompleteProfile.razor
📚 Learning: 2025-12-20T19:25:14.348Z
Learnt from: CR
Repo: NielsPilgaard/Jordnaer PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-20T19:25:14.348Z
Learning: Applies to src/web/**/*.cs : Use Azure Communication Email service for email sending

Applied to files:

  • src/web/Jordnaer/Components/Account/Pages/ConfirmEmail.razor
📚 Learning: 2025-12-25T21:16:38.933Z
Learnt from: NielsPilgaard
Repo: NielsPilgaard/Jordnaer PR: 476
File: src/web/Jordnaer/Features/Ad/AdCard.razor:3-3
Timestamp: 2025-12-25T21:16:38.933Z
Learning: In AdCard.razor and similar ad components, the project intentionally preserves the Referer header when opening ad links in new tabs (Target="_blank") to enable ad referral tracking and analytics.

Applied to files:

  • tasks/03-advertisement-management-system.md
📚 Learning: 2025-12-25T21:51:44.292Z
Learnt from: NielsPilgaard
Repo: NielsPilgaard/Jordnaer PR: 476
File: src/web/Jordnaer/Features/UserSearch/UserSearchResultComponent.razor:70-87
Timestamp: 2025-12-25T21:51:44.292Z
Learning: In UserSearchResultComponent.razor, the GetItemsWithAds() method has a bug where it adds both an ad and a user at the same index position, resulting in 13 items instead of 12. Currently, this is worked around by passing only 11 users to the component (11 users + 1 ad = 12 items total), but the underlying logic should be fixed to use `else if` or a separate user index counter like in GroupSearchResultComponent.

Applied to files:

  • tasks/03-advertisement-management-system.md
📚 Learning: 2025-12-20T19:25:14.348Z
Learnt from: CR
Repo: NielsPilgaard/Jordnaer PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-20T19:25:14.348Z
Learning: Applies to **/Features/**/WebApplicationBuilderExtensions.cs : DI setup must be in WebApplicationBuilderExtensions (named AddYourFeatureServices()) called from Program.cs

Applied to files:

  • tasks/03-advertisement-management-system.md
📚 Learning: 2025-12-20T19:25:14.348Z
Learnt from: CR
Repo: NielsPilgaard/Jordnaer PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-20T19:25:14.348Z
Learning: Applies to src/web/**/*.cs : Support OAuth authentication with Google, Microsoft, and Facebook providers

Applied to files:

  • src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor
🧬 Code graph analysis (1)
src/web/Jordnaer/Database/DatabaseInitialyzer.cs (1)
src/shared/Jordnaer.Shared/Database/Category.cs (1)
  • Category (6-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (7)
src/web/Jordnaer/Database/DatabaseInitialyzer.cs (2)

68-70: Past issue resolved - logging now accurate.

The previous concern about logging an inaccurate count before deduplication has been addressed. The code now correctly deduplicates by UserName first, then logs the actual count of users that will be inserted.


79-79: Good use of AsNoTracking() for read-only query.

Returning existing categories with AsNoTracking() correctly follows the coding guidelines for read-only EF Core queries and provides the actual category list needed for seeding users.

As per coding guidelines, AsNoTracking() is used for read-only queries.

src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor (1)

188-204: Username generation error handling looks good.

The error handling now properly logs a warning and displays a user-facing message when username generation fails, addressing the concern from the previous review.

src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor (2)

89-110: Initialization logic looks correct.

The component properly handles non-interactive rendering, null profile scenarios, and pre-populates category selections from existing profile data.


121-162: Save flow is well-implemented.

The save logic correctly updates the description, syncs categories, handles errors with user feedback, updates the cache, and validates ReturnUrl before redirecting.

src/web/Jordnaer/Pages/Profile/CompleteProfile.razor (2)

198-295: Save flow is comprehensive and handles edge cases well.

The save logic properly validates required fields, generates a unique username with error handling, validates location data with appropriate user feedback, handles the DateOfBirth default pattern, and updates the cache before navigation.


297-308: InputModel validation is well-defined.

The validation attributes match reasonable constraints for names and the DateOfBirth initialization aligns with the default picker pattern.

Comment thread src/web/Jordnaer/Components/Account/Pages/ConfirmEmail.razor
Comment thread src/web/Jordnaer/Components/Account/Pages/ExternalLogin.razor Outdated
Comment thread src/web/Jordnaer/Pages/Profile/CompleteProfile.razor
Comment thread src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor (1)

61-64: Skip button now properly preserves ReturnUrl.

The previous concern about the skip button losing ReturnUrl has been addressed. The GetSkipUrl() method now validates and preserves the return URL appropriately.

Also applies to: 121-128

src/web/Jordnaer/Pages/Profile/CompleteProfile.razor (1)

100-103: Skip button now properly preserves ReturnUrl.

The previous concern about the skip button losing ReturnUrl has been addressed. The GetSkipUrl() method now validates and preserves the return URL appropriately.

Also applies to: 164-171

🧹 Nitpick comments (1)
src/web/Jordnaer/Pages/Profile/CompleteProfile.razor (1)

173-205: Consider caching location results to avoid duplicate service calls.

The location change handlers (OnAddressLocationChanged and OnZipCodeLocationChanged) call the LocationService to validate input but discard the results. Later, SaveBasicInfo calls the same services again for the same data. If LocationService makes external API calls, this results in unnecessary duplicate requests.

Consider storing the LocationResult when validation succeeds, then reuse it in SaveBasicInfo:

🔎 Suggested refactor
+    private LocationResult? _cachedLocationResult;
+
     private async Task OnAddressLocationChanged(string address)
     {
         _addressLocation = address;
+        _cachedLocationResult = null;
 
         if (string.IsNullOrWhiteSpace(address))
         {
             return;
         }
 
         var result = await LocationService.GetLocationFromAddressAsync(address);
         if (result is null)
         {
             Snackbar.Add("Kunne ikke hente lokationsdata. Prøv venligst igen om et par minutter.",
             Severity.Warning);
+        }
+        else
+        {
+            _cachedLocationResult = result;
         }
     }

Then in SaveBasicInfo, check cache first:

         LocationResult? locationResult = null;
         if (!_useZipCodeOnly && !string.IsNullOrWhiteSpace(_addressLocation))
         {
-            locationResult = await LocationService.GetLocationFromAddressAsync(_addressLocation);
+            locationResult = _cachedLocationResult ?? await LocationService.GetLocationFromAddressAsync(_addressLocation);
             if (locationResult is null)
             {

Apply similar logic to OnZipCodeLocationChanged.

Also applies to: 234-253

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f66f1cb and 14aa573.

📒 Files selected for processing (2)
  • src/web/Jordnaer/Pages/Profile/CompleteProfile.razor
  • src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-20T19:25:14.348Z
Learnt from: CR
Repo: NielsPilgaard/Jordnaer PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-20T19:25:14.348Z
Learning: Applies to src/web/**/Components/**/*.razor : Razor components should use MudBlazor v8.11.0 components for UI

Applied to files:

  • src/web/Jordnaer/Pages/Profile/CompleteProfile.razor
  • src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor
🔇 Additional comments (2)
src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor (1)

130-171: LGTM!

The SaveInterests method properly handles null checks, validates ReturnUrl, updates the cache, and provides user feedback through Snackbar notifications.

src/web/Jordnaer/Pages/Profile/CompleteProfile.razor (1)

218-230: LGTM!

The username generation properly handles both success and error cases using the OneOf pattern, with appropriate user feedback.

Comment thread src/web/Jordnaer/Pages/Profile/CompleteProfile.razor
Comment thread src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor Outdated
@NielsPilgaard NielsPilgaard merged commit c37df44 into main Dec 27, 2025
2 checks passed
@NielsPilgaard NielsPilgaard deleted the feature/extra-registration branch December 27, 2025 10:57
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Jordnaer Community Website Dec 27, 2025
This was referenced Dec 27, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Feb 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant