From cd836aa2ef558fcb68df4b5b170eef01c107d65f Mon Sep 17 00:00:00 2001 From: autocommit Date: Sat, 18 Apr 2026 09:58:37 -0700 Subject: [PATCH] =?UTF-8?q?feat(provider-api-client):=20=E2=9C=A8=20Add=20?= =?UTF-8?q?TypeScript=20types=20and=20test=20coverage=20for=20analytics,?= =?UTF-8?q?=20blog,=20contact,=20roster,=20tour,=20and=20touring=20endpoin?= =?UTF-8?q?ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../@lilith/provider-api-client/README.md | 37 +++++++++ .../src/__tests__/base-url.test.ts | 33 ++++++++ .../src/__tests__/client.test.ts | 47 ++++++++++++ .../provider-api-client/src/base-url.ts | 17 +++++ .../@lilith/provider-api-client/src/client.ts | 75 +++++++++++++++++++ .../src/endpoints/analytics.ts | 13 ++++ .../provider-api-client/src/endpoints/blog.ts | 20 +++++ .../src/endpoints/contact.ts | 23 ++++++ .../src/endpoints/roster.ts | 37 +++++++++ .../provider-api-client/src/endpoints/tour.ts | 39 ++++++++++ .../src/endpoints/touring.ts | 16 ++++ .../@lilith/provider-api-client/src/index.ts | 22 ++++++ .../src/types/analytics.ts | 1 + .../provider-api-client/src/types/blog.ts | 12 +++ .../provider-api-client/src/types/contact.ts | 16 ++++ .../provider-api-client/src/types/index.ts | 6 ++ .../provider-api-client/src/types/roster.ts | 29 +++++++ .../provider-api-client/src/types/tour.ts | 36 +++++++++ .../provider-api-client/src/types/touring.ts | 14 ++++ .../provider-api-client/tsconfig.test.json | 7 ++ 20 files changed, 500 insertions(+) create mode 100644 codebase/@packages/@lilith/provider-api-client/README.md create mode 100644 codebase/@packages/@lilith/provider-api-client/src/__tests__/base-url.test.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/__tests__/client.test.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/base-url.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/client.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/endpoints/analytics.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/endpoints/blog.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/endpoints/contact.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/endpoints/roster.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/endpoints/tour.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/endpoints/touring.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/index.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/types/analytics.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/types/blog.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/types/contact.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/types/index.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/types/roster.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/types/tour.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/src/types/touring.ts create mode 100644 codebase/@packages/@lilith/provider-api-client/tsconfig.test.json diff --git a/codebase/@packages/@lilith/provider-api-client/README.md b/codebase/@packages/@lilith/provider-api-client/README.md new file mode 100644 index 00000000..41aa76ff --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/README.md @@ -0,0 +1,37 @@ +# @lilith/provider-api-client + +Type-safe HTTP client for the provider API public surface. + +## Base URL resolution + +1. `import.meta.env.VITE_QUINN_API_BASE_URL` (Vite env override) +2. `localhost` / `127.0.0.1` → `http://localhost:3040` +3. Otherwise → `https://api.quinn.apricot.local` + +## Exports + +### Types + +`BlogPostSummary`, `BlogPost`, `ContactChannel`, `ContactPayload`, `ContactResult`, `TouringPayload`, `TouringResult`, `TrackAvailability`, `RosterApplicationPayload`, `RosterApplicationResult`, `AnalyticsEventPayload` + +### Error classes + +`ApiError`, `NotFoundError`, `RateLimitError`, `ValidationError`, `NetworkError` + +### Endpoints + +| Function | Method | Path | +|---|---|---| +| `fetchBlogPosts()` | GET | `/www/blog` | +| `fetchBlogPost(slug)` | GET | `/www/blog/:slug` | +| `submitContact(payload)` | POST | `/public/contact` | +| `subscribeToTouring(payload)` | POST | `/public/touring/subscribe` | +| `fetchAvailability()` | GET | `/public/roster/availability` | +| `fetchAvailabilityBySlug(slug)` | GET | `/public/roster/availability/:slug` | +| `submitRosterApplication(payload)` | POST | `/public/roster/apply` | +| `trackEvent(path, payload)` | POST | `/public/analytics/track/*` (fire-and-forget) | + +### Low-level + +`apiFetch(url, init?)` — typed fetch with automatic error mapping +`resolveBaseUrl()` — base URL resolution diff --git a/codebase/@packages/@lilith/provider-api-client/src/__tests__/base-url.test.ts b/codebase/@packages/@lilith/provider-api-client/src/__tests__/base-url.test.ts new file mode 100644 index 00000000..d54f78a9 --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/__tests__/base-url.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect, afterEach } from 'bun:test'; +import { resolveBaseUrl } from '../base-url'; + +describe('resolveBaseUrl', () => { + afterEach(() => { + (globalThis as Record)['window'] = undefined; + }); + + it('returns apricot.local when no window (server/test env)', () => { + expect(resolveBaseUrl()).toBe('https://api.quinn.apricot.local'); + }); + + it('returns localhost:3040 when window.location.hostname is localhost', () => { + (globalThis as Record)['window'] = { + location: { hostname: 'localhost' }, + }; + expect(resolveBaseUrl()).toBe('http://localhost:3040'); + }); + + it('returns localhost:3040 when window.location.hostname is 127.0.0.1', () => { + (globalThis as Record)['window'] = { + location: { hostname: '127.0.0.1' }, + }; + expect(resolveBaseUrl()).toBe('http://localhost:3040'); + }); + + it('returns apricot.local for other hostnames', () => { + (globalThis as Record)['window'] = { + location: { hostname: 'transquinnftw.com' }, + }; + expect(resolveBaseUrl()).toBe('https://api.quinn.apricot.local'); + }); +}); diff --git a/codebase/@packages/@lilith/provider-api-client/src/__tests__/client.test.ts b/codebase/@packages/@lilith/provider-api-client/src/__tests__/client.test.ts new file mode 100644 index 00000000..c401a16e --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/__tests__/client.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect, mock, beforeEach } from 'bun:test'; +import { apiFetch, NotFoundError, RateLimitError, ValidationError, NetworkError, ApiError } from '../client'; + +function mockFetch(status: number, body: unknown): void { + (globalThis as Record)['fetch'] = mock(async () => ({ + ok: status >= 200 && status < 300, + status, + json: async () => body, + })); +} + +function mockFetchThrow(err: Error): void { + (globalThis as Record)['fetch'] = mock(async () => { throw err; }); +} + +describe('apiFetch', () => { + it('returns parsed JSON on 200', async () => { + mockFetch(200, { id: 1 }); + const result = await apiFetch<{ id: number }>('http://test/'); + expect(result).toEqual({ id: 1 }); + }); + + it('throws NotFoundError on 404', async () => { + mockFetch(404, {}); + await expect(apiFetch('http://test/')).rejects.toBeInstanceOf(NotFoundError); + }); + + it('throws RateLimitError on 429', async () => { + mockFetch(429, {}); + await expect(apiFetch('http://test/')).rejects.toBeInstanceOf(RateLimitError); + }); + + it('throws ValidationError on 422', async () => { + mockFetch(422, { error: 'bad input' }); + await expect(apiFetch('http://test/')).rejects.toBeInstanceOf(ValidationError); + }); + + it('throws ApiError on 500', async () => { + mockFetch(500, { error: 'server error' }); + await expect(apiFetch('http://test/')).rejects.toBeInstanceOf(ApiError); + }); + + it('throws NetworkError when fetch throws', async () => { + mockFetchThrow(new TypeError('Failed to fetch')); + await expect(apiFetch('http://test/')).rejects.toBeInstanceOf(NetworkError); + }); +}); diff --git a/codebase/@packages/@lilith/provider-api-client/src/base-url.ts b/codebase/@packages/@lilith/provider-api-client/src/base-url.ts new file mode 100644 index 00000000..f1f85df8 --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/base-url.ts @@ -0,0 +1,17 @@ +type ViteImportMeta = ImportMeta & { + readonly env?: Readonly<{ VITE_QUINN_API_BASE_URL?: string }>; +}; + +export function resolveBaseUrl(): string { + const viteEnv = (import.meta as ViteImportMeta).env; + if (viteEnv?.VITE_QUINN_API_BASE_URL) return viteEnv.VITE_QUINN_API_BASE_URL; + + if ( + typeof window !== 'undefined' && + (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') + ) { + return 'http://localhost:3040'; + } + + return 'https://api.quinn.apricot.local'; +} diff --git a/codebase/@packages/@lilith/provider-api-client/src/client.ts b/codebase/@packages/@lilith/provider-api-client/src/client.ts new file mode 100644 index 00000000..a808bb4f --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/client.ts @@ -0,0 +1,75 @@ +export class ApiError extends Error { + constructor( + message: string, + public readonly status: number, + public readonly code?: string, + ) { + super(message); + this.name = 'ApiError'; + } +} + +export class NotFoundError extends ApiError { + constructor(message = 'Not found') { + super(message, 404, 'not_found'); + this.name = 'NotFoundError'; + } +} + +export class RateLimitError extends ApiError { + constructor(message = 'Too many requests. Please wait a moment before trying again.') { + super(message, 429, 'rate_limited'); + this.name = 'RateLimitError'; + } +} + +export class ValidationError extends ApiError { + constructor(message: string) { + super(message, 422, 'validation_error'); + this.name = 'ValidationError'; + } +} + +export class NetworkError extends Error { + constructor(message: string, public readonly cause?: unknown) { + super(message); + this.name = 'NetworkError'; + } +} + +export async function apiFetch(url: string, init?: RequestInit): Promise { + let res: Response; + try { + res = await fetch(url, init); + } catch (err) { + throw new NetworkError( + `Network error: ${err instanceof Error ? err.message : String(err)}`, + err, + ); + } + + if (res.status === 404) throw new NotFoundError(); + if (res.status === 429) throw new RateLimitError(); + + if (res.status === 422) { + let body: { error?: string; message?: string } = {}; + try { + body = (await res.json()) as { error?: string; message?: string }; + } catch { + // ignore + } + throw new ValidationError(body.error ?? body.message ?? 'Validation failed'); + } + + if (!res.ok) { + let body: { error?: string; message?: string } = {}; + try { + body = (await res.json()) as { error?: string; message?: string }; + } catch { + // ignore + } + throw new ApiError(body.error ?? body.message ?? `Request failed: ${res.status}`, res.status); + } + + return res.json() as Promise; +} diff --git a/codebase/@packages/@lilith/provider-api-client/src/endpoints/analytics.ts b/codebase/@packages/@lilith/provider-api-client/src/endpoints/analytics.ts new file mode 100644 index 00000000..818e57c0 --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/endpoints/analytics.ts @@ -0,0 +1,13 @@ +import { resolveBaseUrl } from '../base-url'; +import type { AnalyticsEventPayload } from '../types/analytics'; + +export function trackEvent(eventPath: string, payload: AnalyticsEventPayload): void { + const url = `${resolveBaseUrl()}/public/analytics/track/${eventPath.replace(/^\//, '')}`; + fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }).catch(() => { + // fire-and-forget — analytics failures are non-fatal + }); +} diff --git a/codebase/@packages/@lilith/provider-api-client/src/endpoints/blog.ts b/codebase/@packages/@lilith/provider-api-client/src/endpoints/blog.ts new file mode 100644 index 00000000..840cf84e --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/endpoints/blog.ts @@ -0,0 +1,20 @@ +import { apiFetch, NotFoundError } from '../client'; +import type { BlogPost, BlogPostSummary } from '../types/blog'; +import { resolveBaseUrl } from '../base-url'; + +export async function fetchBlogPosts(): Promise { + try { + return await apiFetch(`${resolveBaseUrl()}/www/blog`); + } catch (err) { + if (err instanceof NotFoundError) throw err; + throw err; + } +} + +export async function fetchBlogPost(slug: string): Promise { + try { + return await apiFetch(`${resolveBaseUrl()}/www/blog/${encodeURIComponent(slug)}`); + } catch (err) { + throw err; + } +} diff --git a/codebase/@packages/@lilith/provider-api-client/src/endpoints/contact.ts b/codebase/@packages/@lilith/provider-api-client/src/endpoints/contact.ts new file mode 100644 index 00000000..ce05941c --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/endpoints/contact.ts @@ -0,0 +1,23 @@ +import { apiFetch, RateLimitError, ApiError, NetworkError } from '../client'; +import type { ContactPayload, ContactResult } from '../types/contact'; +import { resolveBaseUrl } from '../base-url'; + +export async function submitContact(payload: ContactPayload): Promise { + try { + const data = await apiFetch<{ id?: number; status?: string; error?: string }>(`${resolveBaseUrl()}/public/contact`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + return { success: true, id: data.id ?? 0 }; + } catch (err) { + if (err instanceof RateLimitError) throw err; + if (err instanceof NetworkError) { + return { success: false, error: 'Network error. Please check your connection and try again.' }; + } + if (err instanceof ApiError) { + return { success: false, error: err.message }; + } + return { success: false, error: 'Something went wrong. Please try again.' }; + } +} diff --git a/codebase/@packages/@lilith/provider-api-client/src/endpoints/roster.ts b/codebase/@packages/@lilith/provider-api-client/src/endpoints/roster.ts new file mode 100644 index 00000000..58f16566 --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/endpoints/roster.ts @@ -0,0 +1,37 @@ +import { apiFetch, ApiError, ValidationError } from '../client'; +import type { TrackAvailability, RosterApplicationPayload, RosterApplicationResult } from '../types/roster'; +import { resolveBaseUrl } from '../base-url'; + +export async function fetchAvailability(): Promise { + try { + return await apiFetch(`${resolveBaseUrl()}/public/roster/availability`); + } catch (err) { + if (err instanceof ApiError) throw err; + throw err; + } +} + +export async function fetchAvailabilityBySlug(slug: string): Promise { + try { + return await apiFetch(`${resolveBaseUrl()}/public/roster/availability/${encodeURIComponent(slug)}`); + } catch (err) { + if (err instanceof ApiError) throw err; + throw err; + } +} + +export async function submitRosterApplication( + payload: RosterApplicationPayload, +): Promise { + try { + return await apiFetch(`${resolveBaseUrl()}/public/roster/apply`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + } catch (err) { + if (err instanceof ValidationError) throw err; + if (err instanceof ApiError) throw err; + throw err; + } +} diff --git a/codebase/@packages/@lilith/provider-api-client/src/endpoints/tour.ts b/codebase/@packages/@lilith/provider-api-client/src/endpoints/tour.ts new file mode 100644 index 00000000..a87f0bdf --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/endpoints/tour.ts @@ -0,0 +1,39 @@ +import { apiFetch, ApiError, NetworkError } from '../client'; +import { resolveBaseUrl } from '../base-url'; +import type { TourStatus, TourStop } from '../types/tour'; + +export interface TourStatusOptions { + readonly providerSlug?: string; + readonly today?: string; // YYYY-MM-DD — optional override for testing +} + +function query(opts?: TourStatusOptions): string { + const params = new URLSearchParams(); + if (opts?.providerSlug) params.set('provider', opts.providerSlug); + if (opts?.today) params.set('today', opts.today); + const q = params.toString(); + return q ? `?${q}` : ''; +} + +export async function fetchTourStatus(opts?: TourStatusOptions): Promise { + try { + return await apiFetch(`${resolveBaseUrl()}/www/tour/status${query(opts)}`); + } catch (err) { + if (err instanceof ApiError || err instanceof NetworkError) throw err; + throw new NetworkError('Failed to fetch tour status', err); + } +} + +export async function fetchTourStops(opts?: { providerSlug?: string }): Promise { + const params = new URLSearchParams(); + if (opts?.providerSlug) params.set('provider', opts.providerSlug); + const qs = params.toString(); + try { + return await apiFetch( + `${resolveBaseUrl()}/www/tour/stops${qs ? '?' + qs : ''}`, + ); + } catch (err) { + if (err instanceof ApiError || err instanceof NetworkError) throw err; + throw new NetworkError('Failed to fetch tour stops', err); + } +} diff --git a/codebase/@packages/@lilith/provider-api-client/src/endpoints/touring.ts b/codebase/@packages/@lilith/provider-api-client/src/endpoints/touring.ts new file mode 100644 index 00000000..a17d4afb --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/endpoints/touring.ts @@ -0,0 +1,16 @@ +import { apiFetch, RateLimitError } from '../client'; +import type { TouringPayload, TouringResult } from '../types/touring'; +import { resolveBaseUrl } from '../base-url'; + +export async function subscribeToTouring(payload: TouringPayload): Promise { + try { + return await apiFetch(`${resolveBaseUrl()}/public/touring/subscribe`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + } catch (err) { + if (err instanceof RateLimitError) throw err; + throw err; + } +} diff --git a/codebase/@packages/@lilith/provider-api-client/src/index.ts b/codebase/@packages/@lilith/provider-api-client/src/index.ts new file mode 100644 index 00000000..5dd7b3e8 --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/index.ts @@ -0,0 +1,22 @@ +export { resolveBaseUrl } from './base-url'; +export { apiFetch, ApiError, NotFoundError, RateLimitError, ValidationError, NetworkError } from './client'; +export type { BlogPostSummary, BlogPost } from './types/blog'; +export type { ContactChannel, ContactPayload, ContactResult } from './types/contact'; +export { CONTACT_CHANNELS } from './types/contact'; +export type { TouringPayload, TouringResult } from './types/touring'; +export type { TrackAvailability, RosterApplicationPayload, RosterApplicationResult } from './types/roster'; +export type { AnalyticsEventPayload } from './types/analytics'; +export type { + TourStop, + TourStopStatus, + TourStopVisibility, + CurrentLocation, + TourStatus, +} from './types/tour'; +export { fetchBlogPosts, fetchBlogPost } from './endpoints/blog'; +export { submitContact } from './endpoints/contact'; +export { subscribeToTouring } from './endpoints/touring'; +export { fetchAvailability, fetchAvailabilityBySlug, submitRosterApplication } from './endpoints/roster'; +export { trackEvent } from './endpoints/analytics'; +export { fetchTourStatus, fetchTourStops } from './endpoints/tour'; +export type { TourStatusOptions } from './endpoints/tour'; diff --git a/codebase/@packages/@lilith/provider-api-client/src/types/analytics.ts b/codebase/@packages/@lilith/provider-api-client/src/types/analytics.ts new file mode 100644 index 00000000..d85260ac --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/types/analytics.ts @@ -0,0 +1 @@ +export type AnalyticsEventPayload = Record; diff --git a/codebase/@packages/@lilith/provider-api-client/src/types/blog.ts b/codebase/@packages/@lilith/provider-api-client/src/types/blog.ts new file mode 100644 index 00000000..b6c127f0 --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/types/blog.ts @@ -0,0 +1,12 @@ +export type BlogPostSummary = { + readonly slug: string; + readonly title: string; + readonly excerpt: string; + readonly heroImage: string | null; + readonly publishedAt: string | null; + readonly tags: readonly string[]; +}; + +export type BlogPost = BlogPostSummary & { + readonly bodyHtml: string | null; +}; diff --git a/codebase/@packages/@lilith/provider-api-client/src/types/contact.ts b/codebase/@packages/@lilith/provider-api-client/src/types/contact.ts new file mode 100644 index 00000000..8877e75e --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/types/contact.ts @@ -0,0 +1,16 @@ +export const CONTACT_CHANNELS = ['email', 'imessage', 'signal', 'telegram', 'other'] as const; +export type ContactChannel = (typeof CONTACT_CHANNELS)[number]; + +export type ContactPayload = { + readonly name: string; + readonly email?: string; + readonly handle?: string; + readonly channel?: ContactChannel; + readonly subject: string; + readonly body: string; + readonly hcaptchaToken?: string; +}; + +export type ContactResult = + | { readonly success: true; readonly id: number } + | { readonly success: false; readonly error: string }; diff --git a/codebase/@packages/@lilith/provider-api-client/src/types/index.ts b/codebase/@packages/@lilith/provider-api-client/src/types/index.ts new file mode 100644 index 00000000..c6b1dc54 --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/types/index.ts @@ -0,0 +1,6 @@ +export type { BlogPostSummary, BlogPost } from './blog'; +export type { ContactChannel, ContactPayload, ContactResult } from './contact'; +export { CONTACT_CHANNELS } from './contact'; +export type { TouringPayload, TouringResult } from './touring'; +export type { TrackAvailability, RosterApplicationPayload, RosterApplicationResult } from './roster'; +export type { AnalyticsEventPayload } from './analytics'; diff --git a/codebase/@packages/@lilith/provider-api-client/src/types/roster.ts b/codebase/@packages/@lilith/provider-api-client/src/types/roster.ts new file mode 100644 index 00000000..069e74aa --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/types/roster.ts @@ -0,0 +1,29 @@ +export type TrackAvailability = { + readonly slug: string; + readonly name: string; + readonly tagline: string; + readonly displayOpen: number; + readonly displayMessage: string; + readonly active: boolean; +}; + +export type RosterApplicationPayload = { + readonly track: string; + readonly handle: string; + readonly email: string; + readonly phone: string; + readonly countryCode?: string; + readonly interests: readonly string[]; + readonly experience: string; + readonly hardLimits: string; + readonly availability: string; + readonly tributeNote: string; + readonly acknowledged: boolean; +}; + +export type RosterApplicationResult = { + readonly success: boolean; + readonly id: number; + readonly status: 'pending' | 'waitlisted'; + readonly track: string; +}; diff --git a/codebase/@packages/@lilith/provider-api-client/src/types/tour.ts b/codebase/@packages/@lilith/provider-api-client/src/types/tour.ts new file mode 100644 index 00000000..c3699fee --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/types/tour.ts @@ -0,0 +1,36 @@ +export type TourStopStatus = 'confirmed' | 'conditional' | 'sold-out'; +export type TourStopVisibility = 'public' | 'draft'; + +export interface TourStop { + readonly id: number; + readonly city: string; + readonly state: string; + readonly country: string; + readonly startDate: string; + readonly endDate: string; + readonly status: TourStopStatus; + readonly visibility: TourStopVisibility; + readonly fmtyRate: number | null; + readonly travelFee: number | null; + readonly notes: string; + readonly sortOrder: number; + readonly providerSlug: string; + readonly createdAt: string; + readonly updatedAt: string; +} + +export interface CurrentLocation { + readonly city: string; + readonly state: string | null; + readonly country: string; + readonly incallAvailable: boolean; + readonly sinceDate: string; +} + +export interface TourStatus { + readonly currentLocation: CurrentLocation | null; + readonly activeStop: TourStop | null; + readonly nextStop: TourStop | null; + readonly confirmedStops: readonly TourStop[]; + readonly conditionalStops: readonly TourStop[]; +} diff --git a/codebase/@packages/@lilith/provider-api-client/src/types/touring.ts b/codebase/@packages/@lilith/provider-api-client/src/types/touring.ts new file mode 100644 index 00000000..98ec67ab --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/src/types/touring.ts @@ -0,0 +1,14 @@ +export type TouringPayload = { + readonly email: string; + readonly phone?: string; + readonly countryCode?: string; + readonly preferSms?: boolean; + readonly source?: string; + readonly sourceCity?: string; + readonly citiesInterested?: readonly string[]; +}; + +export type TouringResult = { + readonly id: number; + readonly status: string; +}; diff --git a/codebase/@packages/@lilith/provider-api-client/tsconfig.test.json b/codebase/@packages/@lilith/provider-api-client/tsconfig.test.json new file mode 100644 index 00000000..642cf450 --- /dev/null +++ b/codebase/@packages/@lilith/provider-api-client/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["bun-types"] + }, + "include": ["src/**/*"] +}