# Vendor Portal Implementation - Specification

**Date:** 2026-03-26  
**Status:** Approved  
**Author:** opencode

## 1. Overview

Implement a vendor-facing portal integrated in the main Next.js app at `/vendor-portal` route. Vendors can view invited tenders, submit bids, and track their submissions.

## 2. Architecture

- **Route Group:** `src/app/(vendor-portal)/` - separate layout from dashboard
- **Authentication:** Same NextAuth, VENDOR role login via `/login`
- **Access:** Invitation-only (vendors see only tenders they're invited to)

## 3. Data Model Changes

### 3.1 Tender Vendor Invitation

```prisma
model TenderVendor {
  id        String      @id @default(cuid())
  tenderId  String
  tender    TenderNotice @relation(fields: [tenderId], references: [id])
  vendorId  String
  vendor    Vendor      @relation(fields: [vendorId], references: [id])
  invitedAt DateTime    @default(now())

  @@unique([tenderId, vendorId])
}
```

## 4. Backend Implementation

### 4.1 Vendor Router Extensions

```typescript
// src/server/routers/vendor.router.ts
invite: adminProcedure
  .input(z.object({ tenderId: z.string(), vendorId: z.string() }))
  .mutation(async ({ ctx, input }) =>
    ctx.db.tenderVendor.create({ data: input })
  ),

getInvitedTenders: vendorProcedure.query(async ({ ctx }) => {
  const vendor = await ctx.db.vendor.findFirst({
    where: { email: ctx.session.user.email }
  });
  return ctx.db.tenderVendor.findMany({
    where: { vendorId: vendor.id },
    include: { tender: { include: { bundle: true } } }
  });
}),
```

### 4.2 New Vendor Procedure

```typescript
// src/server/trpc.ts
export const vendorProcedure = protectedProcedure.use(enforceRole(["VENDOR"]));
```

### 4.3 Bid Router Extensions

```typescript
// src/server/routers/bid.router.ts
getMyBids: vendorProcedure.query(async ({ ctx }) => {
  const vendor = await ctx.db.vendor.findFirst({
    where: { email: ctx.session.user.email }
  });
  return ctx.db.bidSubmission.findMany({
    where: { vendorId: vendor.id },
    include: { tender: true, bidLineItems: true }
  });
}),
```

## 5. Frontend Implementation

### 5.1 Vendor Portal Layout

```typescript
// src/app/(vendor-portal)/layout.tsx
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
import { TRPCProvider } from "@/trpc/provider";

export default async function VendorPortalLayout({ children }: { children: React.ReactNode }) {
  const session = await auth();
  if (!session || session.user.role !== "VENDOR") redirect("/login");

  return (
    <TRPCProvider>
      <div className="flex h-screen bg-background">
        {/* Vendor sidebar */}
        <main className="flex-1 overflow-y-auto p-6">{children}</main>
      </div>
    </TRPCProvider>
  );
}
```

### 5.2 Vendor Tenders Page

- List invited tenders with status badges
- Show submission deadline countdown
- Link to submit/view bid

### 5.3 Bid Submission Page

- Display BOQ items in table
- Input fields for quoted rates
- Calculate totals dynamically
- Submit button (disabled after deadline)

### 5.4 My Bids Page

- Table of submitted bids with status
- Link to view bid details

## 6. Admin Integration

- Add "Invite Vendors" dialog to tender detail page
- Multi-select vendor list
- Display invited vendor count on tender card

## 7. Acceptance Criteria

1. Vendor can log in and see only invited tenders
2. Vendor can submit bid before deadline
3. Vendor can edit bid until deadline
4. Vendor can view their bid history
5. Admin can invite vendors to tenders
6. Partial bids are allowed and flagged
