Scaffold and Phase 1
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@storybid/shared",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "tsc --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export * from "./types/auction.js";
|
||||
export * from "./types/bidder.js";
|
||||
export * from "./types/bid.js";
|
||||
export * from "./types/event.js";
|
||||
export * from "./types/organization.js";
|
||||
export * from "./types/payment.js";
|
||||
export * from "./types/socket-events.js";
|
||||
export * from "./types/roles.js";
|
||||
@@ -0,0 +1,72 @@
|
||||
export type AuctionType = "live" | "silent";
|
||||
|
||||
export type AuctionStatus = "draft" | "active" | "paused" | "closed";
|
||||
|
||||
export interface Auction {
|
||||
id: string;
|
||||
eventId: string;
|
||||
type: AuctionType;
|
||||
name: string;
|
||||
status: AuctionStatus;
|
||||
sortOrder: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
// ── Auction Item ──────────────────────────────────────────────────────────────
|
||||
|
||||
export type ItemState =
|
||||
| "preview"
|
||||
| "active"
|
||||
| "going_once"
|
||||
| "going_twice"
|
||||
| "sold"
|
||||
| "passed"
|
||||
| "closed"; // silent auction final state
|
||||
|
||||
export interface AuctionItem {
|
||||
id: string;
|
||||
auctionId: string;
|
||||
lotNumber: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
donorName: string | null;
|
||||
category: string | null;
|
||||
fairMarketValue: number | null;
|
||||
openingBid: number;
|
||||
reservePrice: number | null;
|
||||
currentHighBid: number | null;
|
||||
currentHighBidderId: string | null;
|
||||
bidIncrement: number;
|
||||
state: ItemState;
|
||||
pickupNotes: string | null;
|
||||
sortOrder: number;
|
||||
// Silent-auction specific
|
||||
silentWindowId: string | null;
|
||||
softCloseEnabled: boolean;
|
||||
softCloseExtendMinutes: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ItemMedia {
|
||||
id: string;
|
||||
itemId: string;
|
||||
mediaType: "image" | "video" | "document" | "embed";
|
||||
url: string;
|
||||
thumbnailUrl: string | null;
|
||||
caption: string | null;
|
||||
sortOrder: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface SilentAuctionWindow {
|
||||
id: string;
|
||||
auctionId: string;
|
||||
name: string;
|
||||
opensAt: string;
|
||||
closesAt: string;
|
||||
softCloseEnabled: boolean;
|
||||
softCloseExtendMinutes: number;
|
||||
status: "pending" | "open" | "closed";
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
export type OriginMode =
|
||||
| "public" // bid arrived via public FQDN
|
||||
| "local_dns" // bid arrived via event-LAN hostname
|
||||
| "local_ip" // bid arrived via raw local IP
|
||||
| "offline_queue"; // bid was queued client-side and synced later
|
||||
|
||||
export type SyncStatus = "synced" | "pending" | "conflict" | "rejected";
|
||||
|
||||
export interface Bid {
|
||||
id: string;
|
||||
itemId: string;
|
||||
bidderId: string;
|
||||
amount: number;
|
||||
/** ISO-8601 timestamp from the client clock at intent time */
|
||||
clientCreatedAt: string;
|
||||
/** ISO-8601 timestamp when the server accepted the bid */
|
||||
serverReceivedAt: string;
|
||||
originMode: OriginMode;
|
||||
syncStatus: SyncStatus;
|
||||
deviceId: string;
|
||||
/** Client-side monotonic sequence within the device session */
|
||||
clientSeq: number;
|
||||
isWinning: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
// Outbox entry stored in IndexedDB before network sync
|
||||
export interface OutboxBid {
|
||||
localId: string; // UUID generated client-side
|
||||
itemId: string;
|
||||
bidderId: string;
|
||||
amount: number;
|
||||
clientCreatedAt: string;
|
||||
deviceId: string;
|
||||
clientSeq: number;
|
||||
attempts: number;
|
||||
lastAttemptAt: string | null;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export interface Bidder {
|
||||
id: string;
|
||||
organizationId: string;
|
||||
email: string | null;
|
||||
phone: string | null;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
paddleNumber: string | null;
|
||||
tableAssignment: string | null;
|
||||
notes: string | null;
|
||||
paymentMethodOnFile: boolean;
|
||||
checkInStatus: "pending" | "checked_in";
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface BidderAuthMethod {
|
||||
id: string;
|
||||
bidderId: string;
|
||||
type: "email_magic_link" | "sms_otp";
|
||||
identifier: string; // email address or E.164 phone number
|
||||
verifiedAt: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export type EventStatus = "draft" | "published" | "active" | "closed" | "archived";
|
||||
|
||||
export interface AuctionEvent {
|
||||
id: string;
|
||||
organizationId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
description: string | null;
|
||||
venueAddress: string | null;
|
||||
startAt: string;
|
||||
endAt: string;
|
||||
status: EventStatus;
|
||||
timezone: string;
|
||||
bannerImageUrl: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface Organization {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
logoUrl: string | null;
|
||||
primaryColor: string | null;
|
||||
stripeAccountId: string | null;
|
||||
publicUrl: string | null;
|
||||
localHostname: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
export type InvoiceStatus =
|
||||
| "draft"
|
||||
| "open"
|
||||
| "paid"
|
||||
| "partially_paid"
|
||||
| "void";
|
||||
|
||||
export interface Invoice {
|
||||
id: string;
|
||||
bidderId: string;
|
||||
eventId: string;
|
||||
stripeInvoiceId: string | null;
|
||||
totalAmount: number;
|
||||
paidAmount: number;
|
||||
status: InvoiceStatus;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface Payment {
|
||||
id: string;
|
||||
invoiceId: string;
|
||||
stripePaymentIntentId: string | null;
|
||||
amount: number;
|
||||
currency: string;
|
||||
status: "pending" | "succeeded" | "failed" | "refunded";
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface PaddleRaiseCampaign {
|
||||
id: string;
|
||||
eventId: string;
|
||||
name: string;
|
||||
goal: number | null;
|
||||
totalRaised: number;
|
||||
tiers: number[]; // suggested donation amounts
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface Donation {
|
||||
id: string;
|
||||
eventId: string;
|
||||
bidderId: string | null;
|
||||
campaignId: string | null;
|
||||
amount: number;
|
||||
anonymous: boolean;
|
||||
stripePaymentIntentId: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export type UserRole =
|
||||
| "admin"
|
||||
| "event_manager"
|
||||
| "auctioneer"
|
||||
| "spotter"
|
||||
| "checkin_staff"
|
||||
| "bidder";
|
||||
|
||||
export const STAFF_ROLES: UserRole[] = [
|
||||
"admin",
|
||||
"event_manager",
|
||||
"auctioneer",
|
||||
"spotter",
|
||||
"checkin_staff",
|
||||
];
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { ItemState, AuctionItem } from "./auction.js";
|
||||
import type { Bid } from "./bid.js";
|
||||
|
||||
// ── Events emitted by the SERVER ──────────────────────────────────────────────
|
||||
|
||||
export interface ServerToClientEvents {
|
||||
// Live auction
|
||||
item_activated: (payload: { item: AuctionItem }) => void;
|
||||
next_live_bid: (payload: { itemId: string; amount: number }) => void;
|
||||
live_bid_accepted: (payload: { bid: Bid; item: AuctionItem }) => void;
|
||||
item_state_changed: (payload: { itemId: string; state: ItemState }) => void;
|
||||
item_sold: (payload: { itemId: string; winnerId: string; amount: number }) => void;
|
||||
|
||||
// Silent auction
|
||||
silent_bid_accepted: (payload: { bid: Bid; item: AuctionItem }) => void;
|
||||
silent_outbid: (payload: { itemId: string; yourBidderId: string; newAmount: number }) => void;
|
||||
silent_window_closing: (payload: { windowId: string; closesAt: string }) => void;
|
||||
silent_window_extended: (payload: { windowId: string; newClosesAt: string }) => void;
|
||||
silent_item_closed: (payload: { itemId: string; winnerId: string | null; finalAmount: number | null }) => void;
|
||||
|
||||
// Paddle raise
|
||||
paddle_raise_update: (payload: { campaignId: string; totalRaised: number }) => void;
|
||||
|
||||
// Connectivity / sync
|
||||
sync_status_changed: (payload: { status: "connected" | "local" | "offline" }) => void;
|
||||
bid_sync_result: (payload: { localId: string; accepted: boolean; bid?: Bid; error?: string }) => void;
|
||||
}
|
||||
|
||||
// ── Events emitted by the CLIENT ──────────────────────────────────────────────
|
||||
|
||||
export interface ClientToServerEvents {
|
||||
// Bidder joins/leaves a room scoped to an event
|
||||
join_event: (eventId: string) => void;
|
||||
leave_event: (eventId: string) => void;
|
||||
|
||||
// Live bid (amount is the auctioneer-called amount shown in UI)
|
||||
place_live_bid: (payload: { itemId: string; amount: number; deviceId: string; clientSeq: number; clientCreatedAt: string }) => void;
|
||||
|
||||
// Silent bid
|
||||
place_silent_bid: (payload: { itemId: string; amount: number; deviceId: string; clientSeq: number; clientCreatedAt: string }) => void;
|
||||
|
||||
// Sync queued outbox bids after reconnect
|
||||
sync_outbox: (bids: Array<{ localId: string; itemId: string; amount: number; deviceId: string; clientSeq: number; clientCreatedAt: string }>) => void;
|
||||
|
||||
// Auctioneer controls
|
||||
auctioneer_activate_item: (itemId: string) => void;
|
||||
auctioneer_call_next_bid: (payload: { itemId: string; amount: number }) => void;
|
||||
auctioneer_accept_bid: (payload: { itemId: string; bidderId: string; amount: number }) => void;
|
||||
auctioneer_going_once: (itemId: string) => void;
|
||||
auctioneer_going_twice: (itemId: string) => void;
|
||||
auctioneer_sold: (itemId: string) => void;
|
||||
auctioneer_pass: (itemId: string) => void;
|
||||
}
|
||||
|
||||
// ── Shared inter-server events (for Redis adapter) ───────────────────────────
|
||||
|
||||
export interface InterServerEvents {
|
||||
ping: () => void;
|
||||
}
|
||||
|
||||
// ── Per-socket data ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface SocketData {
|
||||
bidderId?: string;
|
||||
staffId?: string;
|
||||
role?: string;
|
||||
deviceId?: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user