Replace entire repo content with code from /root/shahikitchen-google/
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
import { mkdir, readFile, writeFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
import type { MenuCategory, MenuItem } from '@/domain/menu/entities';
|
||||
import type { MenuVersionTrigger } from '@/domain/menu/versioning';
|
||||
import { menuCategories as defaultMenuCategories } from './static-menu-data';
|
||||
|
||||
const MENU_DATA_DIR = path.join(process.cwd(), 'data');
|
||||
const MENU_DATA_FILE = path.join(MENU_DATA_DIR, 'menu.json');
|
||||
|
||||
let memoryCache: MenuCategory[] | null = null;
|
||||
|
||||
async function ensureMenuFile(): Promise<void> {
|
||||
await mkdir(MENU_DATA_DIR, { recursive: true });
|
||||
|
||||
try {
|
||||
await readFile(MENU_DATA_FILE, 'utf8');
|
||||
} catch {
|
||||
await writeFile(MENU_DATA_FILE, JSON.stringify(defaultMenuCategories, null, 2), 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
function isMenuCategoryArray(value: unknown): value is MenuCategory[] {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.every(
|
||||
(category) =>
|
||||
typeof category === 'object' &&
|
||||
category !== null &&
|
||||
typeof (category as MenuCategory).id === 'string' &&
|
||||
Array.isArray((category as MenuCategory).items),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export async function readMenuCategories(): Promise<MenuCategory[]> {
|
||||
if (memoryCache) return memoryCache;
|
||||
|
||||
await ensureMenuFile();
|
||||
const raw = await readFile(MENU_DATA_FILE, 'utf8');
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
|
||||
if (!isMenuCategoryArray(parsed)) {
|
||||
memoryCache = defaultMenuCategories;
|
||||
return memoryCache;
|
||||
}
|
||||
|
||||
memoryCache = parsed;
|
||||
return memoryCache;
|
||||
}
|
||||
|
||||
export interface WriteMenuOptions {
|
||||
snapshotLabel?: string;
|
||||
snapshotTrigger?: MenuVersionTrigger;
|
||||
skipSnapshot?: boolean;
|
||||
}
|
||||
|
||||
export async function persistMenuCategories(categories: MenuCategory[]): Promise<void> {
|
||||
await ensureMenuFile();
|
||||
await writeFile(MENU_DATA_FILE, JSON.stringify(categories, null, 2), 'utf8');
|
||||
memoryCache = categories;
|
||||
}
|
||||
|
||||
export async function writeMenuCategories(
|
||||
categories: MenuCategory[],
|
||||
options?: WriteMenuOptions,
|
||||
): Promise<void> {
|
||||
await persistMenuCategories(categories);
|
||||
|
||||
if (!options?.skipSnapshot) {
|
||||
const { createMenuSnapshot, ensureMenuBaseline } = await import('./menu-versioning');
|
||||
await ensureMenuBaseline();
|
||||
await createMenuSnapshot(
|
||||
categories,
|
||||
options?.snapshotLabel ?? 'Menu updated',
|
||||
options?.snapshotTrigger ?? 'auto',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateMenuItem(
|
||||
categoryId: string,
|
||||
itemId: string,
|
||||
updates: Partial<MenuItem>,
|
||||
): Promise<MenuItem | null> {
|
||||
const categories = await readMenuCategories();
|
||||
let updatedItem: MenuItem | null = null;
|
||||
|
||||
const nextCategories = categories.map((category) => {
|
||||
if (category.id !== categoryId) return category;
|
||||
|
||||
return {
|
||||
...category,
|
||||
items: category.items.map((item) => {
|
||||
if (item.id !== itemId) return item;
|
||||
updatedItem = { ...item, ...updates, id: item.id };
|
||||
return updatedItem;
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
if (!updatedItem) return null;
|
||||
|
||||
const dishName =
|
||||
nextCategories
|
||||
.find((category) => category.id === categoryId)
|
||||
?.items.find((item) => item.id === itemId)?.name ?? itemId;
|
||||
|
||||
await writeMenuCategories(nextCategories, {
|
||||
snapshotLabel: `Updated dish: ${dishName}`,
|
||||
});
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
export function slugifyMenuId(text: string): string {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
}
|
||||
|
||||
function allItems(categories: MenuCategory[]): MenuItem[] {
|
||||
return categories.flatMap((category) => category.items);
|
||||
}
|
||||
|
||||
function isItemIdTaken(categories: MenuCategory[], itemId: string): boolean {
|
||||
return allItems(categories).some((item) => item.id === itemId);
|
||||
}
|
||||
|
||||
function uniqueItemId(categories: MenuCategory[], baseId: string): string {
|
||||
if (!isItemIdTaken(categories, baseId)) return baseId;
|
||||
let suffix = 2;
|
||||
while (isItemIdTaken(categories, `${baseId}-${suffix}`)) suffix += 1;
|
||||
return `${baseId}-${suffix}`;
|
||||
}
|
||||
|
||||
export async function addMenuItem(categoryId: string, item: MenuItem): Promise<MenuItem | null> {
|
||||
const categories = await readMenuCategories();
|
||||
const categoryExists = categories.some((category) => category.id === categoryId);
|
||||
if (!categoryExists) return null;
|
||||
|
||||
const id = uniqueItemId(categories, slugifyMenuId(item.id || item.name));
|
||||
if (!id) return null;
|
||||
|
||||
const newItem: MenuItem = { ...item, id };
|
||||
|
||||
const nextCategories = categories.map((category) =>
|
||||
category.id === categoryId
|
||||
? { ...category, items: [...category.items, newItem] }
|
||||
: category,
|
||||
);
|
||||
|
||||
await writeMenuCategories(nextCategories, {
|
||||
snapshotLabel: `Added dish: ${newItem.name}`,
|
||||
});
|
||||
return newItem;
|
||||
}
|
||||
|
||||
export async function removeMenuItem(categoryId: string, itemId: string): Promise<boolean> {
|
||||
const categories = await readMenuCategories();
|
||||
let removed = false;
|
||||
|
||||
const nextCategories = categories.map((category) => {
|
||||
if (category.id !== categoryId) return category;
|
||||
const nextItems = category.items.filter((item) => {
|
||||
if (item.id === itemId) {
|
||||
removed = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return { ...category, items: nextItems };
|
||||
});
|
||||
|
||||
if (!removed) return false;
|
||||
|
||||
const removedName =
|
||||
categories
|
||||
.find((category) => category.id === categoryId)
|
||||
?.items.find((item) => item.id === itemId)?.name ?? itemId;
|
||||
|
||||
await writeMenuCategories(nextCategories, {
|
||||
snapshotLabel: `Removed dish: ${removedName}`,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function addMenuCategory(category: MenuCategory): Promise<MenuCategory | null> {
|
||||
const categories = await readMenuCategories();
|
||||
const id = slugifyMenuId(category.id || category.name);
|
||||
if (!id) return null;
|
||||
|
||||
if (categories.some((existing) => existing.id === id)) return null;
|
||||
|
||||
const newCategory: MenuCategory = {
|
||||
id,
|
||||
name: category.name.trim(),
|
||||
items: category.items ?? [],
|
||||
};
|
||||
|
||||
await writeMenuCategories([...categories, newCategory], {
|
||||
snapshotLabel: `Added category: ${newCategory.name}`,
|
||||
});
|
||||
return newCategory;
|
||||
}
|
||||
|
||||
export async function removeMenuCategory(categoryId: string): Promise<boolean> {
|
||||
const categories = await readMenuCategories();
|
||||
if (!categories.some((category) => category.id === categoryId)) return false;
|
||||
|
||||
const removedCategory = categories.find((category) => category.id === categoryId);
|
||||
const nextCategories = categories.filter((category) => category.id !== categoryId);
|
||||
await writeMenuCategories(nextCategories, {
|
||||
snapshotLabel: `Removed category: ${removedCategory?.name ?? categoryId}`,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function updateMenuCategory(
|
||||
categoryId: string,
|
||||
updates: Partial<Pick<MenuCategory, 'name'>>,
|
||||
): Promise<MenuCategory | null> {
|
||||
const categories = await readMenuCategories();
|
||||
let updatedCategory: MenuCategory | null = null;
|
||||
|
||||
const nextCategories = categories.map((category) => {
|
||||
if (category.id !== categoryId) return category;
|
||||
updatedCategory = {
|
||||
...category,
|
||||
...(typeof updates.name === 'string' ? { name: updates.name.trim() } : {}),
|
||||
};
|
||||
return updatedCategory;
|
||||
});
|
||||
|
||||
if (!updatedCategory) return null;
|
||||
|
||||
const categoryName =
|
||||
nextCategories.find((category) => category.id === categoryId)?.name ?? categoryId;
|
||||
|
||||
await writeMenuCategories(nextCategories, {
|
||||
snapshotLabel: `Updated category: ${categoryName}`,
|
||||
});
|
||||
return updatedCategory;
|
||||
}
|
||||
|
||||
export function clearMenuCache(): void {
|
||||
memoryCache = null;
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
import { mkdir, readFile, writeFile, unlink } from 'fs/promises';
|
||||
import path from 'path';
|
||||
import type { MenuCategory } from '@/domain/menu/entities';
|
||||
import {
|
||||
MENU_BASELINE_VERSION_ID,
|
||||
type MenuVersionListItem,
|
||||
type MenuVersionMeta,
|
||||
type MenuVersionSnapshot,
|
||||
type MenuVersionTrigger,
|
||||
} from '@/domain/menu/versioning';
|
||||
import { menuCategories as defaultMenuCategories } from './static-menu-data';
|
||||
|
||||
const VERSIONS_DIR = path.join(process.cwd(), 'data', 'menu-versions');
|
||||
const INDEX_FILE = path.join(VERSIONS_DIR, 'index.json');
|
||||
const BASELINE_FILE = path.join(VERSIONS_DIR, 'baseline.json');
|
||||
|
||||
interface VersionIndex {
|
||||
versions: MenuVersionMeta[];
|
||||
}
|
||||
|
||||
function versionFilePath(id: string): string {
|
||||
return id === MENU_BASELINE_VERSION_ID
|
||||
? BASELINE_FILE
|
||||
: path.join(VERSIONS_DIR, `${id}.json`);
|
||||
}
|
||||
|
||||
function isMenuCategoryArray(value: unknown): value is MenuCategory[] {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.every(
|
||||
(category) =>
|
||||
typeof category === 'object' &&
|
||||
category !== null &&
|
||||
typeof (category as MenuCategory).id === 'string' &&
|
||||
Array.isArray((category as MenuCategory).items),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function countMenuStats(categories: MenuCategory[]): { categoryCount: number; dishCount: number } {
|
||||
return {
|
||||
categoryCount: categories.length,
|
||||
dishCount: categories.reduce((sum, category) => sum + category.items.length, 0),
|
||||
};
|
||||
}
|
||||
|
||||
function menuFingerprint(categories: MenuCategory[]): string {
|
||||
return JSON.stringify(categories);
|
||||
}
|
||||
|
||||
function cloneCategories(categories: MenuCategory[]): MenuCategory[] {
|
||||
return JSON.parse(JSON.stringify(categories)) as MenuCategory[];
|
||||
}
|
||||
|
||||
async function ensureVersionsDir(): Promise<void> {
|
||||
await mkdir(VERSIONS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
async function readIndex(): Promise<VersionIndex> {
|
||||
await ensureVersionsDir();
|
||||
try {
|
||||
const raw = await readFile(INDEX_FILE, 'utf8');
|
||||
const parsed = JSON.parse(raw) as VersionIndex;
|
||||
if (!Array.isArray(parsed.versions)) return { versions: [] };
|
||||
return parsed;
|
||||
} catch {
|
||||
return { versions: [] };
|
||||
}
|
||||
}
|
||||
|
||||
async function writeIndex(index: VersionIndex): Promise<void> {
|
||||
await ensureVersionsDir();
|
||||
await writeFile(INDEX_FILE, JSON.stringify(index, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
async function writeSnapshotFile(snapshot: MenuVersionSnapshot): Promise<void> {
|
||||
await ensureVersionsDir();
|
||||
await writeFile(versionFilePath(snapshot.id), JSON.stringify(snapshot, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
async function readSnapshotFile(id: string): Promise<MenuVersionSnapshot | null> {
|
||||
try {
|
||||
const raw = await readFile(versionFilePath(id), 'utf8');
|
||||
const parsed = JSON.parse(raw) as MenuVersionSnapshot;
|
||||
if (!isMenuCategoryArray(parsed.categories)) return null;
|
||||
return parsed;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function createVersionId(): string {
|
||||
return `v-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
|
||||
export async function ensureMenuBaseline(): Promise<MenuVersionMeta> {
|
||||
await ensureVersionsDir();
|
||||
const index = await readIndex();
|
||||
const existing = index.versions.find((version) => version.id === MENU_BASELINE_VERSION_ID);
|
||||
if (existing) return existing;
|
||||
|
||||
const categories = cloneCategories(defaultMenuCategories);
|
||||
const stats = countMenuStats(categories);
|
||||
const createdAt = new Date().toISOString();
|
||||
|
||||
const baseline: MenuVersionSnapshot = {
|
||||
id: MENU_BASELINE_VERSION_ID,
|
||||
label: 'Original menu (baseline)',
|
||||
createdAt,
|
||||
trigger: 'auto',
|
||||
categories,
|
||||
isBaseline: true,
|
||||
};
|
||||
|
||||
await writeSnapshotFile(baseline);
|
||||
|
||||
const meta: MenuVersionMeta = {
|
||||
id: MENU_BASELINE_VERSION_ID,
|
||||
label: baseline.label,
|
||||
createdAt,
|
||||
trigger: 'auto',
|
||||
isBaseline: true,
|
||||
...stats,
|
||||
};
|
||||
|
||||
index.versions.push(meta);
|
||||
await writeIndex(index);
|
||||
return meta;
|
||||
}
|
||||
|
||||
export async function createMenuSnapshot(
|
||||
categories: MenuCategory[],
|
||||
label: string,
|
||||
trigger: MenuVersionTrigger = 'auto',
|
||||
): Promise<MenuVersionMeta> {
|
||||
await ensureMenuBaseline();
|
||||
const index = await readIndex();
|
||||
const id = createVersionId();
|
||||
const createdAt = new Date().toISOString();
|
||||
const stats = countMenuStats(categories);
|
||||
|
||||
const snapshot: MenuVersionSnapshot = {
|
||||
id,
|
||||
label: label.trim() || 'Menu updated',
|
||||
createdAt,
|
||||
trigger,
|
||||
categories: cloneCategories(categories),
|
||||
isBaseline: false,
|
||||
};
|
||||
|
||||
await writeSnapshotFile(snapshot);
|
||||
|
||||
const meta: MenuVersionMeta = {
|
||||
id,
|
||||
label: snapshot.label,
|
||||
createdAt,
|
||||
trigger,
|
||||
isBaseline: false,
|
||||
...stats,
|
||||
};
|
||||
|
||||
index.versions.unshift(meta);
|
||||
await writeIndex(index);
|
||||
return meta;
|
||||
}
|
||||
|
||||
export async function listMenuVersions(liveCategories: MenuCategory[]): Promise<MenuVersionListItem[]> {
|
||||
await ensureMenuBaseline();
|
||||
const index = await readIndex();
|
||||
const liveHash = menuFingerprint(liveCategories);
|
||||
|
||||
const items: MenuVersionListItem[] = [];
|
||||
|
||||
for (const meta of index.versions) {
|
||||
const snapshot = await readSnapshotFile(meta.id);
|
||||
const matchesLive = snapshot ? menuFingerprint(snapshot.categories) === liveHash : false;
|
||||
items.push({ ...meta, matchesLive });
|
||||
}
|
||||
|
||||
const baseline = items.find((item) => item.isBaseline);
|
||||
const nonBaseline = items.filter((item) => !item.isBaseline);
|
||||
nonBaseline.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
||||
|
||||
return baseline ? [...nonBaseline, baseline] : nonBaseline;
|
||||
}
|
||||
|
||||
export async function getMenuVersionCategories(versionId: string): Promise<MenuCategory[] | null> {
|
||||
const snapshot = await readSnapshotFile(versionId);
|
||||
return snapshot ? cloneCategories(snapshot.categories) : null;
|
||||
}
|
||||
|
||||
export async function restoreMenuVersion(
|
||||
versionId: string,
|
||||
persist: (categories: MenuCategory[]) => Promise<void>,
|
||||
readLive: () => Promise<MenuCategory[]>,
|
||||
): Promise<MenuVersionMeta> {
|
||||
const snapshot = await readSnapshotFile(versionId);
|
||||
if (!snapshot) {
|
||||
throw new Error('Version not found.');
|
||||
}
|
||||
|
||||
const current = await readLive();
|
||||
await createMenuSnapshot(
|
||||
current,
|
||||
`Before rollback to "${snapshot.label}"`,
|
||||
'restore',
|
||||
);
|
||||
|
||||
await persist(snapshot.categories);
|
||||
|
||||
return {
|
||||
id: snapshot.id,
|
||||
label: snapshot.label,
|
||||
createdAt: snapshot.createdAt,
|
||||
trigger: snapshot.trigger,
|
||||
isBaseline: snapshot.isBaseline,
|
||||
...countMenuStats(snapshot.categories),
|
||||
};
|
||||
}
|
||||
|
||||
export async function resetMenuVersionHistory(categories: MenuCategory[]): Promise<MenuVersionMeta> {
|
||||
await ensureVersionsDir();
|
||||
const index = await readIndex();
|
||||
|
||||
for (const version of index.versions) {
|
||||
if (version.id === MENU_BASELINE_VERSION_ID) continue;
|
||||
try {
|
||||
await unlink(versionFilePath(version.id));
|
||||
} catch {
|
||||
// File may already be missing.
|
||||
}
|
||||
}
|
||||
|
||||
const stats = countMenuStats(categories);
|
||||
const createdAt = new Date().toISOString();
|
||||
|
||||
const baseline: MenuVersionSnapshot = {
|
||||
id: MENU_BASELINE_VERSION_ID,
|
||||
label: 'Current menu (baseline)',
|
||||
createdAt,
|
||||
trigger: 'manual',
|
||||
categories: cloneCategories(categories),
|
||||
isBaseline: true,
|
||||
};
|
||||
|
||||
await writeSnapshotFile(baseline);
|
||||
|
||||
const meta: MenuVersionMeta = {
|
||||
id: MENU_BASELINE_VERSION_ID,
|
||||
label: baseline.label,
|
||||
createdAt,
|
||||
trigger: 'manual',
|
||||
isBaseline: true,
|
||||
...stats,
|
||||
};
|
||||
|
||||
index.versions = [meta];
|
||||
await writeIndex(index);
|
||||
return meta;
|
||||
}
|
||||
|
||||
export async function deleteMenuVersion(versionId: string): Promise<boolean> {
|
||||
if (versionId === MENU_BASELINE_VERSION_ID) {
|
||||
throw new Error('The baseline version cannot be deleted.');
|
||||
}
|
||||
|
||||
const index = await readIndex();
|
||||
const nextVersions = index.versions.filter((version) => version.id !== versionId);
|
||||
if (nextVersions.length === index.versions.length) return false;
|
||||
|
||||
index.versions = nextVersions;
|
||||
await writeIndex(index);
|
||||
|
||||
try {
|
||||
await unlink(versionFilePath(versionId));
|
||||
} catch {
|
||||
// File may already be missing; index update is sufficient.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -133,7 +133,7 @@ export const menuCategories: MenuCategory[] = [
|
||||
{ id: "chicken-karahi", name: "Chicken Karahi", description: "Wok-tossed chicken in a robust tomato, chili and ginger gravy.", price: 149, image: "chicken-karahi.jpg", video: "chicken-karahi.mp4" },
|
||||
{ id: "lahore-sizzler", name: "Lahore Sizzler", description: "Sizzling platter of marinated chicken with vegetables and spicy sauces.", price: 169, image: "lahore-sizzler.jpg", video: "lahore-sizzler.mp4" },
|
||||
{ id: "butter-chicken", name: "Butter Chicken", description: "Tender chicken in a creamy tomato and butter gravy with mild spices.", price: 149, image: "butter-chicken.jpg" },
|
||||
{ id: "chicken-haleem", name: "Chicken Haleem", description: "Slow-cooked shredded chicken with lentils, wheat and aromatic spices.", price: 149, image: "chicken-haleem.jpg", video: "chicken-haleem.mp4" },
|
||||
{ id: "chicken-haleem", name: "Chicken Haleem", description: "Slow-cooked shredded chicken with lentils, wheat and aromatic spices.", price: 139, image: "chicken-haleem.jpg", video: "chicken-haleem.mp4" },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -171,7 +171,7 @@ export const menuCategories: MenuCategory[] = [
|
||||
{ id: "lambay-gulab-jaman", name: "Lambay Gulab Jamun", description: "Elongated gulab jamun with extra syrup — a Shahi Sweets favourite.", image: "lambay-gulab-jaman.jpg", ...WEIGHT_SWEET_PRICING },
|
||||
{ id: "cream-gulab-jaman", name: "Cream Gulab Jamun", description: "Gulab jamun filled with creamy centre, finished in fragrant syrup.", image: "cream-gulab-jaman.jpg", ...WEIGHT_SWEET_PRICING },
|
||||
{ id: "ras-gulay", name: "Ras Gulay", description: "Spongy cottage-cheese balls in light sugar syrup — chilled and refreshing.", image: "ras-gulay.jpg", video: "ras-gulay.mp4", ...WEIGHT_SWEET_PRICING },
|
||||
{ id: "rasmalai", name: "Rasmalai", description: "Soft cheese dumplings soaked in chilled sweetened milk with cardamom and saffron.", image: "rasmalai.jpg", video: "rasmalai.mp4", ...WEIGHT_SWEET_PRICING },
|
||||
{ id: "rasmalai", name: "Rasmalai", description: "Soft cheese dumplings soaked in chilled sweetened milk with cardamom and saffron.", price: 45, image: "rasmalai.jpg", video: "rasmalai.mp4" },
|
||||
{ id: "cham-cham", name: "Cham Cham", description: "Oval Bengali sweet coated in coconut or pistachio — soft and milky.", image: "cham-cham.jpg", video: "cham-cham.mp4", ...WEIGHT_SWEET_PRICING },
|
||||
{ id: "paira", name: "Paira", description: "Traditional milk fudge sweet with a smooth, grainy texture.", image: "paira.jpg", video: "paira.mp4", ...WEIGHT_SWEET_PRICING },
|
||||
{ id: "laddu", name: "Laddu", description: "Round gram-flour and ghee sweet balls — festive and aromatic.", image: "laddu.jpg", video: "laddu.mp4", ...WEIGHT_SWEET_PRICING },
|
||||
@@ -197,10 +197,10 @@ export const menuCategories: MenuCategory[] = [
|
||||
items: [
|
||||
{ id: "masala-chai", name: "Masala Chai", description: "Traditional spiced tea brewed with milk, cardamom, ginger and aromatic spices.", price: 39, image: "masala-chai.jpg" },
|
||||
{ id: "mango-lassi", name: "Mango Lassi", description: "Refreshing sweet yogurt drink blended with ripe mango and cardamom.", price: 45, image: "mango-lassi.jpg", video: "mango-lassi.mp4" },
|
||||
{ id: "coca-cola", name: "Coca-Cola", description: "Classic chilled cola soft drink.", price: 29, video: "coca-cola.mp4" },
|
||||
{ id: "pepsi-fanta", name: "Pepsi / Fanta", description: "Refreshing cola or orange flavored carbonated beverage.", price: 29, video: "pepsi-fanta.mp4" },
|
||||
{ id: "sprite-ramlosa", name: "Sprite / Ramlösa", description: "Crisp lemon-lime soda or sparkling mineral water.", price: 29, video: "sprite-ramlosa.mp4" },
|
||||
{ id: "energy-drink", name: "Energy Drink", description: "Caffeinated beverage for an instant energy boost.", price: 39, video: "energy-drink.mp4" },
|
||||
{ id: "coca-cola", name: "Coca-Cola", description: "Classic chilled cola soft drink.", price: 25, video: "coca-cola.mp4" },
|
||||
{ id: "pepsi-fanta", name: "Pepsi / Fanta", description: "Refreshing cola or orange flavored carbonated beverage.", price: 25, video: "pepsi-fanta.mp4" },
|
||||
{ id: "sprite-ramlosa", name: "Sprite / Ramlösa", description: "Crisp lemon-lime soda or sparkling mineral water.", price: 25, video: "sprite-ramlosa.mp4" },
|
||||
{ id: "energy-drink", name: "Energy Drink", description: "Caffeinated beverage for an instant energy boost.", price: 29, video: "energy-drink.mp4" },
|
||||
{ id: "juice", name: "Juice", description: "Fresh fruit juice, typically mango or other seasonal flavors.", price: 20, image: "mango-juice.jpg" },
|
||||
{ id: "coffee", name: "Coffee", description: "Freshly brewed hot coffee.", price: 39, image: "black-coffee.jpg" },
|
||||
{ id: "latte", name: "Latte", description: "Espresso coffee with steamed milk and a light layer of foam.", price: 49, image: "latte.jpg", video: "latte.mp4" },
|
||||
|
||||
Reference in New Issue
Block a user