/** * rBnb Automerge document schemas. * * Community hospitality — trust-based space sharing and couch surfing. * Granularity: one Automerge document per space (listings + stays + endorsements). * DocId format: {space}:bnb:listings */ import type { DocSchema } from '../../shared/local-first/document'; // ── Economy model ── export type EconomyModel = 'gift' | 'suggested' | 'fixed' | 'sliding_scale' | 'exchange'; // ── Listing types ── export type ListingType = | 'couch' | 'room' | 'apartment' | 'cabin' | 'tent_site' | 'land' | 'studio' | 'loft' | 'house' | 'other'; // ── Stay request status ── export type StayStatus = | 'pending' | 'accepted' | 'declined' | 'cancelled' | 'completed' | 'endorsed'; // ── Endorsement visibility ── export type EndorsementVisibility = 'public' | 'private' | 'community'; // ── Core types ── export interface AvailabilityWindow { id: string; listingId: string; startDate: number; // epoch ms (start of day) endDate: number; // epoch ms (end of day) status: 'available' | 'blocked' | 'tentative'; notes: string | null; createdAt: number; } export interface Listing { id: string; hostDid: string; // DID of the host hostName: string; title: string; description: string; type: ListingType; economy: EconomyModel; // Pricing (relevant for non-gift economies) suggestedAmount: number | null; // suggested/fixed price per night currency: string | null; // e.g. 'USD', 'EUR', or null for gift slidingMin: number | null; // sliding scale minimum slidingMax: number | null; // sliding scale maximum exchangeDescription: string | null; // what the host wants in exchange // Location locationName: string; locationLat: number | null; locationLng: number | null; locationGranularity: string | null; // 'city', 'neighborhood', 'address' // Capacity & details guestCapacity: number; bedroomCount: number | null; bedCount: number | null; bathroomCount: number | null; amenities: string[]; // e.g. ['wifi', 'kitchen', 'laundry', 'parking'] houseRules: string[]; // e.g. ['no_smoking', 'quiet_hours', 'shoes_off'] photos: string[]; // asset IDs or URLs coverPhoto: string | null; // Trust & auto-accept trustThreshold: number | null; // 0-100, rNetwork trust score for auto-accept instantAccept: boolean; // if true + trust met → auto-accept requests // Metadata isActive: boolean; createdAt: number; updatedAt: number; } export interface StayMessage { id: string; senderDid: string; senderName: string; body: string; sentAt: number; } export interface StayRequest { id: string; listingId: string; guestDid: string; guestName: string; hostDid: string; // Dates checkIn: number; // epoch ms checkOut: number; // epoch ms guestCount: number; // Status flow: pending → accepted/declined → completed → endorsed status: StayStatus; // Messages embedded in the CRDT (conversation lives in the request) messages: StayMessage[]; // Contribution (for non-gift economies) offeredAmount: number | null; offeredCurrency: string | null; offeredExchange: string | null; // Timestamps requestedAt: number; respondedAt: number | null; completedAt: number | null; cancelledAt: number | null; } export interface Endorsement { id: string; stayId: string; listingId: string; // Who wrote it and about whom authorDid: string; authorName: string; subjectDid: string; // the person being endorsed (host or guest) subjectName: string; direction: 'guest_to_host' | 'host_to_guest'; // Content body: string; rating: number | null; // 1-5, optional (endorsements > ratings) tags: string[]; // e.g. ['welcoming', 'clean', 'great_conversation'] visibility: EndorsementVisibility; // Trust integration — feeds into rNetwork trust graph trustWeight: number; // 0-1, how much this endorsement affects trust score createdAt: number; } export interface SpaceConfig { defaultEconomy: EconomyModel; defaultTrustThreshold: number; // 0-100 amenityCatalog: string[]; // available amenities for this community houseRuleCatalog: string[]; // available house rules endorsementTagCatalog: string[]; // available endorsement tags requireEndorsement: boolean; // whether both parties must endorse after stay maxStayDays: number; // maximum stay length in days } // ── Top-level document ── export interface BnbDoc { meta: { module: string; collection: string; version: number; spaceSlug: string; createdAt: number; }; config: SpaceConfig; listings: Record; availability: Record; stays: Record; endorsements: Record; } // ── Schema registration ── export const DEFAULT_AMENITIES = [ 'wifi', 'kitchen', 'laundry', 'parking', 'garden', 'workspace', 'heating', 'air_conditioning', 'hot_water', 'towels', 'linens', 'pets_welcome', 'wheelchair_accessible', 'bike_storage', ]; export const DEFAULT_HOUSE_RULES = [ 'no_smoking', 'quiet_hours', 'shoes_off', 'no_parties', 'clean_up_after', 'no_pets', 'check_in_by_10pm', ]; export const DEFAULT_ENDORSEMENT_TAGS = [ 'welcoming', 'clean', 'great_conversation', 'respectful', 'generous', 'good_communication', 'safe_space', 'cozy', 'well_located', 'quiet', 'fun', 'helpful', ]; const DEFAULT_CONFIG: SpaceConfig = { defaultEconomy: 'gift', defaultTrustThreshold: 30, amenityCatalog: DEFAULT_AMENITIES, houseRuleCatalog: DEFAULT_HOUSE_RULES, endorsementTagCatalog: DEFAULT_ENDORSEMENT_TAGS, requireEndorsement: false, maxStayDays: 30, }; export const bnbSchema: DocSchema = { module: 'bnb', collection: 'listings', version: 1, init: (): BnbDoc => ({ meta: { module: 'bnb', collection: 'listings', version: 1, spaceSlug: '', createdAt: Date.now(), }, config: { ...DEFAULT_CONFIG }, listings: {}, availability: {}, stays: {}, endorsements: {}, }), }; // ── Helpers ── export function bnbDocId(space: string) { return `${space}:bnb:listings` as const; } /** Economy model display labels */ export const ECONOMY_LABELS: Record = { gift: 'Gift Economy', suggested: 'Suggested Contribution', fixed: 'Fixed Price', sliding_scale: 'Sliding Scale', exchange: 'Skill/Service Exchange', }; /** Listing type display labels */ export const LISTING_TYPE_LABELS: Record = { couch: 'Couch', room: 'Private Room', apartment: 'Apartment', cabin: 'Cabin', tent_site: 'Tent Site', land: 'Land', studio: 'Studio', loft: 'Loft', house: 'House', other: 'Other', }; /** Listing type icons */ export const LISTING_TYPE_ICONS: Record = { couch: '\u{1F6CB}', // couch room: '\u{1F6CF}', // bed apartment: '\u{1F3E2}', // building cabin: '\u{1F3E1}', // house tent_site: '\u{26FA}', // tent land: '\u{1F333}', // tree studio: '\u{1F3A8}', // palette loft: '\u{1F3D7}', // building construction house: '\u{1F3E0}', // house other: '\u{1F3E8}', // hotel };