Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix registration bugs, update input design, and improve validation logic #543

Merged
merged 34 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
256de47
Rename SignedIn and SignedOut to LoggedIn and LoggedOut
tjementum Jul 20, 2024
192c803
Add aria-label to input fields on Login Account Registration
tjementum Jul 20, 2024
6181737
Remove FieldError used by native browser validation and allow custom …
tjementum Jul 21, 2024
aa26f48
Simplify Start and Complete Account Registration forms in action.ts b…
tjementum Jul 21, 2024
8297431
Update Login / Sign up forms
raix Jul 21, 2024
ad567de
Unify field height in field / input / select / text field
raix Jul 22, 2024
5203147
Refactor ErrorList / Description / FieldError / Group / TextField
raix Jul 22, 2024
20bb5ce
Add validation for Select
raix Jul 22, 2024
9c6d4a1
Refactor register pages to use a shared design and move components in…
raix Jul 22, 2024
ba49f7f
Make account-registration/start endpoint to return the AccountRegistr…
tjementum Jul 22, 2024
e1409e9
Update react aria components
raix Jul 23, 2024
c1da72b
Clean up pages / routes folder
raix Jul 23, 2024
a880f09
Use Action State instead of Form State
raix Jul 23, 2024
a7084b3
Downgrade to React 18 canary until RAC supports v19
raix Jul 22, 2024
55ce4ba
Add useRegistration for auth actions
raix Jul 23, 2024
17f71a1
Downgrade React useActionState to FormState
raix Jul 23, 2024
9fbbe87
Use Zod instead of typecasting response data
raix Jul 23, 2024
971290e
Update translations
raix Jul 23, 2024
665c919
Disable native browser validation setting validationBehavior to "aria"
raix Jul 23, 2024
db0ce31
Generalize development websocket mode for federated HMR
raix Jul 23, 2024
4e572a7
Add availability icons to DomainInputField
raix Jul 23, 2024
3bb3616
Add useIsSubdomainFree hook
raix Jul 23, 2024
5614791
Move actions to action folder
raix Jul 23, 2024
8257da1
Update DomainInput subdomain availability
raix Jul 23, 2024
6435325
Update register expired action
raix Jul 23, 2024
ece0d14
Fix auth routes
raix Jul 23, 2024
64f86c1
Add spectrum illustrations
raix Jul 23, 2024
1f81ef4
Update styling of illustrated message
raix Jul 23, 2024
3c99ca6
Configure expiration and error illustrated message for auth
raix Jul 23, 2024
04b2298
Remove redundant email on code verification page
raix Jul 23, 2024
489ed08
Update styling Domain Input
raix Jul 24, 2024
ff3a922
Turn off auto correct on Domain Input
raix Jul 24, 2024
db96ccb
Add missing space in start registration form
raix Jul 24, 2024
c95b92d
Remove redundant error message in login
raix Jul 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public void MapEndpoints(IEndpointRouteBuilder routes)
=> await mediator.Send(query)
).Produces<bool>();

group.MapPost("/start", async Task<ApiResult> (StartAccountRegistrationCommand command, ISender mediator)
=> (await mediator.Send(command)).AddResourceUri(RoutesPrefix)
);
group.MapPost("/start", async Task<ApiResult<StartAccountRegistrationResponse>> (StartAccountRegistrationCommand command, ISender mediator)
=> await mediator.Send(command)
).Produces<StartAccountRegistrationResponse>();

