feat: add shipping line companies management

- Implement ShippingLineCompaniesService for registering and managing shipping line companies.
- Create ResendActivationAction component for resending activation links to shipping lines.
- Develop ShippingLineCompaniesPage for listing and registering shipping lines with validation.
- Introduce shippingLineCompanies.service for API interactions related to shipping lines.
- Define types for shipping line companies, including registration and pagination.
- Add placeholder pages for shipping line portal, including home, bookings, help, invoices, and settings.
This commit is contained in:
marshalyordanos
2026-08-13 08:54:20 +03:00
parent e37f1e0807
commit 9aae132dd4
42 changed files with 2463 additions and 117 deletions

View File

@@ -52,6 +52,11 @@ import {
} from "./entities/company-profile.entity";
import { ResponseExternalProfileDto } from "./dto/response-external-profile.dto";
import { CompanyInfoResponseDto } from "./dto/company-info-response.dto";
import {
AccountInfoResponse,
ShippingLineInfoResponseDto,
} from "./dto/account-info-response.dto";
import { ShippingLineCompaniesService } from "../shipping-lines/shipping-line-companies.service";
import { UpdateProfileDto } from "./dto/update-profile.dto";
import { ProfileResponseDto } from "./dto/profile-response.dto";
import { DashboardSummaryResponseDto } from "./dto/dashboard-summary-response.dto";
@@ -96,6 +101,7 @@ export class CompaniesController {
constructor(
private readonly companiesService: CompaniesService,
private readonly filesService: FilesService,
private readonly shippingLineCompaniesService: ShippingLineCompaniesService,
) { }
/**
@@ -119,16 +125,31 @@ export class CompaniesController {
@Get("getInfo")
@PortalCustomer()
@ApiOperation({ summary: "Get company info for the current user" })
@ApiOperation({
summary: "Get account info for the current user (customer or shipping line)",
})
async getInfo(
@CurrentUser() user: CurrentIamUser,
): Promise<CompanyInfoResponseDto> {
): Promise<AccountInfoResponse> {
// A shipping line has no company and no external profile, so the customer
// lookup below would 404. Checked first, and reported with an explicit
// `accountKind` so the portal can skip onboarding for shipping lines
// without inferring it from a missing company.
const shippingLine = await this.shippingLineCompaniesService.findByUserId(
user.id,
);
if (shippingLine) {
return new ShippingLineInfoResponseDto(shippingLine);
}
const { profile, company } =
await this.companiesService.getCompanyInfoByUserId(user.id);
const review = await this.companiesService.getOpenChangeRequestForCompany(
company.id,
);
return new CompanyInfoResponseDto(profile, company, review);
return Object.assign(new CompanyInfoResponseDto(profile, company, review), {
accountKind: "customer" as const,
});
}
@Get("profile")

View File

@@ -17,6 +17,7 @@ import { CompanyProfile } from "./entities/company-profile.entity";
import { CompanyChangeRequest } from "./entities/company-change-request.entity";
import { CompanyRevision } from "./entities/company-revision.entity";
import { Booking } from "../bookings/entities/booking.entity";
import { ShippingLineCompaniesModule } from "../shipping-lines/shipping-line-companies.module";
import { CompanyProfileRepository } from "./company-profile.repository";
import { CompanyChangeRequestRepository } from "./company-change-request.repository";
import { CompanyRevisionRepository } from "./company-revision.repository";
@@ -44,6 +45,9 @@ import { VerifaydaModule } from "../verifayda/verifayda.module";
forwardRef(() => NotificationInboxModule),
// Fayda identity verification for the company's owner and PoA.
VerifaydaModule,
// `GET /companies/getInfo` serves both portal audiences: it must recognise a
// shipping-line session, which has no company row to look up.
ShippingLineCompaniesModule,
],
controllers: [CompaniesController],
providers: [

View File

@@ -0,0 +1,66 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { ShippingLineCompany } from "../../shipping-lines/entities/shipping-line-company.entity";
import { CompanyInfoResponseDto } from "./company-info-response.dto";
/**
* What kind of account is signed in to the portal.
*
* The portal keys its onboarding gate off this rather than off "is `company`
* missing?": a failed or slow company fetch also leaves `company` empty, and
* treating that as "no onboarding needed" would let customers skip onboarding
* whenever the request failed. A shipping line is identified positively, and
* anything else defaults to `customer`.
*/
export type AccountKind = "customer" | "shipping_line";
/** The signed-in shipping line. No company, no profile, no onboarding. */
export class ShippingLineInfoResponseDto {
@ApiProperty({ enum: ["shipping_line"] })
accountKind: "shipping_line" = "shipping_line";
@ApiProperty()
id: string;
@ApiProperty()
name: string;
@ApiProperty()
email: string;
@ApiPropertyOptional()
phoneNumber?: string | null;
@ApiPropertyOptional()
scacCode?: string | null;
@ApiProperty()
status: string;
/**
* Always null. Present so the portal can read `company` / `profile` off either
* payload shape without narrowing the union first — the fields a customer
* session carries simply have no shipping-line equivalent.
*/
@ApiProperty({ nullable: true })
company: null = null;
@ApiProperty({ nullable: true })
profile: null = null;
@ApiProperty({ nullable: true })
review: null = null;
constructor(entity: ShippingLineCompany) {
this.id = entity.id;
this.name = entity.name;
this.email = entity.email;
this.phoneNumber = entity.phoneNumber ?? null;
this.scacCode = entity.scacCode ?? null;
this.status = entity.status;
}
}
export type AccountInfoResponse =
| (CompanyInfoResponseDto & { accountKind: "customer" })
| ShippingLineInfoResponseDto;