Seller registration and verification is the onboarding funnel that converts buyers into active sellers. A frictionless registration (just a few extra fields) maximises conversion; the verification step (admin reviews and approves) ensures trust and safety. The complete flow โ registration โ dashboard โ first listing โ approval โ live โ demonstrates the full classified website working end-to-end with authentication, role assignment, and the listing lifecycle.
Seller Registration Flow
// โโ seller-registration.component.ts โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
@Component({
selector: 'app-seller-registration',
standalone: true,
imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule,
MatButtonModule, MatCheckboxModule],
template: `
<div class="registration-page">
<h1>Become a Seller</h1>
<p>Start listing your items and reaching thousands of buyers.</p>
@if (registered()) {
<!-- Success state โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<div class="success-card" data-cy="registration-success">
<mat-icon color="primary">check_circle</mat-icon>
<h2>You're now a seller!</h2>
<p>You can start creating listings straight away.</p>
<p class="verification-note">
For additional trust badges and unlimited listings,
<strong>apply for seller verification</strong> from your dashboard.
</p>
<button mat-raised-button color="primary"
routerLink="/my-listings/new">
Create Your First Listing
</button>
</div>
} @else {
<!-- Registration form โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<mat-form-field>
<mat-label>Business / Display Name</mat-label>
<input matInput formControlName="sellerName" data-cy="seller-name">
<mat-error *ngIf="form.get('sellerName')?.errors?.['required']">
Display name is required
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Contact Phone</mat-label>
<input matInput formControlName="phone" type="tel">
</mat-form-field>
<mat-checkbox formControlName="agreedToTerms" data-cy="agree-terms">
I agree to the <a routerLink="/terms">seller terms</a>
</mat-checkbox>
@if (errorMessage()) {
<mat-error>{{ errorMessage() }}</mat-error>
}
<button mat-raised-button color="primary" type="submit"
[disabled]="form.invalid || submitting()"
data-cy="register-seller-btn">
@if (submitting()) { Registering... } @else { Become a Seller }
</button>
</form>
}
</div>
`,
})
export class SellerRegistrationComponent {
private api = inject(AuthApiService);
private auth = inject(AuthService);
private notify = inject(ToastService);
registered = signal(false);
submitting = signal(false);
errorMessage = signal<string | null>(null);
form = inject(FormBuilder).nonNullable.group({
sellerName: ['', [Validators.required, Validators.minLength(2)]],
phone: [''],
agreedToTerms: [false, Validators.requiredTrue],
});
onSubmit(): void {
if (this.form.invalid) return;
this.submitting.set(true);
this.errorMessage.set(null);
this.api.registerAsSeller(this.form.getRawValue()).subscribe({
next: () => {
// Refresh auth session to pick up the new Seller role in the JWT
this.auth.refreshSession().subscribe(() => {
this.registered.set(true);
this.submitting.set(false);
});
},
error: (err: ApiError) => {
this.submitting.set(false);
this.errorMessage.set(
err.status === 409
? 'You are already registered as a seller.'
: 'Registration failed. Please try again.'
);
},
});
}
}
// โโ Seller verification status banner โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// seller-dashboard.component.html:
// @if (!auth.isVerifiedSeller()) {
// <div class="verification-banner" data-cy="verification-banner">
// <mat-icon>verified</mat-icon>
// <div>
// <strong>Get Verified</strong>
// <p>Verified sellers get a trust badge, unlimited listings, and higher placement.</p>
// </div>
// <button mat-stroked-button (click)="applyForVerification()">Apply Now</button>
// </div>
// } @else {
// <div class="verified-badge">
// <mat-icon color="primary">verified</mat-icon>
// Verified Seller
// </div>
// }
auth.refreshSession() to get a new JWT that includes the Seller role. Without this refresh, the user’s current JWT still has only the Buyer role and they cannot access seller features until their token expires (up to 15 minutes). The refresh session call exchanges the refresh token cookie for a new access token โ seamless to the user, immediately effective. Always refresh the session after role assignments that the user should feel immediately.agreedToTerms: [false, Validators.requiredTrue] pattern is the clean way to require a checkbox to be checked โ Validators.requiredTrue validates that the boolean value is exactly true (not just truthy). The submit button is disabled while form.invalid โ including while the checkbox is unchecked. This ensures sellers cannot accidentally register without agreeing to terms, and the validation state prevents the form from being submitted programmatically without the agreement.Full Registration-to-First-Listing Flow
| Step | User Action | System Response |
|---|---|---|
| 1 | Creates account (buyer) | JWT with Buyer role issued |
| 2 | Clicks “Become a Seller” | POST /api/auth/register-seller โ Seller role added |
| 3 | Session refreshed | New JWT with Seller role |
| 4 | Creates listing (draft) | POST /api/listings โ Status: PendingReview |
| 5 | Moderator approves | PATCH /api/admin/listings/{id}/moderate โ Status: Active |
| 6 | Email: “Your listing is live” | ListingPublishedEvent โ SendPublishedEmailHandler |
| 7 | Buyer contacts seller | POST /api/listings/{id}/contact โ 202 Accepted |
| 8 | Seller replies | PUT /api/contact-requests/{id}/reply โ 204 |
Common Mistakes
Mistake 1 โ Not refreshing the session after seller role assignment (new role not in JWT)
โ Wrong โ no refresh call; user stays on the success screen but cannot create listings; old JWT has Buyer role only.
โ
Correct โ auth.refreshSession() immediately after successful registration; new JWT has Seller role; features unlocked.
Mistake 2 โ Not recording terms agreement server-side (legal risk)
โ Wrong โ checkbox checked on the client only; no server record of when and which version of terms was agreed to.
โ Correct โ server records agreement: userId, termsVersion, agreedAt timestamp; legally defensible record.