first commit
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@menulio/shared",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./slug.js";
|
||||
export * from "./theme.js";
|
||||
export * from "./types.js";
|
||||
@@ -0,0 +1,30 @@
|
||||
// Slug rules per PRD §10: lowercase, Turkish characters normalized, spaces -> "-", safe charset.
|
||||
const TR_MAP: Record<string, string> = {
|
||||
ç: "c",
|
||||
ğ: "g",
|
||||
ı: "i",
|
||||
ö: "o",
|
||||
ş: "s",
|
||||
ü: "u",
|
||||
Ç: "c",
|
||||
Ğ: "g",
|
||||
İ: "i",
|
||||
Ö: "o",
|
||||
Ş: "s",
|
||||
Ü: "u",
|
||||
};
|
||||
|
||||
export function slugify(input: string): string {
|
||||
const normalized = input
|
||||
.split("")
|
||||
.map((char) => TR_MAP[char] ?? char)
|
||||
.join("");
|
||||
|
||||
return normalized
|
||||
.toLowerCase()
|
||||
.normalize("NFD")
|
||||
.replace(/[̀-ͯ]/g, "")
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.slice(0, 60);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// Theme config contract shared by the theme engine (PRD §8): mobile writes it,
|
||||
// the public web renderer consumes it. Changing a theme must never touch menu data.
|
||||
export const themeConfigSchema = z.object({
|
||||
primaryColor: z.string(),
|
||||
background: z.string(),
|
||||
productLayout: z.enum(["card", "list"]),
|
||||
categoryLayout: z.enum(["accordion", "tabs", "flat"]),
|
||||
});
|
||||
|
||||
export type ThemeConfig = z.infer<typeof themeConfigSchema>;
|
||||
@@ -0,0 +1,133 @@
|
||||
// Mirrors supabase/migrations/20260819000000_initial_schema.sql.
|
||||
// Kept hand-written for V1; consider generating via `supabase gen types` once
|
||||
// the schema stabilizes.
|
||||
|
||||
export type SubscriptionStatus = "active" | "grace_period" | "suspended" | "deleted";
|
||||
export type AiImportStatus = "pending" | "processing" | "completed" | "failed";
|
||||
export type DomainStatus = "pending" | "verified" | "failed";
|
||||
|
||||
export interface Restaurant {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
logoUrl: string | null;
|
||||
phone: string | null;
|
||||
address: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
id: string;
|
||||
restaurantId: string;
|
||||
name: string;
|
||||
address: string | null;
|
||||
phone: string | null;
|
||||
}
|
||||
|
||||
export interface Menu {
|
||||
id: string;
|
||||
locationId: string;
|
||||
name: string;
|
||||
isPublished: boolean;
|
||||
publishedAt: string | null;
|
||||
}
|
||||
|
||||
export interface MenuCategory {
|
||||
id: string;
|
||||
menuId: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
sortOrder: number;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface MenuItem {
|
||||
id: string;
|
||||
categoryId: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
price: number;
|
||||
imageUrl: string | null;
|
||||
sortOrder: number;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface MenuItemOption {
|
||||
id: string;
|
||||
menuItemId: string;
|
||||
groupName: string;
|
||||
label: string;
|
||||
priceDelta: number;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export interface AiImport {
|
||||
id: string;
|
||||
restaurantId: string;
|
||||
sourceImage: string;
|
||||
status: AiImportStatus;
|
||||
model: string | null;
|
||||
error: string | null;
|
||||
processedAt: string | null;
|
||||
}
|
||||
|
||||
export interface AiImportItem {
|
||||
id: string;
|
||||
aiImportId: string;
|
||||
categoryName: string | null;
|
||||
itemName: string;
|
||||
description: string | null;
|
||||
price: number | null;
|
||||
confidence: number | null;
|
||||
}
|
||||
|
||||
export interface AiExtractedItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
price: number;
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export interface AiExtractedCategory {
|
||||
id: string;
|
||||
name: string;
|
||||
items: AiExtractedItem[];
|
||||
}
|
||||
|
||||
export interface AiImportResponse {
|
||||
importId: string;
|
||||
status: AiImportStatus;
|
||||
model: string | null;
|
||||
categories: AiExtractedCategory[];
|
||||
totalItems: number;
|
||||
lowConfidenceCount: number;
|
||||
}
|
||||
|
||||
export interface AiImportApplyPayload {
|
||||
categories: {
|
||||
name: string;
|
||||
items: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
price: number;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface Subscription {
|
||||
id: string;
|
||||
restaurantId: string;
|
||||
status: SubscriptionStatus;
|
||||
productId: string | null;
|
||||
trialEndsAt: string | null;
|
||||
currentPeriodEndsAt: string | null;
|
||||
}
|
||||
|
||||
export interface Domain {
|
||||
id: string;
|
||||
restaurantId: string;
|
||||
hostname: string;
|
||||
isCustom: boolean;
|
||||
status: DomainStatus;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user