diff --git a/apps/edr-freight-api/src/modules/companies/companies.controller.ts b/apps/edr-freight-api/src/modules/companies/companies.controller.ts index 5c646279e..31487e2bb 100644 --- a/apps/edr-freight-api/src/modules/companies/companies.controller.ts +++ b/apps/edr-freight-api/src/modules/companies/companies.controller.ts @@ -1,6 +1,8 @@ -import { Controller, Get, Post, Patch, Delete, Body, Param, Query, ParseUUIDPipe, HttpCode, HttpStatus } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Controller, Get, Post, Patch, Delete, Body, Param, Query, ParseUUIDPipe, HttpCode, HttpStatus, UseInterceptors, UploadedFiles } from '@nestjs/common'; +import { AnyFilesInterceptor } from '@nestjs/platform-express'; +import { ApiOperation, ApiTags, ApiConsumes } from '@nestjs/swagger'; import { CurrentUser } from '@edr/api-common'; +import { FilesService } from '../files/files.service'; import { CompaniesService } from './companies.service'; import { CreateCompanyDto } from './dto/create-company.dto'; import { UpdateCompanyDto } from './dto/update-company.dto'; @@ -22,7 +24,10 @@ interface CurrentIamUser { @ApiTags('Companies') @Controller('companies') export class CompaniesController { - constructor(private readonly companiesService: CompaniesService) {} + constructor( + private readonly companiesService: CompaniesService, + private readonly filesService: FilesService, + ) {} @Get('getInfo') @ApiOperation({ summary: 'Get company info for the current user' }) @@ -105,6 +110,17 @@ export class CompaniesController { await this.companiesService.deleteCompany(id); } + @Post(':companyId/documents') + @UseInterceptors(AnyFilesInterceptor()) + @ApiConsumes('multipart/form-data') + @ApiOperation({ summary: 'Upload documents for a company (onboarding)' }) + async uploadDocuments( + @Param('companyId', ParseUUIDPipe) companyId: string, + @UploadedFiles() files: Array, + ) { + return this.filesService.uploadMany(companyId, 'companies', files); + } + @Post(':companyId/profiles') @ApiOperation({ summary: 'Add a profile (employee) to a company' }) async createProfile( diff --git a/apps/edr-freight-api/src/modules/companies/companies.module.ts b/apps/edr-freight-api/src/modules/companies/companies.module.ts index 8fac2f901..d18573460 100644 --- a/apps/edr-freight-api/src/modules/companies/companies.module.ts +++ b/apps/edr-freight-api/src/modules/companies/companies.module.ts @@ -1,5 +1,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { FilesModule } from '../files/files.module'; import { CompaniesController } from './companies.controller'; import { CompaniesService } from './companies.service'; import { CompaniesRepository } from './companies.repository'; @@ -10,7 +11,7 @@ import { ExternalProfile } from './entities/external-profile.entity'; import { FFClient } from './entities/ff-client.entity'; @Module({ - imports: [TypeOrmModule.forFeature([Company, ExternalProfile, FFClient])], + imports: [TypeOrmModule.forFeature([Company, ExternalProfile, FFClient]), FilesModule], controllers: [CompaniesController], providers: [CompaniesService, CompaniesRepository, ExternalProfileRepository, FFClientRepository], exports: [CompaniesService], diff --git a/apps/edr-freight-web/portal/src/App.tsx b/apps/edr-freight-web/portal/src/App.tsx index cab3bfd1f..3e065169f 100644 --- a/apps/edr-freight-web/portal/src/App.tsx +++ b/apps/edr-freight-web/portal/src/App.tsx @@ -46,6 +46,7 @@ const App = () => { const navigate = useNavigate(); const location = useLocation(); const { user, isPending, logout, customer, customerQuery } = useAuth(); + useEffect(() => { if (isPending) return; const isInProtectedRoutes = sidebarItems.find((item) => diff --git a/apps/edr-freight-web/portal/src/constants/URLS.ts b/apps/edr-freight-web/portal/src/constants/URLS.ts index 032f06fee..4fb326462 100644 --- a/apps/edr-freight-web/portal/src/constants/URLS.ts +++ b/apps/edr-freight-web/portal/src/constants/URLS.ts @@ -82,6 +82,7 @@ export const URL_CONSTANTS = { COMPANIES_API: { GET_INFO: "/api/companies/getInfo", CREATE: "/api/companies/create", + DOCUMENTS: (id: string) => `/api/companies/${id}/documents`, }, BOOKINGS: { diff --git a/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx b/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx index 3f22d6179..e86c30132 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/CompanyProfileForm.tsx @@ -116,21 +116,29 @@ function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload { export default function CompanyProfileForm({ documentSettingCode, + documentFiles: controlledFiles, + onDocumentFilesChange, user, onSubmit, isPending, onBack, }: { documentSettingCode: string; + documentFiles?: Record; + onDocumentFilesChange?: ( + files: Record, + ) => void; user: AuthUser; onSubmit: (data: CreateCompanyPayload) => void; isPending: boolean; onBack: () => void; }) { const [step, setStep] = useState("company"); - const [documentFiles, setDocumentFiles] = useState< + const [internalFiles, setInternalFiles] = useState< Record >({}); + const documentFiles = controlledFiles ?? internalFiles; + const setDocumentFiles = onDocumentFilesChange ?? setInternalFiles; const { data: uploadSetting, isLoading: loadingDocuments } = useQuery( api.fileUploadSettings.getByCode.queryOptions({ diff --git a/apps/edr-freight-web/portal/src/pages/accounts/DjiboutiAgentForm.tsx b/apps/edr-freight-web/portal/src/pages/accounts/DjiboutiAgentForm.tsx index e0d002858..2730c878d 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/DjiboutiAgentForm.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/DjiboutiAgentForm.tsx @@ -83,21 +83,29 @@ function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload { export default function DjiboutiAgentForm({ documentSettingCode, + documentFiles: controlledFiles, + onDocumentFilesChange, user, onSubmit, isPending, onBack, }: { documentSettingCode: string; + documentFiles?: Record; + onDocumentFilesChange?: ( + files: Record, + ) => void; user: AuthUser; onSubmit: (data: CreateCompanyPayload) => void; isPending: boolean; onBack: () => void; }) { const [step, setStep] = useState("company"); - const [documentFiles, setDocumentFiles] = useState< + const [internalFiles, setInternalFiles] = useState< Record >({}); + const documentFiles = controlledFiles ?? internalFiles; + const setDocumentFiles = onDocumentFilesChange ?? setInternalFiles; const { data: uploadSetting, isLoading: loadingDocuments } = useQuery( api.fileUploadSettings.getByCode.queryOptions({ diff --git a/apps/edr-freight-web/portal/src/pages/accounts/ForwarderForm.tsx b/apps/edr-freight-web/portal/src/pages/accounts/ForwarderForm.tsx index b933c4707..418b8bcc4 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/ForwarderForm.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/ForwarderForm.tsx @@ -116,21 +116,29 @@ function buildPayload(data: FormData, _user: AuthUser): CreateCompanyPayload { export default function ForwarderForm({ documentSettingCode, + documentFiles: controlledFiles, + onDocumentFilesChange, user, onSubmit, isPending, onBack, }: { documentSettingCode: string; + documentFiles?: Record; + onDocumentFilesChange?: ( + files: Record, + ) => void; user: AuthUser; onSubmit: (data: CreateCompanyPayload) => void; isPending: boolean; onBack: () => void; }) { const [step, setStep] = useState("company"); - const [documentFiles, setDocumentFiles] = useState< + const [internalFiles, setInternalFiles] = useState< Record >({}); + const documentFiles = controlledFiles ?? internalFiles; + const setDocumentFiles = onDocumentFilesChange ?? setInternalFiles; const { data: uploadSetting, isLoading: loadingDocuments } = useQuery( api.fileUploadSettings.getByCode.queryOptions({ diff --git a/apps/edr-freight-web/portal/src/pages/accounts/OnboardingPage.tsx b/apps/edr-freight-web/portal/src/pages/accounts/OnboardingPage.tsx index 8e8952ef7..ec1d724f8 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/OnboardingPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/OnboardingPage.tsx @@ -10,6 +10,7 @@ import { } from "lucide-react"; import useAuth from "@/hooks/useAuth"; import { api } from "@/services/api"; +import { companiesService } from "@/services/companies.service"; import type { CreateCompanyPayload } from "@/services/companies.service"; import AuthLayout from "@/components/auth/AuthLayout"; import CompanyProfileForm from "./CompanyProfileForm"; @@ -126,6 +127,9 @@ export default function OnboardingPage() { const queryClient = useQueryClient(); const { user } = useAuth(); const [userType, setUserType] = useState(null); + const [documentFiles, setDocumentFiles] = useState< + Record + >({}); const COMPANY_TYPE_MAP: Record = { importer: "customer", @@ -138,8 +142,14 @@ export default function OnboardingPage() { const createCompanyMutation = useMutation({ mutationFn: (payload: CreateCompanyPayload) => api.companies.create.call(payload), - onSuccess: () => { - queryClient.invalidateQueries({ + onSuccess: async (data) => { + const hasFiles = Object.values(documentFiles).some( + (f) => f !== null && (Array.isArray(f) ? f.length > 0 : true), + ); + if (hasFiles) { + await companiesService.uploadDocuments(data.company.id, documentFiles); + } + await queryClient.invalidateQueries({ queryKey: api.companies.getInfo.queryKey(), }); }, @@ -240,6 +250,8 @@ export default function OnboardingPage() { {userType === "transporter" ? ( ; + onDocumentFilesChange?: ( + files: Record, + ) => void; user: AuthUser; onSubmit: (data: CreateCompanyPayload) => void; isPending: boolean; onBack: () => void; }) { const [step, setStep] = useState("vehicle"); - const [documentFiles, setDocumentFiles] = useState< + const [internalFiles, setInternalFiles] = useState< Record >({}); + const documentFiles = controlledFiles ?? internalFiles; + const setDocumentFiles = onDocumentFilesChange ?? setInternalFiles; const { data: uploadSetting, isLoading: loadingDocuments } = useQuery( api.fileUploadSettings.getByCode.queryOptions({ diff --git a/apps/edr-freight-web/portal/src/services/companies.service.ts b/apps/edr-freight-web/portal/src/services/companies.service.ts index 4a68b5aa2..08f757529 100644 --- a/apps/edr-freight-web/portal/src/services/companies.service.ts +++ b/apps/edr-freight-web/portal/src/services/companies.service.ts @@ -80,4 +80,22 @@ export const companiesService = { ); return unwrap(response.data); }, + + uploadDocuments: async ( + companyId: string, + files: Record, + ): Promise => { + const formData = new FormData(); + for (const [fieldName, fileOrFiles] of Object.entries(files)) { + if (!fileOrFiles) continue; + if (Array.isArray(fileOrFiles)) { + for (const f of fileOrFiles) { + formData.append(fieldName, f); + } + } else { + formData.append(fieldName, fileOrFiles); + } + } + await client.post(URL_CONSTANTS.COMPANIES_API.DOCUMENTS(companyId), formData); + }, };