Replace entire repo content with code from /root/shahikitchen-google/
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
const ADMIN_USERNAME = 'admin';
|
||||
const ADMIN_PASSWORD = 'bubbasen99';
|
||||
|
||||
export function isValidAdminCredentials(username: string, password: string): boolean {
|
||||
return username.trim() === ADMIN_USERNAME && password === ADMIN_PASSWORD;
|
||||
}
|
||||
@@ -2,7 +2,10 @@ import type { Language } from '@/domain/language/entities';
|
||||
import type { MenuItem } from '@/domain/menu/entities';
|
||||
import type { OrderLine } from '@/domain/shared/order-line';
|
||||
import { getMenuItemById } from '@/infrastructure/menu/static-menu-data';
|
||||
import { getCategoryName as resolveCategoryName } from '@/presentation/i18n/menu/category-names';
|
||||
import {
|
||||
getCategoryName as resolveCategoryName,
|
||||
getMenuItemInclusionNote as resolveMenuItemInclusionNote,
|
||||
} from '@/presentation/i18n/menu/category-names';
|
||||
import { getMenuItemName as resolveItemName } from '@/presentation/i18n/menu/item-names';
|
||||
import type { TranslationBundle } from '@/presentation/i18n/translations';
|
||||
|
||||
@@ -39,6 +42,10 @@ export function getCategoryName(
|
||||
return resolveCategoryName(lang, categoryId, fallback);
|
||||
}
|
||||
|
||||
export function getMenuItemInclusionNote(lang: Language, categoryId: string): string | undefined {
|
||||
return resolveMenuItemInclusionNote(lang, categoryId);
|
||||
}
|
||||
|
||||
export function localizeMenuItem(
|
||||
lang: Language,
|
||||
t: TranslationBundle,
|
||||
|
||||
@@ -5,23 +5,11 @@ import {
|
||||
} from '@/domain/language/entities';
|
||||
import type { LanguageRepository } from '@/domain/language/repository';
|
||||
|
||||
export function detectBrowserLanguage(): Language {
|
||||
if (typeof navigator === 'undefined') return DEFAULT_LANGUAGE;
|
||||
|
||||
const browserLang = navigator.language.toLowerCase();
|
||||
if (browserLang.startsWith('sv')) return 'sv';
|
||||
if (browserLang.startsWith('ar')) return 'ar';
|
||||
if (browserLang.startsWith('tr')) return 'tr';
|
||||
if (browserLang.startsWith('hi')) return 'hi';
|
||||
if (browserLang.startsWith('ur')) return 'ur';
|
||||
if (browserLang.startsWith('en')) return 'en';
|
||||
return DEFAULT_LANGUAGE;
|
||||
}
|
||||
|
||||
/** New visitors see Swedish unless they have saved a language preference. */
|
||||
export function resolveInitialLanguage(repository: LanguageRepository): Language {
|
||||
const saved = repository.load();
|
||||
if (saved && isLanguage(saved)) return saved;
|
||||
return detectBrowserLanguage();
|
||||
return DEFAULT_LANGUAGE;
|
||||
}
|
||||
|
||||
export function persistLanguage(repository: LanguageRepository, language: Language): void {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { Language } from '@/domain/language/entities';
|
||||
import { getTranslation, type TranslationBundle } from '@/presentation/i18n/translations';
|
||||
import type { CartInquiryCopy } from '@/application/messaging/whatsapp-message-builder';
|
||||
|
||||
/** WhatsApp inquiries are sent in Swedish or English only — never other UI languages. */
|
||||
export type WhatsAppInquiryLanguage = 'sv' | 'en';
|
||||
|
||||
export function getWhatsAppInquiryLanguage(language: Language): WhatsAppInquiryLanguage {
|
||||
return language === 'sv' ? 'sv' : 'en';
|
||||
}
|
||||
|
||||
export function getWhatsAppInquiryTranslation(language: Language): TranslationBundle {
|
||||
return getTranslation(getWhatsAppInquiryLanguage(language));
|
||||
}
|
||||
|
||||
export function buildCartInquiryCopy(t: TranslationBundle): CartInquiryCopy {
|
||||
return {
|
||||
pickupIntro: t.cartDrawer.inquiryPickupIntro,
|
||||
deliveryIntro: t.cartDrawer.inquiryDeliveryIntro,
|
||||
messageTotal: t.cartDrawer.messageTotal,
|
||||
messageName: t.cartDrawer.messageName,
|
||||
messageEmail: t.cartDrawer.messageEmail,
|
||||
messageBranch: t.cartDrawer.messageBranch,
|
||||
branchAskim: t.cartDrawer.branchAskim,
|
||||
branchBackaplan: t.cartDrawer.branchBackaplan,
|
||||
messageAddress: t.cartDrawer.messageAddress,
|
||||
messagePreferredDate: t.cartDrawer.messagePreferredDate,
|
||||
messagePreferredTime: t.cartDrawer.messagePreferredTime,
|
||||
scheduleDateToday: t.cartDrawer.dateToday,
|
||||
scheduleDateTomorrow: t.cartDrawer.dateTomorrow,
|
||||
deliverySwishNote: t.cartDrawer.deliverySwishNote,
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { DeliveryInquiryDetails, PickupInquiryDetails } from '@/domain/cart/inquiry';
|
||||
import type { DeliveryInquiryDetails, InquiryPreferredDate, PickupInquiryDetails } from '@/domain/cart/inquiry';
|
||||
import { hasInquirySchedule, isInquiryBranchOnlineEnabled } from '@/domain/cart/inquiry';
|
||||
import type { BookingDetails } from '@/domain/booking/entities';
|
||||
import { isBookingComplete } from '@/domain/booking/validation';
|
||||
import type { Language } from '@/domain/language/entities';
|
||||
@@ -16,9 +17,15 @@ export interface CartInquiryCopy {
|
||||
deliveryIntro: string;
|
||||
messageTotal: string;
|
||||
messageName: string;
|
||||
messagePhone: string;
|
||||
messageEmail: string;
|
||||
messageBranch: string;
|
||||
branchAskim: string;
|
||||
branchBackaplan: string;
|
||||
messageAddress: string;
|
||||
messagePreferredDate: string;
|
||||
messagePreferredTime: string;
|
||||
scheduleDateToday: string;
|
||||
scheduleDateTomorrow: string;
|
||||
deliverySwishNote: string;
|
||||
}
|
||||
|
||||
@@ -37,6 +44,26 @@ export interface BookingLocationCopy {
|
||||
backaplan: string;
|
||||
}
|
||||
|
||||
export interface BookingMessageCopy extends BookingLocationCopy {
|
||||
modeTable: string;
|
||||
modeEvent: string;
|
||||
eventLine: string;
|
||||
greeting: string;
|
||||
labelName: string;
|
||||
labelPhone: string;
|
||||
labelEmail: string;
|
||||
labelLocation: string;
|
||||
labelDate: string;
|
||||
labelTime: string;
|
||||
labelGuests: string;
|
||||
labelNotes: string;
|
||||
labelPreOrder: string;
|
||||
preOrderNone: string;
|
||||
preOrderTotal: string;
|
||||
confirmLine: string;
|
||||
thanksLine: string;
|
||||
}
|
||||
|
||||
export function getSuggestedPickupTime(language: Language, from: Date = new Date()): string {
|
||||
const suggested = new Date(from.getTime() + PICKUP_LEAD_TIME_MINUTES * 60 * 1000);
|
||||
const timeLocale =
|
||||
@@ -53,43 +80,71 @@ function localizeInquiryLines(lang: Language, items: OrderLine[]): string {
|
||||
);
|
||||
}
|
||||
|
||||
function resolveInquiryBranchLabel(
|
||||
branch: PickupInquiryDetails['branch'],
|
||||
copy: CartInquiryCopy,
|
||||
): string {
|
||||
return branch === 'askim' ? copy.branchAskim : copy.branchBackaplan;
|
||||
}
|
||||
|
||||
function resolveInquiryDateLabel(
|
||||
date: InquiryPreferredDate,
|
||||
copy: CartInquiryCopy,
|
||||
): string {
|
||||
return date === 'tomorrow' ? copy.scheduleDateTomorrow : copy.scheduleDateToday;
|
||||
}
|
||||
|
||||
function formatInquiryScheduleLine(details: PickupInquiryDetails, copy: CartInquiryCopy): string {
|
||||
if (!hasInquirySchedule(details)) return '';
|
||||
|
||||
return `${copy.messagePreferredDate}: ${resolveInquiryDateLabel(details.preferredDate, copy)}
|
||||
${copy.messagePreferredTime}: ${details.preferredTime.trim()}`;
|
||||
}
|
||||
|
||||
export function buildPickupInquiryMessage(
|
||||
items: OrderLine[],
|
||||
details: PickupInquiryDetails,
|
||||
copy: CartInquiryCopy,
|
||||
language: Language = 'en',
|
||||
language: Language = 'sv',
|
||||
): string | null {
|
||||
if (items.length === 0) return null;
|
||||
if (items.length === 0 || !isInquiryBranchOnlineEnabled(details.branch)) return null;
|
||||
|
||||
const lines = localizeInquiryLines(language, items);
|
||||
const total = calculateOrderTotal(items);
|
||||
|
||||
const scheduleLine = formatInquiryScheduleLine(details, copy);
|
||||
|
||||
return `${copy.pickupIntro}
|
||||
${lines}
|
||||
${copy.messageTotal}: ${total} kr
|
||||
${copy.messageBranch}: ${resolveInquiryBranchLabel(details.branch, copy)}
|
||||
${copy.messageName}: ${details.name.trim()}
|
||||
${copy.messagePhone}: ${details.phone.trim()}`;
|
||||
${copy.messageEmail}: ${details.email.trim()}${scheduleLine ? `\n${scheduleLine}` : ''}`;
|
||||
}
|
||||
|
||||
export function buildDeliveryInquiryMessage(
|
||||
items: OrderLine[],
|
||||
details: DeliveryInquiryDetails,
|
||||
copy: CartInquiryCopy,
|
||||
language: Language = 'en',
|
||||
language: Language = 'sv',
|
||||
): string | null {
|
||||
if (items.length === 0) return null;
|
||||
if (items.length === 0 || !isInquiryBranchOnlineEnabled(details.branch)) return null;
|
||||
|
||||
const lines = localizeInquiryLines(language, items);
|
||||
const total = calculateOrderTotal(items);
|
||||
|
||||
const scheduleLine = formatInquiryScheduleLine(details, copy);
|
||||
const addressLine = details.address.trim()
|
||||
? `${copy.messageAddress}: ${details.address.trim()}\n`
|
||||
: '';
|
||||
|
||||
return `${copy.deliveryIntro}
|
||||
${lines}
|
||||
${copy.messageTotal}: ${total} kr
|
||||
${copy.messageBranch}: ${resolveInquiryBranchLabel(details.branch, copy)}
|
||||
${copy.messageName}: ${details.name.trim()}
|
||||
${copy.messageAddress}: ${details.address.trim()}
|
||||
${copy.messagePhone}: ${details.phone.trim()}
|
||||
${copy.messagePreferredTime}: ${details.preferredTime.trim()}
|
||||
${copy.deliverySwishNote}`;
|
||||
${copy.messageEmail}: ${details.email.trim()}
|
||||
${addressLine}${scheduleLine ? `${scheduleLine}\n` : ''}${copy.deliverySwishNote}`;
|
||||
}
|
||||
|
||||
const PACKAGE_NAME_PLACEHOLDER = '{{package}}';
|
||||
@@ -101,40 +156,54 @@ export function buildCateringPackagePricingMessage(
|
||||
return template.split(PACKAGE_NAME_PLACEHOLDER).join(packageName);
|
||||
}
|
||||
|
||||
function resolveBookingEventLabel(booking: BookingDetails, eventLabel: string): string {
|
||||
if (booking.bookingMode !== 'event' || !booking.eventType) return '';
|
||||
if (booking.eventType === 'other') {
|
||||
return booking.eventTypeOther.trim() || eventLabel;
|
||||
}
|
||||
return eventLabel;
|
||||
}
|
||||
|
||||
export function buildBookingWhatsAppMessage(
|
||||
booking: BookingDetails,
|
||||
preOrder: OrderLine[],
|
||||
locationCopy: BookingLocationCopy
|
||||
copy: BookingMessageCopy,
|
||||
eventLabel = '',
|
||||
language: Language = 'sv',
|
||||
): string | null {
|
||||
if (!isBookingComplete(booking)) return null;
|
||||
|
||||
const locationLabel =
|
||||
booking.location === 'askim' ? locationCopy.askim : locationCopy.backaplan;
|
||||
booking.location === 'askim' ? copy.askim : copy.backaplan;
|
||||
const bookingModeLabel =
|
||||
booking.bookingMode === 'event' ? copy.modeEvent : copy.modeTable;
|
||||
const occasionLine = resolveBookingEventLabel(booking, eventLabel);
|
||||
const occasionText = occasionLine ? `\n${copy.eventLine}: ${occasionLine}` : '';
|
||||
|
||||
const itemsText =
|
||||
preOrder.length > 0
|
||||
? formatOrderLinesForMessage(preOrder).replace(/ — /g, ' — ')
|
||||
: 'None';
|
||||
? localizeInquiryLines(language, preOrder)
|
||||
: copy.preOrderNone;
|
||||
|
||||
const totalText =
|
||||
preOrder.length > 0 ? `\nPre-order total: ${calculateOrderTotal(preOrder)} kr\n` : '';
|
||||
preOrder.length > 0 ? `\n${copy.preOrderTotal}: ${calculateOrderTotal(preOrder)} kr\n` : '';
|
||||
|
||||
return `Hello Shahi Kitchen 👋
|
||||
return `${copy.greeting}
|
||||
|
||||
Table Reservation Inquiry:
|
||||
${bookingModeLabel}:
|
||||
|
||||
Name: ${booking.name}
|
||||
Phone: ${booking.phone}
|
||||
Email: ${booking.email || 'N/A'}
|
||||
Location: ${locationLabel}
|
||||
Date: ${booking.date}
|
||||
Time: ${booking.time}
|
||||
Guests: ${booking.guests}
|
||||
Special Requests: ${booking.notes || 'None'}
|
||||
${copy.labelName}: ${booking.name}
|
||||
${copy.labelPhone}: ${booking.phone}
|
||||
${copy.labelEmail}: ${booking.email || 'N/A'}
|
||||
${copy.labelLocation}: ${locationLabel}
|
||||
${copy.labelDate}: ${booking.date}
|
||||
${copy.labelTime}: ${booking.time}
|
||||
${copy.labelGuests}: ${booking.guests}${occasionText}
|
||||
${copy.labelNotes}: ${booking.notes || copy.preOrderNone}
|
||||
|
||||
Pre-ordered items:
|
||||
${copy.labelPreOrder}:
|
||||
${itemsText}${totalText}
|
||||
Please confirm table availability and pre-order.
|
||||
${copy.confirmLine}
|
||||
|
||||
Thank you!`;
|
||||
${copy.thanksLine}`;
|
||||
}
|
||||
Reference in New Issue
Block a user