group.MapPost("{id}/complete", async Task<ApiResult> (AccountRegistrationId id, CompleteAccountRegistrationCommand command, ISender mediator)
=> await mediator.Send(command with { Id = id })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@
namespace PlatformPlatform.AccountManagement.Application.AccountRegistrations;

public sealed record StartAccountRegistrationCommand(string Subdomain, string Email)
: ICommand, IRequest<Result<AccountRegistrationId>>
: ICommand, IRequest<Result<StartAccountRegistrationResponse>>
{
public TenantId GetTenantId()
{
return new TenantId(Subdomain);
}
}

public sealed record StartAccountRegistrationResponse(string AccountRegistrationId, int ValidForSeconds);

public sealed class StartAccountRegistrationValidator : AbstractValidator<StartAccountRegistrationCommand>
{
public StartAccountRegistrationValidator(ITenantRepository tenantRepository)
Expand All @@ -40,23 +42,23 @@
IEmailService emailService,
IPasswordHasher<object> passwordHasher,
ITelemetryEventsCollector events
) : IRequestHandler<StartAccountRegistrationCommand, Result<AccountRegistrationId>>
) : IRequestHandler<StartAccountRegistrationCommand, Result<StartAccountRegistrationResponse>>
{
public async Task<Result<AccountRegistrationId>> Handle(StartAccountRegistrationCommand command, CancellationToken cancellationToken)
public async Task<Result<StartAccountRegistrationResponse>> Handle(StartAccountRegistrationCommand command, CancellationToken cancellationToken)
{
var existingAccountRegistrations
= accountRegistrationRepository.GetByEmailOrTenantId(command.GetTenantId(), command.Email);

if (existingAccountRegistrations.Any(r => !r.HasExpired()))

Check warning on line 52 in application/account-management/Application/AccountRegistrations/StartAccountRegistration.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Collection-specific "Exists" method should be used instead of the "Any" extension. (https://rules.sonarsource.com/csharp/RSPEC-6605)

Check warning on line 52 in application/account-management/Application/AccountRegistrations/StartAccountRegistration.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Collection-specific "Exists" method should be used instead of the "Any" extension. (https://rules.sonarsource.com/csharp/RSPEC-6605)

Check warning on line 52 in application/account-management/Application/AccountRegistrations/StartAccountRegistration.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Collection-specific "Exists" method should be used instead of the "Any" extension. (https://rules.sonarsource.com/csharp/RSPEC-6605)

Check warning on line 52 in application/account-management/Application/AccountRegistrations/StartAccountRegistration.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Collection-specific "Exists" method should be used instead of the "Any" extension. (https://rules.sonarsource.com/csharp/RSPEC-6605)
{
return Result<AccountRegistrationId>.Conflict(
return Result<StartAccountRegistrationResponse>.Conflict(
"Account registration for this subdomain/mail has already been started. Please check your spam folder."
);
}

if (existingAccountRegistrations.Count(r => r.CreatedAt > TimeProvider.System.GetUtcNow().AddDays(-1)) > 3)
{
return Result<AccountRegistrationId>.TooManyRequests("Too many attempts to register this email address. Please try again later.");
return Result<StartAccountRegistrationResponse>.TooManyRequests("Too many attempts to register this email address. Please try again later.");
}

var oneTimePassword = GenerateOneTimePassword(6);
Expand All @@ -75,7 +77,7 @@
cancellationToken
);

return accountRegistration.Id;
return new StartAccountRegistrationResponse(accountRegistration.Id, accountRegistration.GetValidForSeconds());
}

public static string GenerateOneTimePassword(int length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ namespace PlatformPlatform.AccountManagement.Domain.AccountRegistrations;
public sealed class AccountRegistration : AggregateRoot<AccountRegistrationId>
{
public const int MaxAttempts = 3;
private const int ValidForSeconds = 300;

private AccountRegistration(TenantId tenantId, string email, string oneTimePasswordHash)
: base(AccountRegistrationId.NewId())
{
TenantId = tenantId;
Email = email;
OneTimePasswordHash = oneTimePasswordHash;
ValidUntil = CreatedAt.AddMinutes(5);
ValidUntil = CreatedAt.AddSeconds(ValidForSeconds);
}

public TenantId TenantId { get; private set; }
Expand Down Expand Up @@ -56,6 +57,11 @@ public void MarkAsCompleted()

Completed = true;
}

public int GetValidForSeconds()
{
return Convert.ToInt16((ValidUntil - TimeProvider.System.GetUtcNow()).TotalSeconds);
}
}

[TypeConverter(typeof(StronglyTypedIdTypeConverter<string, AccountRegistrationId>))]
Expand Down
17 changes: 17 additions & 0 deletions application/account-management/WebApp/pages/(auth)/login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createFileRoute } from "@tanstack/react-router";
import { HorizontalHeroLayout } from "@/shared/ui/layout/HorizontalHeroLayout";
import { LoginForm } from "@/shared/ui/auth/LoginForm";
import { ErrorMessage } from "@/shared/ui/auth/ErrorMessage";

export const Route = createFileRoute("/(auth)/login")({
component: () => (
<HorizontalHeroLayout>
<LoginForm />
</HorizontalHeroLayout>
),
errorComponent: (props) => (
<HorizontalHeroLayout>
<ErrorMessage {...props} />
</HorizontalHeroLayout>
)
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createFileRoute } from "@tanstack/react-router";
import { HorizontalHeroLayout } from "@/shared/ui/layout/HorizontalHeroLayout";
import { ErrorMessage } from "@/shared/ui/auth/ErrorMessage";
import { VerificationCodeExpiredMessage } from "@/shared/ui/auth/VerificationCodeExpiredMessage";

export const Route = createFileRoute("/(auth)/register/expired")({
component: () => (
<HorizontalHeroLayout>
<VerificationCodeExpiredMessage />
</HorizontalHeroLayout>
),
errorComponent: (props) => (
<HorizontalHeroLayout>
<ErrorMessage {...props} />
</HorizontalHeroLayout>
)
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createFileRoute } from "@tanstack/react-router";
import { HorizontalHeroLayout } from "@/shared/ui/layout/HorizontalHeroLayout";
import { StartAccountRegistrationForm } from "@/shared/ui/auth/StartAccountRegistrationForm";
import { ErrorMessage } from "@/shared/ui/auth/ErrorMessage";

export const Route = createFileRoute("/(auth)/register/")({
component: () => (
<HorizontalHeroLayout>
<StartAccountRegistrationForm />
</HorizontalHeroLayout>
),
errorComponent: (props) => (
<HorizontalHeroLayout>
<ErrorMessage {...props} />
</HorizontalHeroLayout>
)
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createFileRoute } from "@tanstack/react-router";
import { HorizontalHeroLayout } from "@/shared/ui/layout/HorizontalHeroLayout";
import { CompleteAccountRegistrationForm } from "@/shared/ui/auth/CompleteAccountRegistrationForm";
import { ErrorMessage } from "@/shared/ui/auth/ErrorMessage";

export const Route = createFileRoute("/(auth)/register/verify")({
component: () => (
<HorizontalHeroLayout>
<CompleteAccountRegistrationForm />
</HorizontalHeroLayout>
),
errorComponent: (props) => (
<HorizontalHeroLayout>
<ErrorMessage {...props} />
</HorizontalHeroLayout>
)
});
28 changes: 14 additions & 14 deletions application/account-management/WebApp/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { createFileRoute } from "@tanstack/react-router";
import { CtaSection } from "./-landing/sections/CtaSection";
import { FeatureSection } from "./-landing/sections/FeatureSection";
import { FeatureSection2 } from "./-landing/sections/FeatureSection2";
import { HeroSection } from "./-landing/sections/HeroSection";
import { FeatureSection3 } from "./-landing/sections/FeatureSection3";
import { TechnologySection } from "./-landing/sections/TechnologySection";
import { TechnologySection2 } from "./-landing/sections/TechnologySection2";
import { CommunitySection } from "./-landing/sections/CommunitySection";
import { FeatureSection4 } from "./-landing/sections/FeatureSection4";
import { CtaSection2 } from "./-landing/sections/CtaSection2";
import { CtaSection3 } from "./-landing/sections/CtaSection3";
import { FooterSection } from "./-landing/sections/FooterSection";
import { CtaSection } from "../shared/ui/landingPage/sections/CtaSection";
import { FeatureSection } from "../shared/ui/landingPage/sections/FeatureSection";
import { FeatureSection2 } from "../shared/ui/landingPage/sections/FeatureSection2";
import { HeroSection } from "../shared/ui/landingPage/sections/HeroSection";
import { FeatureSection3 } from "../shared/ui/landingPage/sections/FeatureSection3";
import { TechnologySection } from "../shared/ui/landingPage/sections/TechnologySection";
import { TechnologySection2 } from "../shared/ui/landingPage/sections/TechnologySection2";
import { CommunitySection } from "../shared/ui/landingPage/sections/CommunitySection";
import { FeatureSection4 } from "../shared/ui/landingPage/sections/FeatureSection4";
import { CtaSection2 } from "../shared/ui/landingPage/sections/CtaSection2";
import { CtaSection3 } from "../shared/ui/landingPage/sections/CtaSection3";
import { FooterSection } from "../shared/ui/landingPage/sections/FooterSection";

export const Route = createFileRoute("/")({
component: Home
component: LandingPage
});

function Home() {
function LandingPage() {
return (
<main className="flex w-full flex-col">
<HeroSection />
Expand Down

This file was deleted.

9 changes: 0 additions & 9 deletions application/account-management/WebApp/pages/login/_layout.tsx

This file was deleted.

This file was deleted.

Loading