Replace entire repo content with code from /root/shahikitchen-google/

This commit is contained in:
root
2026-07-03 02:51:15 +00:00
parent c8b7dc95e4
commit 8ccf033329
79 changed files with 7611 additions and 439 deletions
+33
View File
@@ -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}`;
}