Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ Storybook snapshot testing + pipeline integration
Zod 4
Shadcn registry
GLobal open orders checker in Admin -> show toast, count badge over open orders menu icon
Server actions errors - show friendly error message in modal / parallel route -
Server actions errors - show friendly error message in modal / parallel route -
language switch in menu - non-free tiers
360 view?
pricing - nm scans a month
info icon on Price card
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "next-menu",
"version": "0.0.71",
"version": "0.0.72",
"private": true,
"type": "module",
"scripts": {
Expand Down
33 changes: 17 additions & 16 deletions src/app/(landing)/_components/LandingFeatures.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Airplay, Clock, Globe, ScanQrCode, Users, Utensils } from 'lucide-react';
import { Airplay, ChartPieIcon, Clock, PiggyBankIcon, ScanQrCode, Utensils } from 'lucide-react';
import React from 'react';
import { LandingSectionTitle } from '~/app/(landing)/_components/LandingSectionTitle';
import { sections } from '~/domain/landing-content';
Expand All @@ -12,35 +12,36 @@ export interface Feature {
export const LandingFeatures: React.FC = () => {
const features: Feature[] = [
{
title: 'Step into the QR age',
description: 'Create and manage QR-powered menus for your customers',
title: `Your own site - on us`,
description: `Once you set up your menus, you get a public link that hosts them - always online, ready to be shared, no web hosting required.`,
icon: <ScanQrCode className="text-pop h-6 w-6" />,
},
{
title: 'Handle take-out orders with ease',
description: 'Nunc eget tincidunt libero. Pellentesque fringilla congue nisi id lobortis.',
title: 'Always in sync',
description: `Publish updates to your menu items and prices any time - they're instantly reflected in your menus.`,
icon: <Utensils className="text-pop h-6 w-6" />,
},
{
title: 'Set up and manage kiosk screens',
description: 'Mauris turpis lectus, finibus eget gravida a, sollicitudin sit amet risus.',
title: 'No app required',
description:
'Customers that scan your QR codes are taken to your public menu page, that simply works on any mobile device.',
icon: <Airplay className="text-pop h-6 w-6" />,
},
{
title: 'See open orders in real time, anytime',
description: 'Donec ac lobortis enim, id pellentesque massa. Nulla quis enim ut elit consequat malesuada.',
title: 'Fully-automated ordering system',
description: `In interactive mode, guests can order items directly from your menu and can see instant updates for items marked as received at the table by your staff.`,
icon: <Clock className="text-pop h-6 w-6" />,
},
{
title: 'Team Collaboration',
description: 'Quisque tincidunt aliquam malesuada. Maecenas maximus purus ac metus congue viverra. ',
icon: <Users className="text-pop h-6 w-6" />,
title: 'No hidden costs',
description:
'No purchases of expensive terminals or app installs are required. Your guests and your staff only need a device with a browser - be it a phone, a tablet, a laptop or a desktop PC.',
icon: <PiggyBankIcon className="text-pop h-6 w-6" />,
},
{
title: 'Global Accessibility',
description:
'Suspendisse tincidunt diam non risus venenatis, ac efficitur velit sodales. Aenean blandit consequat elit in pellentesque.',
icon: <Globe className="text-pop h-6 w-6" />,
title: 'In-depth reports',
description: `How many guests opened your menus? What's your best selling menu item? What is never ordered? Easy - learn all these and much more from our reports page, included in all price plans!`,
icon: <ChartPieIcon className="text-pop h-6 w-6" />,
},
];

Expand Down
14 changes: 8 additions & 6 deletions src/app/(landing)/_components/LandingHero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ export const LandingHero: React.FC = () => {
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="lg:grid lg:grid-cols-12 lg:gap-8">
<div className="gap-4 sm:text-center md:mx-auto md:max-w-2xl lg:col-span-6 lg:flex lg:flex-col lg:justify-center lg:text-left">
<h1 className="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-5xl xl:text-6xl dark:text-gray-700">
<span className="block">Lorem ipsum your</span>
<span className="text-pop workflow block pb-6">dolor today!</span>
<h1 className="text-3xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-4xl xl:text-5xl dark:text-gray-700">
<span className="block">QR menus,</span>
<span className="text-pop workflow block pb-6 ml-3"> super-powered!</span>
</h1>
<p className="text-base font-light text-gray-500 sm:text-xl lg:text-lg xl:text-xl">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean vel sem leo. Nunc eget
tincidunt libero. Pellentesque fringilla congue nisi id lobortis. Nullam pharetra orci eros,
id interdum ipsum sagittis ac.
TheMenu is the ultimate QR code menu platform for restaurants, bars, cafes, food trucks and
hotels.
</p>
<p className="text-base font-light text-gray-500 sm:text-xl lg:text-lg xl:text-xl">
Ready to start accepting orders for dine-in, takeaway and delivery?
</p>
<div className="mt-4 h-[36px] sm:mx-auto sm:max-w-[270px] sm:text-center lg:mx-0 lg:text-left">
<GetStartedCta
Expand Down
19 changes: 9 additions & 10 deletions src/components/GetStartedCta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,15 @@ function SignedInCta(props: { tier?: PriceTierId; secondaryText?: string; varian
const isOnThisTier = userTier != null && userTier === props.tier;

if (isOnThisTier) {
return null;
// return (
// <div className="relative flex w-full flex-col gap-1">
// <Link href={ROUTES.my} className="w-full">
// <Button className="w-full" variant={props.variant}>
// Go to my account
// </Button>
// </Link>
// </div>
// );
return (
<div className="relative flex w-full flex-col gap-1">
<Link href={ROUTES.my} className="w-full">
<Button className="w-full" variant={props.variant}>
Go to my account
</Button>
</Link>
</div>
);
}

return (
Expand Down
20 changes: 19 additions & 1 deletion src/domain/price-tier-flags.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { z } from 'zod';
import { MENU_MODES } from '~/domain/menu-modes';

export const PriceTierFlagIdSchema = z.union([z.literal('reports'), z.literal('publicSite')]);
export const PriceTierFlagIdSchema = z.union([
z.literal('reports'),
z.literal('publicSite'),
z.literal('nonInteractiveMode'),
z.literal('interactiveMode'),
]);

export type PriceTierFlagId = z.infer<typeof PriceTierFlagIdSchema>;

Expand Down Expand Up @@ -31,4 +37,16 @@ export const priceTierFlags: Record<PriceTierFlagId, PriceTierFlag> = {
resourcePluralName: 'public site',
description: 'A public-facing professional-looking web site that your customers can use to browse your menus.',
},
nonInteractiveMode: {
id: 'nonInteractiveMode',
resourceSingularName: 'non-interactive mode',
resourcePluralName: 'non-interactive mode',
description: MENU_MODES.noninteractive.description,
},
interactiveMode: {
id: 'interactiveMode',
resourceSingularName: 'interactive mode',
resourcePluralName: 'interactive mode',
description: MENU_MODES.interactive.description,
},
};
18 changes: 13 additions & 5 deletions src/domain/price-tiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export const priceTiers: Record<PriceTierId, PriceTier> = {
flags: [
{ id: 'reports', isEnabled: true },
{ id: 'publicSite', isEnabled: true },
{ id: 'nonInteractiveMode', isEnabled: true },
{ id: 'interactiveMode', isEnabled: true },
],
},
start2: {
Expand All @@ -57,7 +59,7 @@ export const priceTiers: Record<PriceTierId, PriceTier> = {
description: 'Takes two minutes to get started',
monthlyUsdPrice: 0,
yearlyUsdPrice: 0,
isPublic: true,
isPublic: false,
isPopular: false,
features: [
{ id: 'locations', quota: 1 },
Expand All @@ -67,6 +69,8 @@ export const priceTiers: Record<PriceTierId, PriceTier> = {
flags: [
{ id: 'reports', isEnabled: true },
{ id: 'publicSite', isEnabled: true },
{ id: 'nonInteractiveMode', isEnabled: true },
{ id: 'interactiveMode', isEnabled: true },
],
},
pro: {
Expand All @@ -80,12 +84,14 @@ export const priceTiers: Record<PriceTierId, PriceTier> = {
isPopular: true,
features: [
{ id: 'locations', quota: 1 },
{ id: 'menus', quota: 1 },
{ id: 'menuItems', quota: 0 },
{ id: 'menus', quota: 10 },
{ id: 'menuItems', quota: 100 },
],
flags: [
{ id: 'reports', isEnabled: false },
{ id: 'publicSite', isEnabled: true },
{ id: 'nonInteractiveMode', isEnabled: true },
{ id: 'interactiveMode', isEnabled: true },
],
},
enterprise: {
Expand All @@ -99,12 +105,14 @@ export const priceTiers: Record<PriceTierId, PriceTier> = {
isPopular: false,
features: [
{ id: 'locations', quota: 1 },
{ id: 'menus', quota: 100 },
{ id: 'menuItems', quota: 14 },
{ id: 'menus', quota: 50 },
{ id: 'menuItems', quota: 1000 },
],
flags: [
{ id: 'reports', isEnabled: true },
{ id: 'publicSite', isEnabled: true },
{ id: 'nonInteractiveMode', isEnabled: true },
{ id: 'interactiveMode', isEnabled: true },
],
},
custom1: {
Expand Down
10 changes: 0 additions & 10 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,6 @@ export default clerkMiddleware(
}
}

// For auth-protected routes, ensure we have a last location cookie
if (isAuthProtectedRoute(req)) {
const machineId = req.cookies.get(CookieKey.CurrentLocationId)?.value;
if (!machineId) {
const anonResponse = NextResponse.next();
anonResponse.cookies.set(CookieKey.MachineId, crypto.randomUUID(), cookieOptions);
return anonResponse;
}
}

// For public routes, ensure we have a machineId cookie
if (!isAuthProtectedRoute(req)) {
const machineId = req.cookies.get(CookieKey.MachineId);
Expand Down