From f4a3c8898af3f818366ecba2a31bacc824e15d9c Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Tue, 15 Oct 2024 10:14:08 +0200 Subject: [PATCH 01/62] DRY --- src/scripts/getTemplates.ts | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/scripts/getTemplates.ts b/src/scripts/getTemplates.ts index 2007b72..a8f7db4 100644 --- a/src/scripts/getTemplates.ts +++ b/src/scripts/getTemplates.ts @@ -23,9 +23,7 @@ export function getHelpPrivacyTemplate( }); } - const body = `Welcome to the Twilio booth! Message the {{0}} you would like and we'll start preparing it. The available options are:\n${indiciesOfFullTitles.join( - "\n", - )}`; + const body = `Welcome to the Twilio booth! Message the {{0}} you would like and we'll start preparing it. ${getAvailableOptions(indiciesOfFullTitles)}`; return { friendly_name: templateName, @@ -69,9 +67,7 @@ export function getWrongOrderTemplate( }); } - const body = `Seems like your order of "{{0}}" is not something we can serve. Possible orders are:\n${indiciesOfFullTitles.join( - "\n", - )}\nWrite "I need help" to get an overview of other commands.`; + const body = `Seems like your order of "{{0}}" is not something we can serve. ${getAvailableOptions(indiciesOfFullTitles)}Write "I need help" to get an overview of other commands.`; return { friendly_name: templateName, @@ -89,6 +85,14 @@ export function getWrongOrderTemplate( }, }; } + +const CONFIRMATION_VERIFIED_EMAIL = `Thank you! Your email address has been verified. `; +const ORDER_LIMITATION_NOTE = `PS: Every attendee can get up to {{0}} {{1}}.`; +function getAvailableOptions(indiciesOfFullTitles: string[]) { + return `What would you like? The options are:\n${indiciesOfFullTitles.join( + "\n", + )}\n`; +} export function getReadyToOrderTemplate( numOptions: number, templateName: string, @@ -114,9 +118,7 @@ export function getReadyToOrderTemplate( }); } - const body = `Thank you! Your email address has been verified. What would you like? The options are:\n${indiciesOfFullTitles.join( - "\n", - )}\nPS: Every attendee can get up to {{0}} {{1}}.`; + const body = `${CONFIRMATION_VERIFIED_EMAIL}${getAvailableOptions(indiciesOfFullTitles)}${ORDER_LIMITATION_NOTE}`; return { friendly_name: templateName, @@ -161,9 +163,7 @@ export function getReadyToOrderLimitlessTemplate( }); } - const body = `Thank you! Your email address has been verified. What would you like? The options are:\n${indiciesOfFullTitles.join( - "\n", - )}\n`; + const body = `${CONFIRMATION_VERIFIED_EMAIL}${getAvailableOptions(indiciesOfFullTitles)}`; return { friendly_name: templateName, @@ -207,10 +207,7 @@ export function getReadyToOrderWithoutEmailValidationTemplate( }); } - const body = `What would you like? The options are:\n${indiciesOfFullTitles.join( - "\n", - )}\nPS: Every attendee can get up to {{0}} {{1}}.`; - + const body = `${getAvailableOptions(indiciesOfFullTitles)}${ORDER_LIMITATION_NOTE}`; return { friendly_name: templateName, language: "en", @@ -254,9 +251,7 @@ export function getReadyToOrderLimitlessWithoutEmailValidationTemplate( }); } - const body = `What would you like? The options are:\n${indiciesOfFullTitles.join( - "\n", - )}\n`; + const body = getAvailableOptions(indiciesOfFullTitles); return { friendly_name: templateName, @@ -297,9 +292,7 @@ export function getEventRegistrationTemplate( }); } - const body = `Which event are you currently at? Please reply with the name of your event below. The options are:\n${indiciesOfFullTitles.join( - "\n", - )}\n`; + const body = `Which event are you currently at? Please reply with the name of your event below. ${getAvailableOptions(indiciesOfFullTitles)}`; return { friendly_name: templateName, From a89aea83f102a37511c732793d1ce84f796c4c7d Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Tue, 15 Oct 2024 11:46:48 +0200 Subject: [PATCH 02/62] stabilize --- src/scripts/createTwilioRes.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/scripts/createTwilioRes.ts b/src/scripts/createTwilioRes.ts index 4177b03..c239e67 100644 --- a/src/scripts/createTwilioRes.ts +++ b/src/scripts/createTwilioRes.ts @@ -16,6 +16,8 @@ import { } from "./getTemplates"; import { env } from "../../next.config"; +// this script runs mostly sequentially. Use a throttled queue later to optimize if needed + // @ts-ignore, defined in next.config.js const { CONTENT_PREFIX } = env; const { OVERRIDE_TEMPLATES } = process.env; @@ -34,7 +36,14 @@ async function createWhatsAppTemplates() { ); if (OVERRIDE_TEMPLATES) { - await Promise.all(templates.map((t) => deleteWhatsAppTemplate(t.sid))); + try { + for await (const t of templates) { + await deleteWhatsAppTemplate(t.sid); // Sequentially delete all templates to avoid rate limiting + } + + } catch (e: any) { + console.error("Error deleting WhatsApp Templates ", e.message); + } console.log(`Deleted ${templates.length} templates.`); templates = (await getAllWhatsAppTemplates()).filter((t) => t.friendly_name.startsWith(CONTENT_PREFIX), @@ -145,8 +154,8 @@ async function createWhatsAppTemplates() { console.log(`Created Template "${templateName}" ${template.sid}`); } } - } catch (e) { - console.error(e); + } catch (e: any) { + console.error("Error creating WhatsApp Templates ", e.message); } } export async function createTwilioRes() { From 2252f9ed59876028e36a0e5b4dd3ab18a84f008d Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Tue, 15 Oct 2024 11:47:14 +0200 Subject: [PATCH 03/62] Improve templates and add sample order --- src/app/webhooks/conversations/route.ts | 13 ++++--- src/lib/templates.ts | 47 ++++++------------------- src/scripts/getTemplates.ts | 15 ++++---- 3 files changed, 27 insertions(+), 48 deletions(-) diff --git a/src/app/webhooks/conversations/route.ts b/src/app/webhooks/conversations/route.ts index 7d7df05..c7a1ac2 100644 --- a/src/app/webhooks/conversations/route.ts +++ b/src/app/webhooks/conversations/route.ts @@ -90,10 +90,11 @@ export async function POST(request: Request) { const dataPolicy = templates.getDataPolicy(newEvent.selection.mode); addMessageToConversation(conversationSid, dataPolicy); const message = - await templates.getReadyToOrderWithoutEmailValidationMessage( + await templates.getReadyToOrderMessage( newEvent, newEvent.selection.items, newEvent.maxOrders, + true ); addMessageToConversation( conversationSid, @@ -136,10 +137,11 @@ export async function POST(request: Request) { const dataPolicy = templates.getDataPolicy(newEvent.selection.mode); addMessageToConversation(conversationSid, dataPolicy); const message = - await templates.getReadyToOrderWithoutEmailValidationMessage( + await templates.getReadyToOrderMessage( newEvent, newEvent.selection.items, newEvent.maxOrders, + true ); addMessageToConversation( conversationSid, @@ -183,10 +185,11 @@ export async function POST(request: Request) { ); addMessageToConversation(conversationSid, welcomeBackMessage); const message = - await templates.getReadyToOrderWithoutEmailValidationMessage( + await templates.getReadyToOrderMessage( newEvent, newEvent.selection.items, newEvent.maxOrders, + true ); await updateOrCreateSyncMapItem( NEXT_PUBLIC_ACTIVE_CUSTOMERS_MAP, @@ -233,10 +236,11 @@ export async function POST(request: Request) { await sleep(500); const message = - await templates.getReadyToOrderWithoutEmailValidationMessage( + await templates.getReadyToOrderMessage( newEvent, newEvent.selection.items, newEvent.maxOrders, + true ); addMessageToConversation( conversationSid, @@ -351,6 +355,7 @@ export async function POST(request: Request) { event, event.selection.items, event.maxOrders, + false ); addMessageToConversation( conversationSid, diff --git a/src/lib/templates.ts b/src/lib/templates.ts index 75f6387..462a833 100644 --- a/src/lib/templates.ts +++ b/src/lib/templates.ts @@ -181,46 +181,18 @@ export async function getReadyToOrderMessage( event: Event, availableOptions: any[], maxNumberOrders: number, + emailValidationSuffix: boolean, ) { const templates = await getTemplates(); - const { mode } = event.selection; - const variables = [ - maxNumberOrders, - modeToBeverage(mode, true), - ...availableOptions - .map((o) => [o.title, o.shortTitle, o.description]) - .flat(), - ]; - const contentVariables: any = {}; - variables.forEach((value, key) => { - contentVariables[key] = value; - }); - - - const limitess = maxNumberOrders >= 50 ? "_limitless" : ""; - - const templateName = `${SERVICE_INSTANCE_PREFIX.toLowerCase()}_ready_to_order${limitess}_${availableOptions.length}`; - const template = templates.find((t) => t.friendly_name === templateName); - - if (!template) { - throw new Error(`Template ${templateName} not found`); + const { mode, items, modifiers } = event.selection; + const maxOrders = `${maxNumberOrders} ${modeToBeverage(mode, true)}`; + let sampleOrder = items[1].title; + if (modifiers.length > 0) { + sampleOrder += ` with ${modifiers[0]}`; } - - return { - contentSid: template.sid, - contentVariables: JSON.stringify(contentVariables), - }; -} - -export async function getReadyToOrderWithoutEmailValidationMessage( - event: Event, - availableOptions: any[], - maxNumberOrders: number, -) { - const templates = await getTemplates(); - const { mode } = event.selection; const variables = [ - maxNumberOrders, + maxOrders, + sampleOrder, modeToBeverage(mode, true), ...availableOptions .map((o) => [o.title, o.shortTitle, o.description]) @@ -232,8 +204,9 @@ export async function getReadyToOrderWithoutEmailValidationMessage( }); const limitess = maxNumberOrders >= 50 ? "_limitless" : ""; + const emailSuffix = emailValidationSuffix ? "_without_email" : ""; - const templateName = `${SERVICE_INSTANCE_PREFIX.toLowerCase()}_ready_to_order${limitess}_without_email_${availableOptions.length}`; + const templateName = `${SERVICE_INSTANCE_PREFIX.toLowerCase()}_ready_to_order${limitess}${emailSuffix}_${availableOptions.length}`; const template = templates.find((t) => t.friendly_name === templateName); if (!template) { diff --git a/src/scripts/getTemplates.ts b/src/scripts/getTemplates.ts index a8f7db4..2a50eaf 100644 --- a/src/scripts/getTemplates.ts +++ b/src/scripts/getTemplates.ts @@ -86,13 +86,14 @@ export function getWrongOrderTemplate( }; } -const CONFIRMATION_VERIFIED_EMAIL = `Thank you! Your email address has been verified. `; -const ORDER_LIMITATION_NOTE = `PS: Every attendee can get up to {{0}} {{1}}.`; +const CONFIRMATION_VERIFIED_EMAIL = `Thank you! Your email address has been verified.`; function getAvailableOptions(indiciesOfFullTitles: string[]) { return `What would you like? The options are:\n${indiciesOfFullTitles.join( "\n", - )}\n`; + )}`; } +const SAMPLE_ORDER = `Or send a message containing your order, e.g. " {{1}}".`; +const ORDER_LIMITATION_NOTE = `\n\nPS: Every attendee can get up to {{0}}.`; export function getReadyToOrderTemplate( numOptions: number, templateName: string, @@ -118,7 +119,7 @@ export function getReadyToOrderTemplate( }); } - const body = `${CONFIRMATION_VERIFIED_EMAIL}${getAvailableOptions(indiciesOfFullTitles)}${ORDER_LIMITATION_NOTE}`; + const body = `${CONFIRMATION_VERIFIED_EMAIL} ${getAvailableOptions(indiciesOfFullTitles)}${ORDER_LIMITATION_NOTE}\n${SAMPLE_ORDER}`; return { friendly_name: templateName, @@ -163,7 +164,7 @@ export function getReadyToOrderLimitlessTemplate( }); } - const body = `${CONFIRMATION_VERIFIED_EMAIL}${getAvailableOptions(indiciesOfFullTitles)}`; + const body = `${CONFIRMATION_VERIFIED_EMAIL} ${getAvailableOptions(indiciesOfFullTitles)}\n${SAMPLE_ORDER}`; return { friendly_name: templateName, @@ -207,7 +208,7 @@ export function getReadyToOrderWithoutEmailValidationTemplate( }); } - const body = `${getAvailableOptions(indiciesOfFullTitles)}${ORDER_LIMITATION_NOTE}`; + const body = `${getAvailableOptions(indiciesOfFullTitles)}\n${SAMPLE_ORDER}${ORDER_LIMITATION_NOTE}`; return { friendly_name: templateName, language: "en", @@ -251,7 +252,7 @@ export function getReadyToOrderLimitlessWithoutEmailValidationTemplate( }); } - const body = getAvailableOptions(indiciesOfFullTitles); + const body = `${getAvailableOptions(indiciesOfFullTitles)}\n${SAMPLE_ORDER}`; return { friendly_name: templateName, From 379e19d540976027e1b2444b1a0e5f050a94ca6b Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Tue, 15 Oct 2024 12:00:58 +0200 Subject: [PATCH 04/62] chore: prettier --- CODE_OF_CONDUCT.md | 2 +- README.md | 54 +++++++++---------- __tests__/e2e/browse-orders.spec.ts | 12 ++--- src/app/api/[slug]/broadcast/route.ts | 6 +-- src/app/api/broadcast/route.ts | 41 +++++++------- src/app/api/event/[slug]/stats/route.ts | 2 +- .../event/[slug]/orders/[terminal]/page.tsx | 15 ++++-- src/app/event/[slug]/orders/ordersList.tsx | 7 +-- src/app/webhooks/conversations/route.ts | 54 +++++++++---------- src/components/menu-item.tsx | 2 +- src/config/spellingMap.ts | 2 +- src/instrumentation.ts | 15 +++--- src/lib/utils.ts | 6 +-- src/scripts/clearOrdersForEvent.ts | 4 +- src/scripts/createTwilioRes.ts | 1 - src/scripts/updateConfig.ts | 4 +- 16 files changed, 113 insertions(+), 114 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 596347e..2f0727e 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -70,4 +70,4 @@ members of the project's leadership. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html -[homepage]: https://www.contributor-covenant.org \ No newline at end of file +[homepage]: https://www.contributor-covenant.org diff --git a/README.md b/README.md index 7502dcd..8f9f058 100644 --- a/README.md +++ b/README.md @@ -8,32 +8,32 @@ Twilio Mixologist is an application that allows you to solve the problem of long queues at stands at events. Attendees can order their coffee, smoothie or whatever you serve via Twilio-powered channels, Mixologists get all orders on a website that can be accessed via a tablet and once an order is done the attendee will be notified via the system to come and pick it up. No more queueing and efficient coffee ☕️ ordering! 🎉 If you want to learn more about how this project was started, check out the this blog post: -> [Serving Coffee with Twilio Programmable SMS and React](https://www.twilio.com/en-us/blog/serving-coffee-with-sms-and-react-html) -Different versions of this system have been used at events such as: - -* [NDC Oslo](https://ndcoslo.com) 2016, 2017 -* [CSSConf EU](https://2017.cssconf.eu/) && [JSConf EU](https://2017.jsconf.eu/) 2017 -* [WeAreDevelopers World Congress](https://www.wearedevelopers.com/world-congress) 2023, 2024 -* [Mobile World Congress Barcelona](https://www.mwcbarcelona.com/) 2023, 2024 -* [Money 20/20](https://www.money2020.com/) 2023 -* [Twilio SIGNAL](https://signal.twilio.com/) 2023, 2024 +> [Serving Coffee with Twilio Programmable SMS and React](https://www.twilio.com/en-us/blog/serving-coffee-with-sms-and-react-html) +Different versions of this system have been used at events such as: +- [NDC Oslo](https://ndcoslo.com) 2016, 2017 +- [CSSConf EU](https://2017.cssconf.eu/) && [JSConf EU](https://2017.jsconf.eu/) 2017 +- [WeAreDevelopers World Congress](https://www.wearedevelopers.com/world-congress) 2023, 2024 +- [Mobile World Congress Barcelona](https://www.mwcbarcelona.com/) 2023, 2024 +- [Money 20/20](https://www.money2020.com/) 2023 +- [Twilio SIGNAL](https://signal.twilio.com/) 2023, 2024 ## Features -* Receive orders using [Twilio Messaging] -* Store orders and real-time synchronization them between back-end and front-end using [Twilio Sync] -* Easy dynamic application configuration using [Twilio Sync] -* Managing message threads using [Twilio Conversations] -* Permission management based on [Twilio Sync] -* Easy way to reset the application from the admin interface -* Support multiple events that happen in parallel -* Query for location in the queue as well as canceling the order as a user -* All combined into a single [NextJS](https://nextjs.org/) web application +- Receive orders using [Twilio Messaging] +- Store orders and real-time synchronization them between back-end and front-end using [Twilio Sync] +- Easy dynamic application configuration using [Twilio Sync] +- Managing message threads using [Twilio Conversations] +- Permission management based on [Twilio Sync] +- Easy way to reset the application from the admin interface +- Support multiple events that happen in parallel +- Query for location in the queue as well as canceling the order as a user +- All combined into a single [NextJS](https://nextjs.org/) web application ### Pending Features + - [ ] Integration with Segment - [ ] Your suggestions @@ -41,17 +41,16 @@ Different versions of this system have been used at events such as: The current [Twilio Channels] are: -* [WhatsApp][twilio whatsapp] -* [SMS][twilio messaging] - +- [WhatsApp][twilio whatsapp] +- [SMS][twilio messaging] ## Setup ### Requirements -* [Node.js] version 20 or higher -* [pnpm] -* A Twilio account - [Sign up here](https://www.twilio.com/try-twilio) +- [Node.js] version 20 or higher +- [pnpm] +- A Twilio account - [Sign up here](https://www.twilio.com/try-twilio) ## Setup @@ -136,13 +135,12 @@ All third party contributors acknowledge that any contributions they provide wil ## Icons Used -* [Mixologist Icons by Oliver Pitsch](https://www.smashingmagazine.com/2016/03/freebie-Mixologist-iconset-50-icons-eps-png-svg/) -* [Bar by BirVa Mehta from Noun Project](https://thenounproject.com/term/bar/1323725/) +- [Mixologist Icons by Oliver Pitsch](https://www.smashingmagazine.com/2016/03/freebie-Mixologist-iconset-50-icons-eps-png-svg/) +- [Bar by BirVa Mehta from Noun Project](https://thenounproject.com/term/bar/1323725/) ## License -MIT - +MIT [twilio console]: https://www.twilio.com/console [twilio rest api]: https://www.twilio.com/docs/api/rest diff --git a/__tests__/e2e/browse-orders.spec.ts b/__tests__/e2e/browse-orders.spec.ts index 60f96d7..1182940 100644 --- a/__tests__/e2e/browse-orders.spec.ts +++ b/__tests__/e2e/browse-orders.spec.ts @@ -165,13 +165,13 @@ test.describe("[mixologist]", () => { await page.goto("/event/test-event/orders"); - await page - .getByRole("button", { name: "Create a Manual Order" }) - .click(); + await page.getByRole("button", { name: "Create a Manual Order" }).click(); await page.getByPlaceholder("Attendee name").fill("Test Name"); await page.getByLabel("Order Item").click(); - await page.getByLabel('Espresso', { exact: true }).click(); - await page.getByPlaceholder("Without regular milk or similar...").fill("Test Notes"); + await page.getByLabel("Espresso", { exact: true }).click(); + await page + .getByPlaceholder("Without regular milk or similar...") + .fill("Test Notes"); await page .getByRole("button", { name: "Create Order", exact: true }) .click(); @@ -179,4 +179,4 @@ test.describe("[mixologist]", () => { page.getByRole("button", { name: "Creating...", exact: true }), ).toBeVisible(); }); -}); \ No newline at end of file +}); diff --git a/src/app/api/[slug]/broadcast/route.ts b/src/app/api/[slug]/broadcast/route.ts index 1461acc..64f824a 100644 --- a/src/app/api/[slug]/broadcast/route.ts +++ b/src/app/api/[slug]/broadcast/route.ts @@ -43,9 +43,9 @@ export async function POST( listItem.data?.status === "queued" || listItem.data?.status === "ready", ); - queuedOrders.forEach(order => { - addMessageToConversation(order.data.key, message) - }) + queuedOrders.forEach((order) => { + addMessageToConversation(order.data.key, message); + }); return new Response(null, { status: 201 }); } catch (e: any) { diff --git a/src/app/api/broadcast/route.ts b/src/app/api/broadcast/route.ts index a98e717..3d3355b 100644 --- a/src/app/api/broadcast/route.ts +++ b/src/app/api/broadcast/route.ts @@ -1,19 +1,18 @@ - - import { headers } from "next/headers"; -import { - fetchSyncListItems, - addMessageToConversation -} from "@/lib/twilio"; +import { fetchSyncListItems, addMessageToConversation } from "@/lib/twilio"; import { Privilege, getAuthenticatedRole } from "@/middleware"; export async function POST(request: Request) { -const headersList = headers(); - const role = getAuthenticatedRole(headersList.get("Authorization") || ""); + const headersList = headers(); + const role = getAuthenticatedRole(headersList.get("Authorization") || ""); - const hasPermissions = Privilege.ADMIN === role || Privilege.MIXOLOGIST === role; - if (!process.env.NEXT_PUBLIC_EVENTS_MAP || !process.env.NEXT_PUBLIC_ACTIVE_CUSTOMERS_MAP ) { + const hasPermissions = + Privilege.ADMIN === role || Privilege.MIXOLOGIST === role; + if ( + !process.env.NEXT_PUBLIC_EVENTS_MAP || + !process.env.NEXT_PUBLIC_ACTIVE_CUSTOMERS_MAP + ) { console.error("No config doc specified"); return new Response("No config doc specified", { status: 500, @@ -31,16 +30,20 @@ const headersList = headers(); ); } - const { event, message } = await request.json(); - try { - const listItems = await fetchSyncListItems(event); - const queuedOrders = listItems.filter(listItem => (listItem.data?.status === 'queued' || listItem.data?.status === 'ready') && !listItem.data?.manual); - - queuedOrders.forEach(order => { - addMessageToConversation(order.data.key, message); - }) - return new Response(null, {status: 201}) + const { event, message } = await request.json(); + try { + const listItems = await fetchSyncListItems(event); + const queuedOrders = listItems.filter( + (listItem) => + (listItem.data?.status === "queued" || + listItem.data?.status === "ready") && + !listItem.data?.manual, + ); + queuedOrders.forEach((order) => { + addMessageToConversation(order.data.key, message); + }); + return new Response(null, { status: 201 }); } catch (e: any) { console.error(e); return new Response(e.message, { status: 500, statusText: e.message }); diff --git a/src/app/api/event/[slug]/stats/route.ts b/src/app/api/event/[slug]/stats/route.ts index fd5abbe..bf60f11 100644 --- a/src/app/api/event/[slug]/stats/route.ts +++ b/src/app/api/event/[slug]/stats/route.ts @@ -8,7 +8,7 @@ export async function GET( request: Request, { params }: { params: { slug: string } }, ): Promise { - const headersList = headers(); + const headersList = headers(); const role = getAuthenticatedRole(headersList.get("Authorization") || ""); if (role !== Privilege.ADMIN) { diff --git a/src/app/event/[slug]/orders/[terminal]/page.tsx b/src/app/event/[slug]/orders/[terminal]/page.tsx index cda011d..6797f21 100644 --- a/src/app/event/[slug]/orders/[terminal]/page.tsx +++ b/src/app/event/[slug]/orders/[terminal]/page.tsx @@ -13,14 +13,23 @@ export default function TerminalPage({ const terminalId = Number(matchedGroups?.[1]), terminalCount = Number(matchedGroups?.[2]); - if (!terminalRegex.test(terminal) || terminalId > terminalCount || terminalId === 0 || isNaN(terminalId) || isNaN(terminalCount)) { + if ( + !terminalRegex.test(terminal) || + terminalId > terminalCount || + terminalId === 0 || + isNaN(terminalId) || + isNaN(terminalCount) + ) { return notFound(); } - return (
- +
); } diff --git a/src/app/event/[slug]/orders/ordersList.tsx b/src/app/event/[slug]/orders/ordersList.tsx index 65d3db4..21093f2 100644 --- a/src/app/event/[slug]/orders/ordersList.tsx +++ b/src/app/event/[slug]/orders/ordersList.tsx @@ -18,12 +18,7 @@ import { getOrderReadyReminderMessage, } from "@/lib/templates"; -import { - Check, - Trash2Icon, - BellRing, - UserCheck, -} from "lucide-react"; +import { Check, Trash2Icon, BellRing, UserCheck } from "lucide-react"; export default function OrdersList({ ordersList, diff --git a/src/app/webhooks/conversations/route.ts b/src/app/webhooks/conversations/route.ts index c7a1ac2..201e30e 100644 --- a/src/app/webhooks/conversations/route.ts +++ b/src/app/webhooks/conversations/route.ts @@ -89,13 +89,12 @@ export async function POST(request: Request) { await sleep(2000); const dataPolicy = templates.getDataPolicy(newEvent.selection.mode); addMessageToConversation(conversationSid, dataPolicy); - const message = - await templates.getReadyToOrderMessage( - newEvent, - newEvent.selection.items, - newEvent.maxOrders, - true - ); + const message = await templates.getReadyToOrderMessage( + newEvent, + newEvent.selection.items, + newEvent.maxOrders, + true, + ); addMessageToConversation( conversationSid, "", @@ -136,13 +135,12 @@ export async function POST(request: Request) { await sleep(2000); const dataPolicy = templates.getDataPolicy(newEvent.selection.mode); addMessageToConversation(conversationSid, dataPolicy); - const message = - await templates.getReadyToOrderMessage( - newEvent, - newEvent.selection.items, - newEvent.maxOrders, - true - ); + const message = await templates.getReadyToOrderMessage( + newEvent, + newEvent.selection.items, + newEvent.maxOrders, + true, + ); addMessageToConversation( conversationSid, "", @@ -184,13 +182,12 @@ export async function POST(request: Request) { newEvent.welcomeMessage, ); addMessageToConversation(conversationSid, welcomeBackMessage); - const message = - await templates.getReadyToOrderMessage( - newEvent, - newEvent.selection.items, - newEvent.maxOrders, - true - ); + const message = await templates.getReadyToOrderMessage( + newEvent, + newEvent.selection.items, + newEvent.maxOrders, + true, + ); await updateOrCreateSyncMapItem( NEXT_PUBLIC_ACTIVE_CUSTOMERS_MAP, conversationSid, @@ -235,13 +232,12 @@ export async function POST(request: Request) { ); await sleep(500); - const message = - await templates.getReadyToOrderMessage( - newEvent, - newEvent.selection.items, - newEvent.maxOrders, - true - ); + const message = await templates.getReadyToOrderMessage( + newEvent, + newEvent.selection.items, + newEvent.maxOrders, + true, + ); addMessageToConversation( conversationSid, "", @@ -355,7 +351,7 @@ export async function POST(request: Request) { event, event.selection.items, event.maxOrders, - false + false, ); addMessageToConversation( conversationSid, diff --git a/src/components/menu-item.tsx b/src/components/menu-item.tsx index 9bde4bf..bf69f85 100644 --- a/src/components/menu-item.tsx +++ b/src/components/menu-item.tsx @@ -124,7 +124,7 @@ export default function MenuItem({ className="m-2 mr-6" /> ), - "Chocolate": ( + Chocolate: ( b.shortTitle.length - a.shortTitle.length, ); for (const item in sortedItems) { - if ( - spellcheckedBody.includes( - sortedItems[item].shortTitle.toLowerCase(), - ) - ) { + if (spellcheckedBody.includes(sortedItems[item].shortTitle.toLowerCase())) { orderItem = sortedItems[item]; break; } diff --git a/src/scripts/clearOrdersForEvent.ts b/src/scripts/clearOrdersForEvent.ts index 9b5a421..4e9cb77 100644 --- a/src/scripts/clearOrdersForEvent.ts +++ b/src/scripts/clearOrdersForEvent.ts @@ -15,7 +15,9 @@ const client = twilio(TWILIO_API_KEY, TWILIO_API_SECRET, { const eventName = process.argv.pop(); if (!eventName || eventName.startsWith("/") || eventName.includes("=")) { - console.error("Please provide an event name as the last argument, e.g. 'pnpm clear-orders wearedevs24'"); + console.error( + "Please provide an event name as the last argument, e.g. 'pnpm clear-orders wearedevs24'", + ); process.exit(1); } diff --git a/src/scripts/createTwilioRes.ts b/src/scripts/createTwilioRes.ts index c239e67..8ab0716 100644 --- a/src/scripts/createTwilioRes.ts +++ b/src/scripts/createTwilioRes.ts @@ -40,7 +40,6 @@ async function createWhatsAppTemplates() { for await (const t of templates) { await deleteWhatsAppTemplate(t.sid); // Sequentially delete all templates to avoid rate limiting } - } catch (e: any) { console.error("Error deleting WhatsApp Templates ", e.message); } diff --git a/src/scripts/updateConfig.ts b/src/scripts/updateConfig.ts index 43706fe..0acf480 100644 --- a/src/scripts/updateConfig.ts +++ b/src/scripts/updateConfig.ts @@ -77,7 +77,7 @@ export async function updateConfig() { ); await configDoc.update({ data: newConfig }); } - + (async () => { - updateConfig() + updateConfig(); })(); From 21e2c2c990382b7db64bc72113399b01f4c049f6 Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Tue, 15 Oct 2024 12:03:34 +0200 Subject: [PATCH 05/62] fix template --- src/lib/templates.ts | 2 +- src/scripts/getTemplates.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/templates.ts b/src/lib/templates.ts index 462a833..78632f9 100644 --- a/src/lib/templates.ts +++ b/src/lib/templates.ts @@ -188,7 +188,7 @@ export async function getReadyToOrderMessage( const maxOrders = `${maxNumberOrders} ${modeToBeverage(mode, true)}`; let sampleOrder = items[1].title; if (modifiers.length > 0) { - sampleOrder += ` with ${modifiers[0]}`; + sampleOrder += ` with ${modifiers[modifiers.length - 1]}`; } const variables = [ maxOrders, diff --git a/src/scripts/getTemplates.ts b/src/scripts/getTemplates.ts index 2a50eaf..e736c1a 100644 --- a/src/scripts/getTemplates.ts +++ b/src/scripts/getTemplates.ts @@ -92,7 +92,7 @@ function getAvailableOptions(indiciesOfFullTitles: string[]) { "\n", )}`; } -const SAMPLE_ORDER = `Or send a message containing your order, e.g. " {{1}}".`; +const SAMPLE_ORDER = `Or send a message containing your order, e.g. "{{1}}".`; const ORDER_LIMITATION_NOTE = `\n\nPS: Every attendee can get up to {{0}}.`; export function getReadyToOrderTemplate( numOptions: number, @@ -119,7 +119,7 @@ export function getReadyToOrderTemplate( }); } - const body = `${CONFIRMATION_VERIFIED_EMAIL} ${getAvailableOptions(indiciesOfFullTitles)}${ORDER_LIMITATION_NOTE}\n${SAMPLE_ORDER}`; + const body = `${CONFIRMATION_VERIFIED_EMAIL} ${getAvailableOptions(indiciesOfFullTitles)}\n${SAMPLE_ORDER}${ORDER_LIMITATION_NOTE}`; return { friendly_name: templateName, From e570f42e8d00a0c119cd0bd8f9ee8b7a31bcc0ba Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Tue, 15 Oct 2024 12:13:08 +0200 Subject: [PATCH 06/62] remove unneeded var --- src/lib/templates.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/templates.ts b/src/lib/templates.ts index 78632f9..9bd8ba3 100644 --- a/src/lib/templates.ts +++ b/src/lib/templates.ts @@ -193,7 +193,6 @@ export async function getReadyToOrderMessage( const variables = [ maxOrders, sampleOrder, - modeToBeverage(mode, true), ...availableOptions .map((o) => [o.title, o.shortTitle, o.description]) .flat(), From be229cafb3c4ef381f3bba773e71bcadf3857d1a Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Tue, 15 Oct 2024 18:01:57 +0200 Subject: [PATCH 07/62] refactor for basic menu screen --- src/app/event/[slug]/menu/menu-item.tsx | 39 +++++ src/app/event/[slug]/menu/page.tsx | 50 +++++- src/components/icon-map.tsx | 45 +++++ src/components/menu-item.tsx | 216 ++---------------------- 4 files changed, 143 insertions(+), 207 deletions(-) create mode 100644 src/app/event/[slug]/menu/menu-item.tsx create mode 100644 src/components/icon-map.tsx diff --git a/src/app/event/[slug]/menu/menu-item.tsx b/src/app/event/[slug]/menu/menu-item.tsx new file mode 100644 index 0000000..78c063d --- /dev/null +++ b/src/app/event/[slug]/menu/menu-item.tsx @@ -0,0 +1,39 @@ +import iconMap from "@/components/icon-map"; +import { XIcon } from "lucide-react"; + +export default function MenuItem({ + title, + shortTitle, + description, +}: { + title: string; + shortTitle: string; + description: string; +}) { + const IconComponent = iconMap[shortTitle]; + return ( +
+ {IconComponent ? ( + + ) : ( + + )} +
+

{title}

+

+ {description} +

+
+
+ ); +} diff --git a/src/app/event/[slug]/menu/page.tsx b/src/app/event/[slug]/menu/page.tsx index 1389adc..4a5a9ef 100644 --- a/src/app/event/[slug]/menu/page.tsx +++ b/src/app/event/[slug]/menu/page.tsx @@ -1,7 +1,55 @@ "use client"; +import MenuItem from "./menu-item"; +import { Event } from "../page"; +import { useSyncMap } from "@/provider/syncProvider"; +import { useEffect, useState } from "react"; + function MenuPage({ params }: { params: { slug: string } }) { - return {`Menu for ${params.slug}`}; + const [eventsMap, _, mapInitialized] = useSyncMap( + process.env.NEXT_PUBLIC_EVENTS_MAP || "", + [params.slug], + ); + + useEffect(() => { + if (mapInitialized) { + // @ts-ignore // TODO fix this TS issue + const existingEvent = eventsMap?.get(params.slug) as Event; + updateInternalEvent(existingEvent); + } + }, [eventsMap, mapInitialized]); + + const [internalEvent, updateInternalEvent] = useState(); + + if (!internalEvent || !internalEvent.selection) { + return
Loading...
; + } + + return ( + //
+ //
+ //

Mixologist

+ //

SEND YOUR ORDER TO +1-866-866-5302

+ //
+ //
+
+
+
+ {internalEvent.selection?.items.map((item: any, index: Number) => ( +
+
+ +
+
+ ))} +
+
+
+ ); } export default MenuPage; diff --git a/src/components/icon-map.tsx b/src/components/icon-map.tsx new file mode 100644 index 0000000..0060c73 --- /dev/null +++ b/src/components/icon-map.tsx @@ -0,0 +1,45 @@ +import { + AmericanoIcon, + SmoothieOrangeIcon, + SmoothiePineappleIcon, + SmoothieStrawberryIcon, + EspressoDopioIcon, + EspressoIcon, + LatteMacchiatoIcon, + FlatWhiteIcon, + EspressoMacchiatoIcon, + EspressoMacchiatoIcon2, + CappuccinoIcon, + CoffeeCupIcon, + CupIcon, + CoffeeIcon, + } from "@/components/icons"; + + const iconMap: { [key: string]: any } = { + "Double Espresso": EspressoDopioIcon, + Espresso: EspressoIcon, + Americano: AmericanoIcon, + "Latte Macchiato": LatteMacchiatoIcon, + "Caffè Latte": LatteMacchiatoIcon, + "Flat White": FlatWhiteIcon, + Macchiato: EspressoMacchiatoIcon, + "Double Macchiato": EspressoMacchiatoIcon2, + Cappuccino: CappuccinoIcon, + "Espresso Macchiato": EspressoMacchiatoIcon2, + Coffee: CoffeeIcon, + Matcha: CoffeeCupIcon, + Chocolate: CoffeeCupIcon, + Mocha: CupIcon, + "Black Tea": CupIcon, + "British Breakfast Tea": CupIcon, + "Apple Chamomile": CupIcon, + "Earl Grey": CupIcon, + "Herbal Tea": CupIcon, + Chai: CupIcon, + Colombia: SmoothieStrawberryIcon, + Aquamarine: SmoothiePineappleIcon, + Lambada: SmoothieOrangeIcon, + }; + + export default iconMap; + \ No newline at end of file diff --git a/src/components/menu-item.tsx b/src/components/menu-item.tsx index bf69f85..06a9cda 100644 --- a/src/components/menu-item.tsx +++ b/src/components/menu-item.tsx @@ -1,21 +1,5 @@ import { XIcon } from "lucide-react"; -import { - AmericanoIcon, - SmoothieOrangeIcon, - SmoothiePineappleIcon, - SmoothieStrawberryIcon, - SmoothieBlenderIcon, - EspressoDopioIcon, - EspressoIcon, - LatteMacchiatoIcon, - FlatWhiteIcon, - EspressoMacchiatoIcon, - EspressoMacchiatoIcon2, - CappuccinoIcon, - CoffeeCupIcon, - CupIcon, - CoffeeIcon, -} from "@/components/icons"; +import iconMap from "./icon-map"; export default function MenuItem({ title, @@ -26,197 +10,18 @@ export default function MenuItem({ shortTitle: string; description: string; }) { - const iconMap: { [key: string]: JSX.Element } = { - "Double Espresso": ( - - ), - Espresso: ( - - ), - Americano: ( - - ), - "Latte Macchiato": ( - - ), - "Caffè Latte": ( - - ), - "Flat White": ( - - ), - - Macchiato: ( - - ), - "Double Macchiato": ( - - ), - Cappuccino: ( - - ), - "Espresso Macchiato": ( - - ), - Coffee: ( - - ), - Matcha: ( - - ), - Chocolate: ( - - ), - Mocha: ( - - ), - "Black Tea": ( - - ), - "British Breakfast Tea": ( - - ), - "Apple Chamomile": ( - - ), - "Earl Grey": ( - - ), - "Herbal Tea": ( - - ), - Chai: ( - - ), - Colombia: ( - - ), - Aquamarine: ( - - ), - Lambada: ( - - ), - }; + const IconComponent = iconMap[shortTitle]; return (
- {iconMap[shortTitle] || ( + {IconComponent ? ( + + ) : (

{title}

-

{`Short title: ${shortTitle}`}

{description}

From cab3e8e0fc302a88898e7ee62eda1b9dbc5857ea Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Tue, 15 Oct 2024 18:32:58 +0200 Subject: [PATCH 08/62] shift layouts --- __tests__/scripts/orderExtractor.test.ts | 2 +- .../event/[slug]/menu/menu-item.tsx | 0 .../event/[slug]/menu/page.tsx | 4 +- src/app/(layout-free)/layout.tsx | 16 +++++ .../configuration/layout.tsx | 0 .../configuration/page.tsx | 0 .../event/[slug]/layout.tsx | 0 .../event/[slug]/orders/[terminal]/page.tsx | 0 .../orders/broadcast-popover-content.tsx | 0 .../orders/custom-order-popover-content.tsx | 2 +- .../event/[slug]/orders/headerControls.tsx | 2 +- .../event/[slug]/orders/ordersInterface.tsx | 2 +- .../event/[slug]/orders/ordersList.tsx | 2 +- .../event/[slug]/orders/page.tsx | 0 .../event/[slug]/page.tsx | 0 .../event/[slug]/qr-code-popovercontent.tsx | 0 .../event/[slug]/stats/countriesChart.tsx | 0 .../event/[slug]/stats/funnelChart.tsx | 0 .../event/[slug]/stats/ordersChart.tsx | 0 .../event/[slug]/stats/page.tsx | 0 src/app/(master-layout)/layout.tsx | 63 +++++++++++++++++++ src/app/{ => (master-layout)}/login/page.tsx | 0 src/app/{ => (master-layout)}/logout/route.ts | 0 src/app/{ => (master-layout)}/page.tsx | 0 src/app/{ => (master-layout)}/sync/layout.tsx | 0 src/app/{ => (master-layout)}/sync/page.tsx | 0 src/app/api/event/[slug]/stats/helper.ts | 2 +- src/app/layout.tsx | 46 +------------- src/app/testtest.tsx | 0 src/lib/templates.ts | 2 +- src/lib/utils.ts | 2 +- 31 files changed, 90 insertions(+), 55 deletions(-) rename src/app/{ => (layout-free)}/event/[slug]/menu/menu-item.tsx (100%) rename src/app/{ => (layout-free)}/event/[slug]/menu/page.tsx (92%) create mode 100644 src/app/(layout-free)/layout.tsx rename src/app/{ => (master-layout)}/configuration/layout.tsx (100%) rename src/app/{ => (master-layout)}/configuration/page.tsx (100%) rename src/app/{ => (master-layout)}/event/[slug]/layout.tsx (100%) rename src/app/{ => (master-layout)}/event/[slug]/orders/[terminal]/page.tsx (100%) rename src/app/{ => (master-layout)}/event/[slug]/orders/broadcast-popover-content.tsx (100%) rename src/app/{ => (master-layout)}/event/[slug]/orders/custom-order-popover-content.tsx (98%) rename src/app/{ => (master-layout)}/event/[slug]/orders/headerControls.tsx (98%) rename src/app/{ => (master-layout)}/event/[slug]/orders/ordersInterface.tsx (97%) rename src/app/{ => (master-layout)}/event/[slug]/orders/ordersList.tsx (99%) rename src/app/{ => (master-layout)}/event/[slug]/orders/page.tsx (100%) rename src/app/{ => (master-layout)}/event/[slug]/page.tsx (100%) rename src/app/{ => (master-layout)}/event/[slug]/qr-code-popovercontent.tsx (100%) rename src/app/{ => (master-layout)}/event/[slug]/stats/countriesChart.tsx (100%) rename src/app/{ => (master-layout)}/event/[slug]/stats/funnelChart.tsx (100%) rename src/app/{ => (master-layout)}/event/[slug]/stats/ordersChart.tsx (100%) rename src/app/{ => (master-layout)}/event/[slug]/stats/page.tsx (100%) create mode 100644 src/app/(master-layout)/layout.tsx rename src/app/{ => (master-layout)}/login/page.tsx (100%) rename src/app/{ => (master-layout)}/logout/route.ts (100%) rename src/app/{ => (master-layout)}/page.tsx (100%) rename src/app/{ => (master-layout)}/sync/layout.tsx (100%) rename src/app/{ => (master-layout)}/sync/page.tsx (100%) delete mode 100644 src/app/testtest.tsx diff --git a/__tests__/scripts/orderExtractor.test.ts b/__tests__/scripts/orderExtractor.test.ts index eec13e9..2a10421 100644 --- a/__tests__/scripts/orderExtractor.test.ts +++ b/__tests__/scripts/orderExtractor.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; import { getOrderItemFromMessage } from "@/lib/utils"; -import { Event } from "@/app/event/[slug]/page"; +import { Event } from "@/app/(master-layout)/event/[slug]/page"; const mockedEvent: Event = { name: "test event", diff --git a/src/app/event/[slug]/menu/menu-item.tsx b/src/app/(layout-free)/event/[slug]/menu/menu-item.tsx similarity index 100% rename from src/app/event/[slug]/menu/menu-item.tsx rename to src/app/(layout-free)/event/[slug]/menu/menu-item.tsx diff --git a/src/app/event/[slug]/menu/page.tsx b/src/app/(layout-free)/event/[slug]/menu/page.tsx similarity index 92% rename from src/app/event/[slug]/menu/page.tsx rename to src/app/(layout-free)/event/[slug]/menu/page.tsx index 4a5a9ef..e26dae5 100644 --- a/src/app/event/[slug]/menu/page.tsx +++ b/src/app/(layout-free)/event/[slug]/menu/page.tsx @@ -1,7 +1,7 @@ "use client"; +import { Event } from "@/app/(master-layout)/event/[slug]/page"; import MenuItem from "./menu-item"; -import { Event } from "../page"; import { useSyncMap } from "@/provider/syncProvider"; import { useEffect, useState } from "react"; @@ -32,7 +32,7 @@ function MenuPage({ params }: { params: { slug: string } }) { //

SEND YOUR ORDER TO +1-866-866-5302

//
// -
+
{internalEvent.selection?.items.map((item: any, index: Number) => ( diff --git a/src/app/(layout-free)/layout.tsx b/src/app/(layout-free)/layout.tsx new file mode 100644 index 0000000..a14e64f --- /dev/null +++ b/src/app/(layout-free)/layout.tsx @@ -0,0 +1,16 @@ +export const metadata = { + title: 'Next.js', + description: 'Generated by Next.js', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/src/app/configuration/layout.tsx b/src/app/(master-layout)/configuration/layout.tsx similarity index 100% rename from src/app/configuration/layout.tsx rename to src/app/(master-layout)/configuration/layout.tsx diff --git a/src/app/configuration/page.tsx b/src/app/(master-layout)/configuration/page.tsx similarity index 100% rename from src/app/configuration/page.tsx rename to src/app/(master-layout)/configuration/page.tsx diff --git a/src/app/event/[slug]/layout.tsx b/src/app/(master-layout)/event/[slug]/layout.tsx similarity index 100% rename from src/app/event/[slug]/layout.tsx rename to src/app/(master-layout)/event/[slug]/layout.tsx diff --git a/src/app/event/[slug]/orders/[terminal]/page.tsx b/src/app/(master-layout)/event/[slug]/orders/[terminal]/page.tsx similarity index 100% rename from src/app/event/[slug]/orders/[terminal]/page.tsx rename to src/app/(master-layout)/event/[slug]/orders/[terminal]/page.tsx diff --git a/src/app/event/[slug]/orders/broadcast-popover-content.tsx b/src/app/(master-layout)/event/[slug]/orders/broadcast-popover-content.tsx similarity index 100% rename from src/app/event/[slug]/orders/broadcast-popover-content.tsx rename to src/app/(master-layout)/event/[slug]/orders/broadcast-popover-content.tsx diff --git a/src/app/event/[slug]/orders/custom-order-popover-content.tsx b/src/app/(master-layout)/event/[slug]/orders/custom-order-popover-content.tsx similarity index 98% rename from src/app/event/[slug]/orders/custom-order-popover-content.tsx rename to src/app/(master-layout)/event/[slug]/orders/custom-order-popover-content.tsx index 2220f1f..016fa98 100644 --- a/src/app/event/[slug]/orders/custom-order-popover-content.tsx +++ b/src/app/(master-layout)/event/[slug]/orders/custom-order-popover-content.tsx @@ -2,7 +2,7 @@ import { PopoverContent } from "@/components/ui/popover"; import { Textarea } from "@/components/ui/text-area"; import { Button } from "@/components/ui/button"; import { useState } from "react"; -import { Selection } from "../../../../components/menu-select"; +import { Selection } from "../../../../../components/menu-select"; import { Select, SelectContent, diff --git a/src/app/event/[slug]/orders/headerControls.tsx b/src/app/(master-layout)/event/[slug]/orders/headerControls.tsx similarity index 98% rename from src/app/event/[slug]/orders/headerControls.tsx rename to src/app/(master-layout)/event/[slug]/orders/headerControls.tsx index c442e5f..6302c43 100644 --- a/src/app/event/[slug]/orders/headerControls.tsx +++ b/src/app/(master-layout)/event/[slug]/orders/headerControls.tsx @@ -10,7 +10,7 @@ import { Popover, PopoverTrigger } from "@/components/ui/popover"; import BroadcastPopoverContent from "./broadcast-popover-content"; import CustomOrderPopoverContent from "./custom-order-popover-content"; -import { Event } from "@/app/event/[slug]/page"; +import { Event } from "@/app/(master-layout)/event/[slug]/page"; import { EventState } from "@/lib/utils"; import { diff --git a/src/app/event/[slug]/orders/ordersInterface.tsx b/src/app/(master-layout)/event/[slug]/orders/ordersInterface.tsx similarity index 97% rename from src/app/event/[slug]/orders/ordersInterface.tsx rename to src/app/(master-layout)/event/[slug]/orders/ordersInterface.tsx index 55a57a0..58dcbd1 100644 --- a/src/app/event/[slug]/orders/ordersInterface.tsx +++ b/src/app/(master-layout)/event/[slug]/orders/ordersInterface.tsx @@ -2,7 +2,7 @@ import { useSyncList, useSyncMap } from "@/provider/syncProvider"; -import { Event } from "@/app/event/[slug]/page"; +import { Event } from "@/app/(master-layout)/event/[slug]/page"; import { EventState } from "@/lib/utils"; import { useToast } from "@/components/ui/use-toast"; import HeaderControls from "./headerControls"; diff --git a/src/app/event/[slug]/orders/ordersList.tsx b/src/app/(master-layout)/event/[slug]/orders/ordersList.tsx similarity index 99% rename from src/app/event/[slug]/orders/ordersList.tsx rename to src/app/(master-layout)/event/[slug]/orders/ordersList.tsx index 21093f2..aa4a429 100644 --- a/src/app/event/[slug]/orders/ordersList.tsx +++ b/src/app/(master-layout)/event/[slug]/orders/ordersList.tsx @@ -10,7 +10,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { useToast } from "@/components/ui/use-toast"; -import { Event } from "@/app/event/[slug]/page"; +import { Event } from "@/app/(master-layout)/event/[slug]/page"; import { getOrderCancelledMessage, diff --git a/src/app/event/[slug]/orders/page.tsx b/src/app/(master-layout)/event/[slug]/orders/page.tsx similarity index 100% rename from src/app/event/[slug]/orders/page.tsx rename to src/app/(master-layout)/event/[slug]/orders/page.tsx diff --git a/src/app/event/[slug]/page.tsx b/src/app/(master-layout)/event/[slug]/page.tsx similarity index 100% rename from src/app/event/[slug]/page.tsx rename to src/app/(master-layout)/event/[slug]/page.tsx diff --git a/src/app/event/[slug]/qr-code-popovercontent.tsx b/src/app/(master-layout)/event/[slug]/qr-code-popovercontent.tsx similarity index 100% rename from src/app/event/[slug]/qr-code-popovercontent.tsx rename to src/app/(master-layout)/event/[slug]/qr-code-popovercontent.tsx diff --git a/src/app/event/[slug]/stats/countriesChart.tsx b/src/app/(master-layout)/event/[slug]/stats/countriesChart.tsx similarity index 100% rename from src/app/event/[slug]/stats/countriesChart.tsx rename to src/app/(master-layout)/event/[slug]/stats/countriesChart.tsx diff --git a/src/app/event/[slug]/stats/funnelChart.tsx b/src/app/(master-layout)/event/[slug]/stats/funnelChart.tsx similarity index 100% rename from src/app/event/[slug]/stats/funnelChart.tsx rename to src/app/(master-layout)/event/[slug]/stats/funnelChart.tsx diff --git a/src/app/event/[slug]/stats/ordersChart.tsx b/src/app/(master-layout)/event/[slug]/stats/ordersChart.tsx similarity index 100% rename from src/app/event/[slug]/stats/ordersChart.tsx rename to src/app/(master-layout)/event/[slug]/stats/ordersChart.tsx diff --git a/src/app/event/[slug]/stats/page.tsx b/src/app/(master-layout)/event/[slug]/stats/page.tsx similarity index 100% rename from src/app/event/[slug]/stats/page.tsx rename to src/app/(master-layout)/event/[slug]/stats/page.tsx diff --git a/src/app/(master-layout)/layout.tsx b/src/app/(master-layout)/layout.tsx new file mode 100644 index 0000000..7b69173 --- /dev/null +++ b/src/app/(master-layout)/layout.tsx @@ -0,0 +1,63 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { cookies } from "next/headers"; +import { Privilege } from "@/middleware"; +import { Toaster } from "@/components/ui/toaster"; + +import { SettingsIcon } from "lucide-react"; +import { Inter } from "next/font/google"; +import SessionButton from "@/components/session-button"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Twilio Mixologist", + description: "Get a free beverage from the Twilio Mixologist", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + const cookiesStore = cookies(); + const isAdmin = + (cookiesStore.get("privilege")?.value as Privilege) === Privilege.ADMIN; + return ( + <> + +
+
+
+ +

+ Twilio Mixologist +

+ + +
+
+
+ {children} +
+
+

Made with ❤️ by Twilio

+
+
+ + ); +} diff --git a/src/app/login/page.tsx b/src/app/(master-layout)/login/page.tsx similarity index 100% rename from src/app/login/page.tsx rename to src/app/(master-layout)/login/page.tsx diff --git a/src/app/logout/route.ts b/src/app/(master-layout)/logout/route.ts similarity index 100% rename from src/app/logout/route.ts rename to src/app/(master-layout)/logout/route.ts diff --git a/src/app/page.tsx b/src/app/(master-layout)/page.tsx similarity index 100% rename from src/app/page.tsx rename to src/app/(master-layout)/page.tsx diff --git a/src/app/sync/layout.tsx b/src/app/(master-layout)/sync/layout.tsx similarity index 100% rename from src/app/sync/layout.tsx rename to src/app/(master-layout)/sync/layout.tsx diff --git a/src/app/sync/page.tsx b/src/app/(master-layout)/sync/page.tsx similarity index 100% rename from src/app/sync/page.tsx rename to src/app/(master-layout)/sync/page.tsx diff --git a/src/app/api/event/[slug]/stats/helper.ts b/src/app/api/event/[slug]/stats/helper.ts index 08af2e0..06b1216 100644 --- a/src/app/api/event/[slug]/stats/helper.ts +++ b/src/app/api/event/[slug]/stats/helper.ts @@ -2,7 +2,7 @@ import { fetchSyncListItems, findSyncMapItems } from "@/lib/twilio"; import { Stages } from "@/lib/utils"; -import { Event } from "@/app/event/[slug]/page"; +import { Event } from "@/app/(master-layout)/event/[slug]/page"; import { modes } from "@/config/menus"; import { headers } from "next/headers"; import { getAuthenticatedRole, Privilege } from "@/middleware"; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 790d79b..ce4922b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,13 +1,6 @@ import type { Metadata } from "next"; -import Link from "next/link"; -import { cookies } from "next/headers"; -import { Privilege } from "@/middleware"; -import { Toaster } from "@/components/ui/toaster"; - -import { SettingsIcon } from "lucide-react"; import { Inter } from "next/font/google"; import "./globals.css"; -import SessionButton from "@/components/session-button"; const inter = Inter({ subsets: ["latin"] }); @@ -21,47 +14,10 @@ export default function RootLayout({ }: { children: React.ReactNode; }) { - const cookiesStore = cookies(); - const isAdmin = - (cookiesStore.get("privilege")?.value as Privilege) === Privilege.ADMIN; return ( - - -
-
-
- -

- Twilio Mixologist -

- - -
-
-
- {children} -
-
-

Made with ❤️ by Twilio

-
-
- + {children} ); } diff --git a/src/app/testtest.tsx b/src/app/testtest.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/lib/templates.ts b/src/lib/templates.ts index 9bd8ba3..55f3f96 100644 --- a/src/lib/templates.ts +++ b/src/lib/templates.ts @@ -1,5 +1,5 @@ const axios = require("axios"); -import { Event } from "@/app/event/[slug]/page"; +import { Event } from "@/app/(master-layout)/event/[slug]/page"; import { modes } from "@/config/menus"; const { SERVICE_INSTANCE_PREFIX = "" } = process.env; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index adf735d..8e3848c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -2,7 +2,7 @@ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; import spellingMap from "@/config/spellingMap"; import { MenuItem } from "@/config/menus"; -import { Event } from "@/app/event/[slug]/page"; +import { Event } from "@/app/(master-layout)/event/[slug]/page"; import { ICountry, countries } from "countries-list"; import { PhoneNumberUtil } from "google-libphonenumber"; From 8f12c9187cd88de786c27f853ee1eaccaf026534 Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Wed, 16 Oct 2024 12:29:14 +0200 Subject: [PATCH 09/62] Add menu page draft --- public/next.svg | 1 - public/placeholder-user.jpg | Bin 1635 -> 0 bytes public/twilio.svg | 3 + public/vercel.svg | 1 - .../event/[slug]/menu/TwilioLogo.tsx | 55 ++++++++++++++++++ .../event/[slug]/menu/header.tsx | 23 ++++++++ .../event/[slug]/menu/layout.tsx | 9 +++ .../event/[slug]/menu/menu-item.tsx | 4 +- .../(layout-free)/event/[slug]/menu/page.tsx | 23 ++++---- src/app/(layout-free)/layout.tsx | 16 ----- src/app/(master-layout)/layout.tsx | 4 +- tailwind.config.ts | 5 ++ 12 files changed, 110 insertions(+), 34 deletions(-) delete mode 100644 public/next.svg delete mode 100644 public/placeholder-user.jpg create mode 100644 public/twilio.svg delete mode 100644 public/vercel.svg create mode 100644 src/app/(layout-free)/event/[slug]/menu/TwilioLogo.tsx create mode 100644 src/app/(layout-free)/event/[slug]/menu/header.tsx create mode 100644 src/app/(layout-free)/event/[slug]/menu/layout.tsx delete mode 100644 src/app/(layout-free)/layout.tsx diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/placeholder-user.jpg b/public/placeholder-user.jpg deleted file mode 100644 index 6fa7543d38ed8fdb38f03b5c0f40ad2d66827d4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1635 zcmex=cRarq{jAqGJXh7$}Ym>C5bm;@P_1sVSzVUPwol9>?*7~p`Fjh%&&i5Vyo`r#xP0%qgXfnHy zn7E>$Lug{-LIb0a!ifhj3W%7RB^MNLx}>0_tP&7BY4IaqjBo(siP4_n_ky5FEmL(` zmhdc@Jb`OxUUPQK!#sq(C`ZJd3YCA-U zh)gnS;tOzhJ@#Yf$SRGl2DVF#R;SL@d2ijhdh5m?{S3~XPpnp*$)2k2D4JJuYTMD;n0K36=Y8OJ z-mS1ZZC1 z49BdqoLoM_v$9v)Hm?5W<`Stp{n4{)u7wjb{~fvi z!T;47&))dGMoGtVj(5+GENxwrz4z{INB>V}1-4JUxp@DgJ8LKAUAQWhoR~X5%Wp!~ z=~v%IB`XWv>j>XTei(%h-3q0fm1bY5udCHKmu^f&G@&$==-St!-gGk?J$~inXs)aj#q07WR4- zM=rxkFUy8C!QJtD_bgKux*|B+dhW6{-z2Xe?@~0Ks(ijMro-3a&Mj#N$xpGBRnmWt zTsUxz-_bhtPjF={K-%Z^57b<}UkWR&Lo)qWhnrV2yD?(SL?Q-l9d; zHS6^j9!_Ejaqqemoyn}=@ptvvS-S7I7N7FZ5}(WNv3l;#-@XTLOmcJGRz1a7FhcBC z-nPY(UsETGE4&Rdme=pC+jj2j;!j+6o-!m}7oD?jz2dGcAvK4%qSmfE=U+z7Z+`#j z$Mx4a@1yJ$R+zt@y0z(yW_ISH^4k|<^!Al@?-M=r`gz3ehXgqVyM1>}k-Q}GF<-;) z*LLGyVd)Qk|Ji$(J?dTY-07-((_=n_`3T&d5FiH|guzja;wny>q}tw+C34*a{8 zfJ9i0DbumfeTVp1E#|(wudjdSJE%AnL@H&$nSBB{^D;34Gp|fN9u>e68m!_b0O__- AD*ylh diff --git a/public/twilio.svg b/public/twilio.svg new file mode 100644 index 0000000..1c8291d --- /dev/null +++ b/public/twilio.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index d2f8422..0000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/app/(layout-free)/event/[slug]/menu/TwilioLogo.tsx b/src/app/(layout-free)/event/[slug]/menu/TwilioLogo.tsx new file mode 100644 index 0000000..a721c80 --- /dev/null +++ b/src/app/(layout-free)/event/[slug]/menu/TwilioLogo.tsx @@ -0,0 +1,55 @@ +import { h, Component } from 'preact'; + +export default class TwilioLogo extends Component { + render({ fullLogo }) { + this.props.color = this.props.color || 'rgb(241, 46, 69)'; + this.props.width = this.props.width || 50; + this.props.x = this.props.x || 0; + this.props.y = this.props.y || 0; + return fullLogo + ? this.renderFullLogo(this.props) + : this.renderSmallLogo(this.props); + } + + renderSmallLogo({ width, x, y, color }) { + return ( + + + + + + + + + + ); + } + + renderFullLogo({ width, height, x, y, color }) { + return ( + + + + + + + + + + ); + } +} \ No newline at end of file diff --git a/src/app/(layout-free)/event/[slug]/menu/header.tsx b/src/app/(layout-free)/event/[slug]/menu/header.tsx new file mode 100644 index 0000000..488f66c --- /dev/null +++ b/src/app/(layout-free)/event/[slug]/menu/header.tsx @@ -0,0 +1,23 @@ +import { CoffeeShopIcon } from "@/components/icons"; + +export default function Header({ number }: { number: string }) { + return ( +
+
+ +
+ +

Mixologist

+ + {/*

SEND YOUR ORDER TO {number}

*/} + + + + ); +} diff --git a/src/app/(layout-free)/event/[slug]/menu/layout.tsx b/src/app/(layout-free)/event/[slug]/menu/layout.tsx new file mode 100644 index 0000000..3b13dc4 --- /dev/null +++ b/src/app/(layout-free)/event/[slug]/menu/layout.tsx @@ -0,0 +1,9 @@ +import SyncProvider from "@/provider/syncProvider"; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} diff --git a/src/app/(layout-free)/event/[slug]/menu/menu-item.tsx b/src/app/(layout-free)/event/[slug]/menu/menu-item.tsx index 78c063d..383f3c8 100644 --- a/src/app/(layout-free)/event/[slug]/menu/menu-item.tsx +++ b/src/app/(layout-free)/event/[slug]/menu/menu-item.tsx @@ -15,8 +15,8 @@ export default function MenuItem({
{IconComponent ? ( diff --git a/src/app/(layout-free)/event/[slug]/menu/page.tsx b/src/app/(layout-free)/event/[slug]/menu/page.tsx index e26dae5..209cad2 100644 --- a/src/app/(layout-free)/event/[slug]/menu/page.tsx +++ b/src/app/(layout-free)/event/[slug]/menu/page.tsx @@ -4,6 +4,8 @@ import { Event } from "@/app/(master-layout)/event/[slug]/page"; import MenuItem from "./menu-item"; import { useSyncMap } from "@/provider/syncProvider"; import { useEffect, useState } from "react"; +import Header from "./header"; + function MenuPage({ params }: { params: { slug: string } }) { const [eventsMap, _, mapInitialized] = useSyncMap( @@ -25,16 +27,15 @@ function MenuPage({ params }: { params: { slug: string } }) { return
Loading...
; } + + const itemsCount = internalEvent.selection.items.length; + const columns = itemsCount % 3 === 0 ? 3 : itemsCount % 4 === 0 ? 4 : 5; + return ( - //
- //
- //

Mixologist

- //

SEND YOUR ORDER TO +1-866-866-5302

- //
- //
-
-
-
+ <> +
+
+
{internalEvent.selection?.items.map((item: any, index: Number) => (
@@ -47,8 +48,8 @@ function MenuPage({ params }: { params: { slug: string } }) {
))}
-
-
+
+ ); } diff --git a/src/app/(layout-free)/layout.tsx b/src/app/(layout-free)/layout.tsx deleted file mode 100644 index a14e64f..0000000 --- a/src/app/(layout-free)/layout.tsx +++ /dev/null @@ -1,16 +0,0 @@ -export const metadata = { - title: 'Next.js', - description: 'Generated by Next.js', -} - -export default function RootLayout({ - children, -}: { - children: React.ReactNode -}) { - return ( - - {children} - - ) -} diff --git a/src/app/(master-layout)/layout.tsx b/src/app/(master-layout)/layout.tsx index 7b69173..a7a97e7 100644 --- a/src/app/(master-layout)/layout.tsx +++ b/src/app/(master-layout)/layout.tsx @@ -8,14 +8,12 @@ import { SettingsIcon } from "lucide-react"; import { Inter } from "next/font/google"; import SessionButton from "@/components/session-button"; -const inter = Inter({ subsets: ["latin"] }); - export const metadata: Metadata = { title: "Twilio Mixologist", description: "Get a free beverage from the Twilio Mixologist", }; -export default function RootLayout({ +export default function MainLayout({ children, }: { children: React.ReactNode; diff --git a/tailwind.config.ts b/tailwind.config.ts index d750aa7..41eb7ba 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,6 +1,11 @@ import type { Config } from "tailwindcss"; const config: Config = { + safelist: [ + "grid-cols-3", + "grid-cols-4", + "grid-cols-5", + ], content: [ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", "./src/components/**/*.{js,ts,jsx,tsx,mdx}", From 3d859a87a7f365f0c4ca5edd5d593ea419458fb6 Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Wed, 16 Oct 2024 12:29:26 +0200 Subject: [PATCH 10/62] chore: prettier --- .../event/[slug]/menu/TwilioLogo.tsx | 6 +- .../event/[slug]/menu/header.tsx | 12 ++- .../(layout-free)/event/[slug]/menu/page.tsx | 2 - src/components/icon-map.tsx | 87 +++++++++---------- tailwind.config.ts | 6 +- 5 files changed, 57 insertions(+), 56 deletions(-) diff --git a/src/app/(layout-free)/event/[slug]/menu/TwilioLogo.tsx b/src/app/(layout-free)/event/[slug]/menu/TwilioLogo.tsx index a721c80..69b43fc 100644 --- a/src/app/(layout-free)/event/[slug]/menu/TwilioLogo.tsx +++ b/src/app/(layout-free)/event/[slug]/menu/TwilioLogo.tsx @@ -1,8 +1,8 @@ -import { h, Component } from 'preact'; +import { h, Component } from "preact"; export default class TwilioLogo extends Component { render({ fullLogo }) { - this.props.color = this.props.color || 'rgb(241, 46, 69)'; + this.props.color = this.props.color || "rgb(241, 46, 69)"; this.props.width = this.props.width || 50; this.props.x = this.props.x || 0; this.props.y = this.props.y || 0; @@ -52,4 +52,4 @@ export default class TwilioLogo extends Component { ); } -} \ No newline at end of file +} diff --git a/src/app/(layout-free)/event/[slug]/menu/header.tsx b/src/app/(layout-free)/event/[slug]/menu/header.tsx index 488f66c..917d64f 100644 --- a/src/app/(layout-free)/event/[slug]/menu/header.tsx +++ b/src/app/(layout-free)/event/[slug]/menu/header.tsx @@ -4,7 +4,11 @@ export default function Header({ number }: { number: string }) { return (
- +
Mixologist {/*

SEND YOUR ORDER TO {number}

*/} - + ); diff --git a/src/app/(layout-free)/event/[slug]/menu/page.tsx b/src/app/(layout-free)/event/[slug]/menu/page.tsx index 209cad2..c877ed8 100644 --- a/src/app/(layout-free)/event/[slug]/menu/page.tsx +++ b/src/app/(layout-free)/event/[slug]/menu/page.tsx @@ -6,7 +6,6 @@ import { useSyncMap } from "@/provider/syncProvider"; import { useEffect, useState } from "react"; import Header from "./header"; - function MenuPage({ params }: { params: { slug: string } }) { const [eventsMap, _, mapInitialized] = useSyncMap( process.env.NEXT_PUBLIC_EVENTS_MAP || "", @@ -27,7 +26,6 @@ function MenuPage({ params }: { params: { slug: string } }) { return
Loading...
; } - const itemsCount = internalEvent.selection.items.length; const columns = itemsCount % 3 === 0 ? 3 : itemsCount % 4 === 0 ? 4 : 5; diff --git a/src/components/icon-map.tsx b/src/components/icon-map.tsx index 0060c73..89d8d95 100644 --- a/src/components/icon-map.tsx +++ b/src/components/icon-map.tsx @@ -1,45 +1,44 @@ import { - AmericanoIcon, - SmoothieOrangeIcon, - SmoothiePineappleIcon, - SmoothieStrawberryIcon, - EspressoDopioIcon, - EspressoIcon, - LatteMacchiatoIcon, - FlatWhiteIcon, - EspressoMacchiatoIcon, - EspressoMacchiatoIcon2, - CappuccinoIcon, - CoffeeCupIcon, - CupIcon, - CoffeeIcon, - } from "@/components/icons"; - - const iconMap: { [key: string]: any } = { - "Double Espresso": EspressoDopioIcon, - Espresso: EspressoIcon, - Americano: AmericanoIcon, - "Latte Macchiato": LatteMacchiatoIcon, - "Caffè Latte": LatteMacchiatoIcon, - "Flat White": FlatWhiteIcon, - Macchiato: EspressoMacchiatoIcon, - "Double Macchiato": EspressoMacchiatoIcon2, - Cappuccino: CappuccinoIcon, - "Espresso Macchiato": EspressoMacchiatoIcon2, - Coffee: CoffeeIcon, - Matcha: CoffeeCupIcon, - Chocolate: CoffeeCupIcon, - Mocha: CupIcon, - "Black Tea": CupIcon, - "British Breakfast Tea": CupIcon, - "Apple Chamomile": CupIcon, - "Earl Grey": CupIcon, - "Herbal Tea": CupIcon, - Chai: CupIcon, - Colombia: SmoothieStrawberryIcon, - Aquamarine: SmoothiePineappleIcon, - Lambada: SmoothieOrangeIcon, - }; - - export default iconMap; - \ No newline at end of file + AmericanoIcon, + SmoothieOrangeIcon, + SmoothiePineappleIcon, + SmoothieStrawberryIcon, + EspressoDopioIcon, + EspressoIcon, + LatteMacchiatoIcon, + FlatWhiteIcon, + EspressoMacchiatoIcon, + EspressoMacchiatoIcon2, + CappuccinoIcon, + CoffeeCupIcon, + CupIcon, + CoffeeIcon, +} from "@/components/icons"; + +const iconMap: { [key: string]: any } = { + "Double Espresso": EspressoDopioIcon, + Espresso: EspressoIcon, + Americano: AmericanoIcon, + "Latte Macchiato": LatteMacchiatoIcon, + "Caffè Latte": LatteMacchiatoIcon, + "Flat White": FlatWhiteIcon, + Macchiato: EspressoMacchiatoIcon, + "Double Macchiato": EspressoMacchiatoIcon2, + Cappuccino: CappuccinoIcon, + "Espresso Macchiato": EspressoMacchiatoIcon2, + Coffee: CoffeeIcon, + Matcha: CoffeeCupIcon, + Chocolate: CoffeeCupIcon, + Mocha: CupIcon, + "Black Tea": CupIcon, + "British Breakfast Tea": CupIcon, + "Apple Chamomile": CupIcon, + "Earl Grey": CupIcon, + "Herbal Tea": CupIcon, + Chai: CupIcon, + Colombia: SmoothieStrawberryIcon, + Aquamarine: SmoothiePineappleIcon, + Lambada: SmoothieOrangeIcon, +}; + +export default iconMap; diff --git a/tailwind.config.ts b/tailwind.config.ts index 41eb7ba..a2459c5 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,11 +1,7 @@ import type { Config } from "tailwindcss"; const config: Config = { - safelist: [ - "grid-cols-3", - "grid-cols-4", - "grid-cols-5", - ], + safelist: ["grid-cols-3", "grid-cols-4", "grid-cols-5"], content: [ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", "./src/components/**/*.{js,ts,jsx,tsx,mdx}", From 843de67a5fe27ab6dc37375ee76625dd5ef7e68a Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Wed, 16 Oct 2024 12:36:32 +0200 Subject: [PATCH 11/62] fix SVG fill prop --- .../event/[slug]/menu/TwilioLogo.tsx | 55 -------- src/components/icons.tsx | 126 +++++++++--------- 2 files changed, 63 insertions(+), 118 deletions(-) delete mode 100644 src/app/(layout-free)/event/[slug]/menu/TwilioLogo.tsx diff --git a/src/app/(layout-free)/event/[slug]/menu/TwilioLogo.tsx b/src/app/(layout-free)/event/[slug]/menu/TwilioLogo.tsx deleted file mode 100644 index 69b43fc..0000000 --- a/src/app/(layout-free)/event/[slug]/menu/TwilioLogo.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { h, Component } from "preact"; - -export default class TwilioLogo extends Component { - render({ fullLogo }) { - this.props.color = this.props.color || "rgb(241, 46, 69)"; - this.props.width = this.props.width || 50; - this.props.x = this.props.x || 0; - this.props.y = this.props.y || 0; - return fullLogo - ? this.renderFullLogo(this.props) - : this.renderSmallLogo(this.props); - } - - renderSmallLogo({ width, x, y, color }) { - return ( - - - - - - - - - - ); - } - - renderFullLogo({ width, height, x, y, color }) { - return ( - - - - - - - - - - ); - } -} diff --git a/src/components/icons.tsx b/src/components/icons.tsx index d0dccc4..669d453 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -5,7 +5,7 @@ export function AmericanoIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -96,7 +96,7 @@ export function AmericanoIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -186,7 +186,7 @@ export function AeroPressIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -218,7 +218,7 @@ export function CafeLatteIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -460,7 +460,7 @@ export function CappuccinoIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -665,7 +665,7 @@ export function CappuccinoIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -922,7 +922,7 @@ export function CarafeIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -953,7 +953,7 @@ export function CheckexIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -994,7 +994,7 @@ export function CoffeaArabicaIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1080,7 +1080,7 @@ export function CoffeeBeanIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1138,7 +1138,7 @@ export function CoffeeBeanIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1177,7 +1177,7 @@ export function FilterCoffeeIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1343,7 +1343,7 @@ export function CoffeeGrinderIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1379,7 +1379,7 @@ export function GrinderMachineIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1471,7 +1471,7 @@ export function CoffeeGrinderIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1506,7 +1506,7 @@ export function CoffeePadIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1566,7 +1566,7 @@ export function CoffeePadsIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - fill="none" + style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1977,15 +1977,15 @@ export function CoffeePadsIcon(props: any) { export function CoffeeShopIcon(props: any) { return ( Date: Wed, 16 Oct 2024 12:42:40 +0200 Subject: [PATCH 12/62] fix svgs in a better way --- .../event/[slug]/menu/header.tsx | 4 +- .../event/[slug]/menu/menu-item.tsx | 3 +- src/components/icons.tsx | 109 +++++++++--------- src/components/menu-item.tsx | 4 +- 4 files changed, 60 insertions(+), 60 deletions(-) diff --git a/src/app/(layout-free)/event/[slug]/menu/header.tsx b/src/app/(layout-free)/event/[slug]/menu/header.tsx index 917d64f..276243b 100644 --- a/src/app/(layout-free)/event/[slug]/menu/header.tsx +++ b/src/app/(layout-free)/event/[slug]/menu/header.tsx @@ -7,7 +7,7 @@ export default function Header({ number }: { number: string }) {
diff --git a/src/app/(layout-free)/event/[slug]/menu/menu-item.tsx b/src/app/(layout-free)/event/[slug]/menu/menu-item.tsx index 383f3c8..1f6124d 100644 --- a/src/app/(layout-free)/event/[slug]/menu/menu-item.tsx +++ b/src/app/(layout-free)/event/[slug]/menu/menu-item.tsx @@ -17,7 +17,7 @@ export default function MenuItem({ ) : ( @@ -25,6 +25,7 @@ export default function MenuItem({ width="3rem" height="3rem" fill="text-black" + style={{fill: "white"}} className="m-2 mx-auto" /> )} diff --git a/src/components/icons.tsx b/src/components/icons.tsx index 669d453..5bf15e6 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -5,7 +5,7 @@ export function AmericanoIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -96,7 +96,7 @@ export function AmericanoIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -186,7 +186,7 @@ export function AeroPressIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -218,7 +218,7 @@ export function CafeLatteIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -460,7 +460,7 @@ export function CappuccinoIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -665,7 +665,7 @@ export function CappuccinoIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -922,7 +922,7 @@ export function CarafeIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -953,7 +953,7 @@ export function CheckexIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -994,7 +994,7 @@ export function CoffeaArabicaIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1080,7 +1080,7 @@ export function CoffeeBeanIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1138,7 +1138,7 @@ export function CoffeeBeanIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1177,7 +1177,7 @@ export function FilterCoffeeIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1343,7 +1343,7 @@ export function CoffeeGrinderIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1379,7 +1379,7 @@ export function GrinderMachineIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1471,7 +1471,7 @@ export function CoffeeGrinderIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1506,7 +1506,7 @@ export function CoffeePadIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1566,7 +1566,7 @@ export function CoffeePadsIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -1981,7 +1981,6 @@ export function CoffeeShopIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -2077,7 +2076,7 @@ export function CoffeeCupIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -2106,7 +2105,7 @@ export function CoffeePotIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -2144,7 +2143,7 @@ export function CortadoIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -2396,7 +2395,7 @@ export function CoffeeIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -2455,7 +2454,7 @@ export function EspressoIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -2512,7 +2511,7 @@ export function EspressoDopioIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -2571,7 +2570,7 @@ export function EspressoMacchiatoIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -2644,7 +2643,7 @@ export function EspressoMacchiatoIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -2773,7 +2772,7 @@ export function EspressoMachineIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -2832,7 +2831,7 @@ export function EspressoMachineIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -2885,7 +2884,7 @@ export function EspressoMakerIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -2924,7 +2923,7 @@ export function EspressoMilkFoamIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -3150,7 +3149,7 @@ export function EspressoIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -3209,7 +3208,7 @@ export function FilterCoffeeMakerIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -3256,7 +3255,7 @@ export function FilterConeIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -3282,7 +3281,7 @@ export function FlatWhiteIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -3442,7 +3441,7 @@ export function FlatWhiteIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -3586,7 +3585,7 @@ export function FrappuccinoIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -3621,7 +3620,7 @@ export function FrenchPressIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -3659,7 +3658,7 @@ export function JarGroundCoffeeIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -4590,7 +4589,7 @@ export function LatteMacchiatoIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -4823,7 +4822,7 @@ export function MilkBottleIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -4860,7 +4859,7 @@ export function MilkBoxIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -4900,7 +4899,7 @@ export function MilkJugIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -4931,7 +4930,7 @@ export function MilkPackageIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -5002,7 +5001,7 @@ export function MoccacinoIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -5084,7 +5083,7 @@ export function CupIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -5142,7 +5141,7 @@ export function ModernCoffeeGrinderIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -5229,7 +5228,7 @@ export function PortafilterIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -5255,7 +5254,7 @@ export function SyphoneCoffeeMakerIcon2(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -5294,7 +5293,7 @@ export function SyphoneCoffeeMakerIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -5333,7 +5332,7 @@ export function TamperIcon(props: any) { width="24" height="24" viewBox="0 0 256 256" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -5358,7 +5357,7 @@ export function SmoothieOrangeIcon(props: any) { width="24" height="24" viewBox="0 0 1200 1200" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -5381,7 +5380,7 @@ export function SmoothiePineappleIcon(props: any) { width="24" height="24" viewBox="0 0 375 375" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -5473,7 +5472,7 @@ export function SmoothieStrawberryIcon(props: any) { width="24" height="24" viewBox="0 0 1200 1200" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -5496,7 +5495,7 @@ export function SmoothieCupIcon(props: any) { width="24" height="24" viewBox="0 0 1200 1200" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" @@ -5515,7 +5514,7 @@ export function SmoothieBlenderIcon(props: any) { width="24" height="24" viewBox="0 0 1200 1200" - style={{fill: "white"}} + fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" diff --git a/src/components/menu-item.tsx b/src/components/menu-item.tsx index 06a9cda..374d160 100644 --- a/src/components/menu-item.tsx +++ b/src/components/menu-item.tsx @@ -18,14 +18,14 @@ export default function MenuItem({ ) : ( )} From 43fef93089324629301bd8ad6957fe0f906afe22 Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Wed, 16 Oct 2024 12:56:02 +0200 Subject: [PATCH 13/62] render on server instead --- .../(layout-free)/event/[slug]/menu/page.tsx | 86 ++++++++++--------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/src/app/(layout-free)/event/[slug]/menu/page.tsx b/src/app/(layout-free)/event/[slug]/menu/page.tsx index c877ed8..1501d63 100644 --- a/src/app/(layout-free)/event/[slug]/menu/page.tsx +++ b/src/app/(layout-free)/event/[slug]/menu/page.tsx @@ -1,54 +1,56 @@ -"use client"; - import { Event } from "@/app/(master-layout)/event/[slug]/page"; import MenuItem from "./menu-item"; -import { useSyncMap } from "@/provider/syncProvider"; -import { useEffect, useState } from "react"; import Header from "./header"; +import { getSyncService } from "@/lib/twilio"; -function MenuPage({ params }: { params: { slug: string } }) { - const [eventsMap, _, mapInitialized] = useSyncMap( - process.env.NEXT_PUBLIC_EVENTS_MAP || "", - [params.slug], - ); +async function MenuPage({ params }: { params: { slug: string } }) { + if (!process.env.NEXT_PUBLIC_EVENTS_MAP) { + throw new Error("No config doc specified"); + } - useEffect(() => { - if (mapInitialized) { - // @ts-ignore // TODO fix this TS issue - const existingEvent = eventsMap?.get(params.slug) as Event; - updateInternalEvent(existingEvent); - } - }, [eventsMap, mapInitialized]); + const syncService = await getSyncService(); + try { + const events = await syncService + .syncMaps()(process.env.NEXT_PUBLIC_EVENTS_MAP) + .fetch(); + const items = await events.syncMapItems().list(); - const [internalEvent, updateInternalEvent] = useState(); + // @ts-ignore + const internalEvent: Event = items.find( + (item: any) => item.data.slug === params.slug, + ).data; - if (!internalEvent || !internalEvent.selection) { - return
Loading...
; - } - - const itemsCount = internalEvent.selection.items.length; - const columns = itemsCount % 3 === 0 ? 3 : itemsCount % 4 === 0 ? 4 : 5; + if (!internalEvent) { + return
Event not found
; + } - return ( - <> -
-
-
- {internalEvent.selection?.items.map((item: any, index: Number) => ( -
-
- + const itemsCount = internalEvent.selection.items.length; + const columns = itemsCount % 3 === 0 ? 3 : itemsCount % 4 === 0 ? 4 : 5; + + return ( + <> +
+
+
+ {internalEvent.selection?.items.map((item: any, index: Number) => ( +
+
+ +
-
- ))} -
-
- - ); + ))} + + + + ); + } catch (e: any) { + console.error(e); + throw new Error("Could not fetch events", e); + } } export default MenuPage; From 6660e8f286df330815bf6a60d765fff72e9e811f Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Wed, 16 Oct 2024 13:01:43 +0200 Subject: [PATCH 14/62] fix: slashes are not allowed for sync keys --- src/app/(master-layout)/event/[slug]/page.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/(master-layout)/event/[slug]/page.tsx b/src/app/(master-layout)/event/[slug]/page.tsx index 37b8c5b..e793519 100644 --- a/src/app/(master-layout)/event/[slug]/page.tsx +++ b/src/app/(master-layout)/event/[slug]/page.tsx @@ -395,6 +395,7 @@ function toKebabCase(string: string) { // no starting or tailing dashes return string .replace(/([a-z])([A-Z])/g, "$1-$2") + .replace(/\//g, "-") .replace(/([0-9])([a-zA-Z])/g, "$1-$2") .replace(/([a-zA-Z])([0-9])/g, "$1-$2") .replace(/[\s_]+/g, "-") From 791894257d5b3fc6e5331e2d7f0ac2adad5e9f1d Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Mon, 21 Oct 2024 15:15:55 +0800 Subject: [PATCH 15/62] typo: template --- src/scripts/getTemplates.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/getTemplates.ts b/src/scripts/getTemplates.ts index e736c1a..6d3098a 100644 --- a/src/scripts/getTemplates.ts +++ b/src/scripts/getTemplates.ts @@ -67,7 +67,7 @@ export function getWrongOrderTemplate( }); } - const body = `Seems like your order of "{{0}}" is not something we can serve. ${getAvailableOptions(indiciesOfFullTitles)}Write "I need help" to get an overview of other commands.`; + const body = `Seems like your order of "{{0}}" is not something we can serve. ${getAvailableOptions(indiciesOfFullTitles)}\nWrite "I need help" to get an overview of other commands.`; return { friendly_name: templateName, From bea05d184723442ecfcf9e569a62b986c5002bae Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Tue, 22 Oct 2024 08:02:38 +0800 Subject: [PATCH 16/62] fix spelling to avoid "latte" issue --- src/config/spellingMap.ts | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/config/spellingMap.ts b/src/config/spellingMap.ts index b865523..1681a06 100644 --- a/src/config/spellingMap.ts +++ b/src/config/spellingMap.ts @@ -6,9 +6,10 @@ export interface SpellingMap { } export default { - expreso: "Espresso", - expresso: "Espresso", - espresso: "Espresso", + americano: "Americano", + "cafe late": "Caffè Latte", + "cafe latte": "Caffè Latte", + caffe: "Caffè", cappacino: "Cappuccino", capacino: "Cappuccino", cappocino: "Cappuccino", @@ -17,23 +18,21 @@ export default { cappuccino: "Cappuccino", capuccino: "Cappuccino", capochino: "Cappuccino", - late: "Latt Macchiato", - lattey: "Latte Macchiato", - larte: "Latte Macchiato", - lattee: "Latte Macchiato", - latte: "Latte Macchiato", - "cafe late": "Latte", - "caffeé latte": "Latte", - "caffe latte": "Latte", - "white americano": "Americano", - americano: "Americano", - caffeé: "Americano", + expreso: "Espresso", + expresso: "Espresso", + espresso: "Espresso", "flat white": "Flat White", flatwhite: "Flat White", "flat-white": "Flat White", "flatt white": "Flat White", "filter coffee": "Coffee", "fliter coffee": "Coffee", + late: "Latt", + lattey: "Latte", + larte: "Latte", + lattee: "Latte", + latte: "Latte", + "white americano": "Americano", "hot chocolate": "Hot Chocolate", chocolate: "Hot Chocolate", cocolate: "Hot Chocolate", From 574f3fa21c1cd39e1952eb0f48e4d7e0c87bc55e Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Thu, 24 Oct 2024 18:05:06 +0800 Subject: [PATCH 17/62] more modifiers --- src/config/menus.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/config/menus.ts b/src/config/menus.ts index e8f863b..1274e7e 100644 --- a/src/config/menus.ts +++ b/src/config/menus.ts @@ -140,6 +140,11 @@ export default { "Oat Milk", "Coconut Milk", "Rice Milk", + "Vanilla Syrup", + "Chocolate Caramel Syrup", + "Hazelnut Syrup", + "Brown Sugar Stick", + "White Sugar Stick", ], }, smoothie: { From 087d5e816ce0c519a5bb2f1ceda2bc3566dab670 Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Mon, 28 Oct 2024 07:05:30 +0100 Subject: [PATCH 18/62] Download script to support param to download only one event key --- src/scripts/download.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/scripts/download.ts b/src/scripts/download.ts index 4260c8c..b81f7a1 100644 --- a/src/scripts/download.ts +++ b/src/scripts/download.ts @@ -13,20 +13,30 @@ const client = twilio(TWILIO_API_KEY, TWILIO_API_SECRET, { accountSid: TWILIO_ACCOUNT_SID, }); +const eventName = process.argv.pop(); + +if (!eventName || eventName.startsWith("/") || eventName.includes("=")) { + console.error( + "Please provide an event name as the last argument, e.g. 'pnpm download wearedevs24'", + ); + process.exit(1); +} + (async () => { //fetch all attendees and write to csv file with header columns const map = await client.sync.v1 .services(TWILIO_SYNC_SERVICE_SID) .syncMaps("ActiveCustomers"); try { - const mapItems = await map.syncMapItems.list({ limit: 1000 }); + const mapItems = await map.syncMapItems.list({ limit: 1000 }); //TODO should fetch all here const attendees = mapItems .map((item) => item.data) .filter( (a) => - a.stage === Stages.VERIFIED_USER || - a.stage === Stages.FIRST_ORDER || - a.stage === Stages.REPEAT_CUSTOMER, + (a.stage === Stages.VERIFIED_USER || + a.stage === Stages.FIRST_ORDER || + a.stage === Stages.REPEAT_CUSTOMER) && + a.event === eventName, ); const csv = attendees.map((attendee) => { return `${attendee.email},${attendee.event},${attendee.stage}`; From 720c7592eeb7816ac944fc90a17226a92d4b72a7 Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Mon, 28 Oct 2024 07:07:58 +0100 Subject: [PATCH 19/62] temp workaround for money2020 --- src/config/spellingMap.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/config/spellingMap.ts b/src/config/spellingMap.ts index 1681a06..ef89cf2 100644 --- a/src/config/spellingMap.ts +++ b/src/config/spellingMap.ts @@ -31,7 +31,9 @@ export default { lattey: "Latte", larte: "Latte", lattee: "Latte", - latte: "Latte", + // latte: "Latte", TODO undo this workaround + latte: "Caffè Latte", + Latte: "Caffè Latte", "white americano": "Americano", "hot chocolate": "Hot Chocolate", chocolate: "Hot Chocolate", From f7d1b36f604c883e3eb99b0771dc0b2492b76f1e Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Mon, 28 Oct 2024 07:16:45 +0100 Subject: [PATCH 20/62] US as default over canada --- src/app/webhooks/conversations/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/webhooks/conversations/route.ts b/src/app/webhooks/conversations/route.ts index 201e30e..ba4c55f 100644 --- a/src/app/webhooks/conversations/route.ts +++ b/src/app/webhooks/conversations/route.ts @@ -80,7 +80,7 @@ export async function POST(request: Request) { event: newEvent.slug, orderCount: 0, stage: Stages.NEW_USER, - country: country?.name, + country: country?.name === "Canada" ? "United States" : country?.name, }, TwoWeeksInSeconds, ); @@ -127,7 +127,7 @@ export async function POST(request: Request) { event: newEvent.slug, orderCount: 0, stage: Stages.NEW_USER, - country: country?.name, + country: country?.name === "Canada" ? "United States" : country?.name, }, TwoWeeksInSeconds, ); From 18afd31ba2bd9151b7827d95c05b859ae320640f Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Mon, 28 Oct 2024 07:30:38 +0100 Subject: [PATCH 21/62] hide some stages if lead collection is disabled --- src/app/api/event/[slug]/stats/helper.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/api/event/[slug]/stats/helper.ts b/src/app/api/event/[slug]/stats/helper.ts index 06b1216..4956497 100644 --- a/src/app/api/event/[slug]/stats/helper.ts +++ b/src/app/api/event/[slug]/stats/helper.ts @@ -89,6 +89,12 @@ export async function calcStatsForEvent( }, {}); let previousSum = 0; const summedUpStages = Object.keys(Stages) + // skip if lead collection is disabled and stage is one of the following: VERIFING, VERIFIED_USER + .filter( + (stage: any) => + event.enableLeadCollection || + ![Stages.VERIFYING, Stages.VERIFIED_USER].includes(stage), + ) .reverse() .map((stage) => { let sum = (attendeeStages[stage] || 0) + previousSum; From cba9d73a9ea13703e87dd7b840a72e7707c2bbda Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Mon, 28 Oct 2024 07:35:55 +0100 Subject: [PATCH 22/62] list stages on chart --- src/app/(master-layout)/event/[slug]/stats/page.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/app/(master-layout)/event/[slug]/stats/page.tsx b/src/app/(master-layout)/event/[slug]/stats/page.tsx index c80fee8..62a9faa 100644 --- a/src/app/(master-layout)/event/[slug]/stats/page.tsx +++ b/src/app/(master-layout)/event/[slug]/stats/page.tsx @@ -44,6 +44,14 @@ function StatsPage({ params }: { params: { slug: string } }) { }); }, []); + function listStages(stats: MixologistStats | undefined) { + if (!stats) return; + const stages = stats.summedUpStages.map((stage) => { + return stage.id; + }); + return `The stages are: ${stages.join(", ")}`; + } + return (
{!stats && ( @@ -207,7 +215,7 @@ function StatsPage({ params }: { params: { slug: string } }) { Attendee Funnel - See how many attendees are at each stage + See how many attendees are at each stage {listStages(stats)} From 8a2cf1e41ed266f35b32491a9ba816390c3a2add Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Mon, 28 Oct 2024 07:48:39 +0100 Subject: [PATCH 23/62] test modifiers note --- src/app/webhooks/conversations/route.ts | 9 ++++++++- src/lib/templates.ts | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/app/webhooks/conversations/route.ts b/src/app/webhooks/conversations/route.ts index ba4c55f..1220492 100644 --- a/src/app/webhooks/conversations/route.ts +++ b/src/app/webhooks/conversations/route.ts @@ -127,7 +127,8 @@ export async function POST(request: Request) { event: newEvent.slug, orderCount: 0, stage: Stages.NEW_USER, - country: country?.name === "Canada" ? "United States" : country?.name, + country: + country?.name === "Canada" ? "United States" : country?.name, }, TwoWeeksInSeconds, ); @@ -390,6 +391,12 @@ export async function POST(request: Request) { const { contentSid, contentVariables } = await templates.getHelpMessage(event); addMessageToConversation(conversationSid, "", contentSid, contentVariables); + setTimeout(() => { + const modifiersNote = templates.getModifiersMessage( + event.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); + }, 1000); return new Response("", { status: 200 }); } else if (incomingMessage.includes("queue")) { const queuePosition = await getQueuePosition( diff --git a/src/lib/templates.ts b/src/lib/templates.ts index 55f3f96..6ece00a 100644 --- a/src/lib/templates.ts +++ b/src/lib/templates.ts @@ -114,6 +114,12 @@ export async function getHelpMessage(event: Event) { }; } +export function getModifiersMessage(modifiers: string[]) { + return `You can add the following add-ons to your order:\n${modifiers + .map((m) => `- ${m}`) + .join("\n")}`; +} + export function getExistingOrderMessage(product: string, orderNumber: number) { return `We're still making you a ${product}.\n\nIf you'd like to change or modify your order reply with 'Change order to {your new choice}'. \n\nCheck order #${orderNumber} with our staff if you think there's something wrong`; } From ee2ecfdc523c44f19880363015c85e07ea2ff55d Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Mon, 28 Oct 2024 07:59:14 +0100 Subject: [PATCH 24/62] sent more helper messages for modifiers --- src/app/webhooks/conversations/route.ts | 51 ++++++++++++++++++++----- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/src/app/webhooks/conversations/route.ts b/src/app/webhooks/conversations/route.ts index 1220492..f88a9a0 100644 --- a/src/app/webhooks/conversations/route.ts +++ b/src/app/webhooks/conversations/route.ts @@ -86,7 +86,7 @@ export async function POST(request: Request) { ); if (!newEvent.enableLeadCollection) { - await sleep(2000); + await sleep(1000); const dataPolicy = templates.getDataPolicy(newEvent.selection.mode); addMessageToConversation(conversationSid, dataPolicy); const message = await templates.getReadyToOrderMessage( @@ -101,6 +101,12 @@ export async function POST(request: Request) { message.contentSid, message.contentVariables, ); + + await sleep(1000); + const modifiersNote = templates.getModifiersMessage( + newEvent.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); } return new Response("Assigned event to attendee", { status: 201 }); @@ -133,7 +139,7 @@ export async function POST(request: Request) { TwoWeeksInSeconds, ); if (!newEvent.enableLeadCollection) { - await sleep(2000); + await sleep(1000); const dataPolicy = templates.getDataPolicy(newEvent.selection.mode); addMessageToConversation(conversationSid, dataPolicy); const message = await templates.getReadyToOrderMessage( @@ -148,6 +154,12 @@ export async function POST(request: Request) { message.contentSid, message.contentVariables, ); + + await sleep(1000); + const modifiersNote = templates.getModifiersMessage( + newEvent.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); } return new Response("Assigned event to attendee", { status: 201 }); } @@ -189,6 +201,13 @@ export async function POST(request: Request) { newEvent.maxOrders, true, ); + + await sleep(500); + const modifiersNote = templates.getModifiersMessage( + newEvent.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); + await updateOrCreateSyncMapItem( NEXT_PUBLIC_ACTIVE_CUSTOMERS_MAP, conversationSid, @@ -245,6 +264,13 @@ export async function POST(request: Request) { message.contentSid, message.contentVariables, ); + + await sleep(500); + const modifiersNote = templates.getModifiersMessage( + newEvent.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); + return new Response("Assigned event to attendee", { status: 201 }); } @@ -361,7 +387,13 @@ export async function POST(request: Request) { message.contentVariables, ); - await sleep(2000); + await sleep(1000); + const modifiersNote = templates.getModifiersMessage( + event.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); + + await sleep(1000); const dataPolicy = templates.getDataPolicy(event.selection.mode); addMessageToConversation(conversationSid, dataPolicy); @@ -391,12 +423,13 @@ export async function POST(request: Request) { const { contentSid, contentVariables } = await templates.getHelpMessage(event); addMessageToConversation(conversationSid, "", contentSid, contentVariables); - setTimeout(() => { - const modifiersNote = templates.getModifiersMessage( - event.selection.modifiers, - ); - addMessageToConversation(conversationSid, modifiersNote); - }, 1000); + + await sleep(1000); + const modifiersNote = templates.getModifiersMessage( + event.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); + return new Response("", { status: 200 }); } else if (incomingMessage.includes("queue")) { const queuePosition = await getQueuePosition( From 38630e5ce77faad3e96fff2e19becd8ff29b90cb Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Mon, 28 Oct 2024 15:48:01 +0100 Subject: [PATCH 25/62] only send modifiers message if there are multiple modifiers --- src/app/webhooks/conversations/route.ts | 72 ++++++++++++++----------- src/scripts/clearAttendeeMap.ts | 2 +- src/scripts/clearOrdersForEvent.ts | 2 +- 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/app/webhooks/conversations/route.ts b/src/app/webhooks/conversations/route.ts index f88a9a0..ccfc2ba 100644 --- a/src/app/webhooks/conversations/route.ts +++ b/src/app/webhooks/conversations/route.ts @@ -102,11 +102,13 @@ export async function POST(request: Request) { message.contentVariables, ); - await sleep(1000); - const modifiersNote = templates.getModifiersMessage( - newEvent.selection.modifiers, - ); - addMessageToConversation(conversationSid, modifiersNote); + if (newEvent.selection.modifiers.length > 1) { + await sleep(1000); + const modifiersNote = templates.getModifiersMessage( + newEvent.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); + } } return new Response("Assigned event to attendee", { status: 201 }); @@ -155,11 +157,13 @@ export async function POST(request: Request) { message.contentVariables, ); - await sleep(1000); - const modifiersNote = templates.getModifiersMessage( - newEvent.selection.modifiers, - ); - addMessageToConversation(conversationSid, modifiersNote); + if (newEvent.selection.modifiers.length > 1) { + await sleep(1000); + const modifiersNote = templates.getModifiersMessage( + newEvent.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); + } } return new Response("Assigned event to attendee", { status: 201 }); } @@ -202,11 +206,13 @@ export async function POST(request: Request) { true, ); - await sleep(500); - const modifiersNote = templates.getModifiersMessage( - newEvent.selection.modifiers, - ); - addMessageToConversation(conversationSid, modifiersNote); + if (newEvent.selection.modifiers.length > 1) { + await sleep(500); + const modifiersNote = templates.getModifiersMessage( + newEvent.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); + } await updateOrCreateSyncMapItem( NEXT_PUBLIC_ACTIVE_CUSTOMERS_MAP, @@ -265,11 +271,13 @@ export async function POST(request: Request) { message.contentVariables, ); - await sleep(500); - const modifiersNote = templates.getModifiersMessage( - newEvent.selection.modifiers, - ); - addMessageToConversation(conversationSid, modifiersNote); + if (newEvent.selection.modifiers.length > 1) { + await sleep(500); + const modifiersNote = templates.getModifiersMessage( + newEvent.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); + } return new Response("Assigned event to attendee", { status: 201 }); } @@ -387,11 +395,13 @@ export async function POST(request: Request) { message.contentVariables, ); - await sleep(1000); - const modifiersNote = templates.getModifiersMessage( - event.selection.modifiers, - ); - addMessageToConversation(conversationSid, modifiersNote); + if (event.selection.modifiers.length > 1) { + await sleep(1000); + const modifiersNote = templates.getModifiersMessage( + event.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); + } await sleep(1000); const dataPolicy = templates.getDataPolicy(event.selection.mode); @@ -424,11 +434,13 @@ export async function POST(request: Request) { await templates.getHelpMessage(event); addMessageToConversation(conversationSid, "", contentSid, contentVariables); - await sleep(1000); - const modifiersNote = templates.getModifiersMessage( - event.selection.modifiers, - ); - addMessageToConversation(conversationSid, modifiersNote); + if (event.selection.modifiers.length > 1) { + await sleep(1000); + const modifiersNote = templates.getModifiersMessage( + event.selection.modifiers, + ); + addMessageToConversation(conversationSid, modifiersNote); + } return new Response("", { status: 200 }); } else if (incomingMessage.includes("queue")) { diff --git a/src/scripts/clearAttendeeMap.ts b/src/scripts/clearAttendeeMap.ts index 64838b1..fe316c3 100644 --- a/src/scripts/clearAttendeeMap.ts +++ b/src/scripts/clearAttendeeMap.ts @@ -18,7 +18,7 @@ const client = twilio(TWILIO_API_KEY, TWILIO_API_SECRET, { const mapItems = await client.sync.v1 .services(TWILIO_SYNC_SERVICE_SID) .syncMaps("ActiveCustomers") - .syncMapItems.list({ limit: 1000 }); + .syncMapItems.list({ limit: 1000 }); // TODO go over all users here, not just 1000, use pagination mapItems.map((item) => { throttle(async () => { diff --git a/src/scripts/clearOrdersForEvent.ts b/src/scripts/clearOrdersForEvent.ts index 4e9cb77..48797cd 100644 --- a/src/scripts/clearOrdersForEvent.ts +++ b/src/scripts/clearOrdersForEvent.ts @@ -42,7 +42,7 @@ if (!eventName || eventName.startsWith("/") || eventName.includes("=")) { const orderItems = await client.sync.v1 .services(TWILIO_SYNC_SERVICE_SID) .syncLists(eventName) - .syncListItems.list({ limit: 1000 }); + .syncListItems.list({ limit: 1000 }); // TODO should delete all here await Promise.all( orderItems.map(async (item) => { await client.sync.v1 From be39c81b6b3f350dc0c176a1f8f2e34877a343b4 Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Wed, 30 Oct 2024 12:48:31 +0100 Subject: [PATCH 26/62] Add chek error script --- README.md | 8 +++- package.json | 1 + src/scripts/checkForErrors.ts | 73 +++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/scripts/checkForErrors.ts diff --git a/README.md b/README.md index 8f9f058..6885637 100644 --- a/README.md +++ b/README.md @@ -117,9 +117,13 @@ pnpm test # run unit tests pnpm test:e2e # run e2e tests ``` -## Deploy +## Tips for production -TBD +Here are a few helpful notes: + +- If you are using the SMS channel, make sure to [set the SMS Geo Permissions](https://www.twilio.com/docs/messaging/guides/sms-geo-permissions)to make sure senders from the entire world can interact with the Mixologist. +- Edit the [opt-out management settings](https://help.twilio.com/articles/360034798533-Getting-Started-with-Advanced-Opt-Out-for-Messaging-Services) of the messaging service to avoid that users accidentally unsubscribe from the list. +- Regularly run `pnpm check-for-errors` and see if unforeseen errors occurred when the users tried to order. ## How To Use diff --git a/package.json b/package.json index 09f905b..a0c07c3 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "update-config": "tsx -r dotenv/config src/scripts/updateConfig dotenv_config_path=.env.local ", "download": "tsx -r dotenv/config src/scripts/download dotenv_config_path=.env.local ", "create-twilio-res": "tsx -r dotenv/config src/scripts/createTwilioRes dotenv_config_path=.env.local ", + "check-for-errors": "tsx -r dotenv/config src/scripts/checkForErrors dotenv_config_path=.env.local ", "clear-attendees": "tsx -r dotenv/config src/scripts/clearAttendeeMap dotenv_config_path=.env.local", "clear-orders": "tsx -r dotenv/config src/scripts/clearOrdersForEvent dotenv_config_path=.env.local", "prettier": "prettier --write ." diff --git a/src/scripts/checkForErrors.ts b/src/scripts/checkForErrors.ts new file mode 100644 index 0000000..0dfac16 --- /dev/null +++ b/src/scripts/checkForErrors.ts @@ -0,0 +1,73 @@ +import twilio from "twilio"; + +const { + TWILIO_API_KEY = "", + TWILIO_API_SECRET = "", + TWILIO_ACCOUNT_SID = "", + TWILIO_MESSAGING_SERVICE_SID = "", +} = process.env; + +// extend this list manually as needed +const ERROR_DICTONARY: any = { + 21610: 'The user has responded with "STOP"', + 21408: "Sent to a disabled region (Geo-Permissions settings)", + 30008: "Unknown error", + 63013: "Violates Channel provider's policy", + 63016: "Failed to send freeform message outside the window", +}; + +function timeSince(date: Date) { + // @ts-ignore + const seconds = Math.floor((new Date() - date) / 1000); + + let interval = seconds / 31536000; + + if (interval > 1) { + return Math.floor(interval) + " years ago"; + } + interval = seconds / 2592000; + if (interval > 1) { + return Math.floor(interval) + " months ago"; + } + interval = seconds / 86400; + if (interval > 1) { + return Math.floor(interval) + " days ago"; + } + interval = seconds / 3600; + if (interval > 1) { + return Math.floor(interval) + " hours ago"; + } + interval = seconds / 60; + if (interval > 1) { + return Math.floor(interval) + " minutes ago"; + } + return Math.floor(seconds) + " seconds ago"; +} + +const client = twilio(TWILIO_API_KEY, TWILIO_API_SECRET, { + accountSid: TWILIO_ACCOUNT_SID, +}); + +(async () => { + let messagesPage = await client.messages.page({}); + + console.log("Time since sent | Message SID | Error code | Error description"); + while (messagesPage && messagesPage.instances.length > 0) { + const errors = messagesPage.instances.forEach((message: any) => { + if ( + message.errorCode !== null && + message.messagingServiceSid === TWILIO_MESSAGING_SERVICE_SID + ) { + console.log( + `${timeSince(message.dateCreated)} | ${message.sid} | ${message.errorCode} | ${ERROR_DICTONARY[message.errorCode]} `, + ); + } + }); + + // @ts-ignore + messagesPage = await messagesPage.nextPage(); + } + + console.log("Go to https://console.twilio.com/us1/monitor/logs/sms to see more details about a given message"); + console.log("Also check https://console.twilio.com/us1/monitor/logs/debugger/errors for other errors"); +})(); From cfb40202fffed831b5bf2f85133c9995c86af8fe Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Wed, 30 Oct 2024 13:09:50 +0100 Subject: [PATCH 27/62] fail gracefully for invalid email input --- src/app/webhooks/conversations/route.ts | 6 +++++- src/lib/templates.ts | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/app/webhooks/conversations/route.ts b/src/app/webhooks/conversations/route.ts index ccfc2ba..b610d04 100644 --- a/src/app/webhooks/conversations/route.ts +++ b/src/app/webhooks/conversations/route.ts @@ -307,8 +307,12 @@ export async function POST(request: Request) { const email = incomingMessageBody.match(regexForEmail)[0]; try { check = await createVerification(email); - } catch (error) { + } catch (error: any) { console.error(error); + const message = templates.getErrorDuringEmailVerificationMessage( + error.message, + ); + addMessageToConversation(conversationSid, message); return new Response("Error During Verifiction", { status: 500 }); } const message = templates.getSentEmailMessage(); diff --git a/src/lib/templates.ts b/src/lib/templates.ts index 6ece00a..6f274c4 100644 --- a/src/lib/templates.ts +++ b/src/lib/templates.ts @@ -149,6 +149,10 @@ export function getInvalidEmailMessage() { return "Invalid email address. Please reply with a valid business email address."; } +export function getErrorDuringEmailVerificationMessage(error: string) { + return `An error occurred during email verification: ${error}`; +} + export function getSentEmailMessage() { return "We have sent you an email with a verification code. Please reply with the code we sent to your email address.\nIf you did not receive the email, please check your spam folder or enter a new email address."; } From 9c413d65fdec77fe978dc884ea6c2b23480dbbd7 Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Wed, 30 Oct 2024 13:16:32 +0100 Subject: [PATCH 28/62] Fail gracefully when someones sends a media message --- src/app/webhooks/conversations/route.ts | 5 +++++ src/lib/templates.ts | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/app/webhooks/conversations/route.ts b/src/app/webhooks/conversations/route.ts index b610d04..cb9e392 100644 --- a/src/app/webhooks/conversations/route.ts +++ b/src/app/webhooks/conversations/route.ts @@ -45,6 +45,11 @@ export async function POST(request: Request) { if (webHookType !== "onMessageAdded") { return new Response("Wrong event type", { status: 200 }); } + if (!incomingMessageBody && data.get("Media")) { + const noMediaMessage = templates.getNoMediaHandlerMessage(); + addMessageToConversation(conversationSid, noMediaMessage); + return new Response("Send no media note", { status: 200 }); + } const author = data.get("Author") as string, address = redact(author); diff --git a/src/lib/templates.ts b/src/lib/templates.ts index 6f274c4..d7d9adf 100644 --- a/src/lib/templates.ts +++ b/src/lib/templates.ts @@ -145,6 +145,10 @@ export function getOopsMessage(error: any) { return `Oops, something went wrong! Talk to someone from Twilio and see if they can help you.`; } +export function getNoMediaHandlerMessage() { + return "Sorry, we don't support media messages. Please send a text message to order a drink on us."; +} + export function getInvalidEmailMessage() { return "Invalid email address. Please reply with a valid business email address."; } From d62491b70c597ac5632d3d392196a52c0f4cffe7 Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Wed, 30 Oct 2024 13:40:12 +0100 Subject: [PATCH 29/62] have one attendee file per event --- .gitignore | 3 ++- src/scripts/download.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 5ea3b04..a877b6b 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ package-lock.json /playwright/.cache/ -attendees.csv \ No newline at end of file + +attendees-*.csv \ No newline at end of file diff --git a/src/scripts/download.ts b/src/scripts/download.ts index b81f7a1..b913b82 100644 --- a/src/scripts/download.ts +++ b/src/scripts/download.ts @@ -41,7 +41,7 @@ if (!eventName || eventName.startsWith("/") || eventName.includes("=")) { const csv = attendees.map((attendee) => { return `${attendee.email},${attendee.event},${attendee.stage}`; }); - writeFileSync("attendees.csv", `Email,Event,Stage\n${csv.join("\n")}`); + writeFileSync(`attendees-${eventName}.csv`, `Email,Event,Stage\n${csv.join("\n")}`); } catch (e) { console.error(e); } From ccf68473abd3ea2d9b86a36e394d11860eae7f56 Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Wed, 30 Oct 2024 13:40:25 +0100 Subject: [PATCH 30/62] add "forget me" command to debug easier --- src/app/webhooks/conversations/route.ts | 18 +++++++++++++++++- src/lib/templates.ts | 4 ++++ src/lib/twilio.ts | 23 +++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/app/webhooks/conversations/route.ts b/src/app/webhooks/conversations/route.ts index cb9e392..8051d2e 100644 --- a/src/app/webhooks/conversations/route.ts +++ b/src/app/webhooks/conversations/route.ts @@ -14,6 +14,8 @@ import { updateSyncListItem, fetchSyncListItem, fetchSyncListItems, + removeSyncMapItem, + deleteConversation, } from "@/lib/twilio"; import { @@ -438,7 +440,21 @@ export async function POST(request: Request) { } const incomingMessage = incomingMessageBody.toLowerCase(); - if (incomingMessage.includes("help")) { + if (incomingMessage.includes("forget me")) { + // remove the user from the active customers map + await removeSyncMapItem(NEXT_PUBLIC_ACTIVE_CUSTOMERS_MAP, conversationSid); + + addMessageToConversation( + conversationSid, + templates.getForgotAttendeeMessage(), + ); + + sleep(1000); + //remove conversation from the conversations service + deleteConversation(conversationSid); + + return new Response("Forgot attendee", { status: 200 }); + } else if (incomingMessage.includes("help")) { const { contentSid, contentVariables } = await templates.getHelpMessage(event); addMessageToConversation(conversationSid, "", contentSid, contentVariables); diff --git a/src/lib/templates.ts b/src/lib/templates.ts index d7d9adf..b9f2f39 100644 --- a/src/lib/templates.ts +++ b/src/lib/templates.ts @@ -149,6 +149,10 @@ export function getNoMediaHandlerMessage() { return "Sorry, we don't support media messages. Please send a text message to order a drink on us."; } +export function getForgotAttendeeMessage() { + return "Your data has been removed from our system. Please send another message to start over."; +} + export function getInvalidEmailMessage() { return "Invalid email address. Please reply with a valid business email address."; } diff --git a/src/lib/twilio.ts b/src/lib/twilio.ts index dac3541..873e237 100644 --- a/src/lib/twilio.ts +++ b/src/lib/twilio.ts @@ -222,6 +222,19 @@ export async function updateSyncMapItem( return updatedData; } +export async function removeSyncMapItem( + syncMapUniqueName: string, + syncMapItemKey: string, +) { + const syncService = await getSyncService(); + const syncMap = await syncService.syncMaps()(syncMapUniqueName); + try { + return syncMap.syncMapItems(syncMapItemKey).remove(); + } catch (err: any) { + throw new Error("Remove a syncMap record failed", { cause: err }); + } +} + export async function findSyncMapItems( syncMapUniqueName: string, filters: any = {}, @@ -308,6 +321,16 @@ export async function getConversationService() { return conversationsClient.fetch(); } +export async function deleteConversation(conversationSid: string) { + if (!TWILIO_CONVERSATIONS_SERVICE_SID) { + throw new Error("Missing sid for for conversations service"); + } + const client = twilio(TWILIO_API_KEY, TWILIO_API_SECRET, { + accountSid: TWILIO_ACCOUNT_SID, + }); + return client.conversations.v1.conversations(conversationSid).remove(); +} + export async function getPossibleSenders() { "use server"; const messagingService = await getMessagingService(); From 05aa68665c47187f91cbedb04dea1922baee1ed1 Mon Sep 17 00:00:00 2001 From: Marius Obert Date: Wed, 30 Oct 2024 14:36:03 +0100 Subject: [PATCH 31/62] Add a kiosk UI to order new items --- .env.example | 1 + README.md | 1 + .../event/[slug]/kiosk/order-form.tsx | 168 ++++++++++++++++++ .../event/[slug]/kiosk/page.tsx | 47 +++++ .../orders/custom-order-popover-content.tsx | 148 +-------------- src/app/api/order/route.ts | 6 +- src/middleware.ts | 4 + 7 files changed, 234 insertions(+), 141 deletions(-) create mode 100644 src/app/(master-layout)/event/[slug]/kiosk/order-form.tsx create mode 100644 src/app/(master-layout)/event/[slug]/kiosk/page.tsx diff --git a/.env.example b/.env.example index d6c3b2e..11ca97d 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ MIXOLOGIST_LOGIN=someuser:somepassword ADMIN_LOGIN=someadmin:otherpassword +KIOSK_LOGIN=somekiosk:somepassword SERVICE_INSTANCE_PREFIX=Mixologist UNLIMITED_ORDERS=CommaSeparatedNumbersToWhichTheLimitDoesNotApply diff --git a/README.md b/README.md index 6885637..d763444 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ The current [Twilio Channels] are: # Application related values MIXOLOGIST_LOGIN=someuser:assword ADMIN_LOGIN=someadmin:password + KIOSK_LOGIN=somekiosk:somepassword SERVICE_INSTANCE_PREFIX=Mixologist ACTIVE_CUSTOMERS_MAP=ActiveCustomers UNLIMITED_ORDERS=CommaSeparatedNumbersToWhichTheLimitDoesNotApply diff --git a/src/app/(master-layout)/event/[slug]/kiosk/order-form.tsx b/src/app/(master-layout)/event/[slug]/kiosk/order-form.tsx new file mode 100644 index 0000000..1152f28 --- /dev/null +++ b/src/app/(master-layout)/event/[slug]/kiosk/order-form.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { Textarea } from "@/components/ui/text-area"; +import { Button } from "@/components/ui/button"; +import { useState } from "react"; +import { Selection } from "../../../../../components/menu-select"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import MultiSelect from "@/components/ui/multi-select"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { useToast } from "@/components/ui/use-toast"; + +export default function OrderForm({ + selection, + eventSlug, + orderCreated, + askForSender, + showToast +}: { + selection: Selection; + eventSlug: string; + askForSender: boolean; + showToast: boolean; + orderCreated?: () => void; +}) { + const [pending, setPending] = useState(false); + + const { toast } = useToast(); + const [order, setOrder] = useState({ + item: {}, + name: "", + modifiers: [], + note: "", + }); + + return ( +
) => { + setPending(true); + e.preventDefault(); + + const body = { + event: eventSlug, + order: { + status: "queued", + key: `${new Date().toISOString()}`, //Replace with UUID at some point + manual: true, + address: "Manual Order", + name: order?.name, + item: order.item, + //@ts-ignore + originalText: `Manual Order: ${order?.name} ordered ${order?.item?.shortTitle} ${order.modifiers?.length > 0 ? `with ${order?.modifiers?.map((m) => m?.value).join(", ")}` : ""} ${order?.note && `with note: ${order?.note}`}`, + }, + }; + + if (order?.modifiers?.length > 0) { + //@ts-ignore + body.order.modifiers = order?.modifiers.map( + //@ts-ignore + (modifier) => modifier?.value, + ); + } + + fetch("/api/order", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }).then((res) => { + setPending(false); + if (res.ok) { + orderCreated && orderCreated(); + setOrder({ + item: {}, + name: "", + modifiers: [], + note: "", + }); + showToast && toast({ + title: "Order Created", + // @ts-ignore + description: `Your order for a ${order.item.title} has been created`, + }); + } + }); + }} + > + + ) => + setOrder({ ...order, name: e.target.value }) + } + /> + + + + {selection.modifiers.length > 0 && ( + <> + + { + return { label: m, value: m }; + })} + onChange={(selected) => { + //@ts-ignore + setOrder({ ...order, modifiers: selected }); + }} + /> + + )} + + +