Compare commits

..

3 Commits

Author SHA1 Message Date
RaviAnand Mohabir da835189c5 feat: display menu item tags 3 months ago
RaviAnand Mohabir c5853f1f55 feat: 💄 add margin below images 3 months ago
RaviAnand Mohabir a5b8f491eb feat: add feature toggles to homepage
- Add take-away and delivery options
- Update responsive styling
3 months ago

@ -8,7 +8,7 @@ export const getMenuCategories = async (
return payload.find({ collection: "menu-category", pagination: false, ...opts }); return payload.find({ collection: "menu-category", pagination: false, ...opts });
}; };
export const getMenuItems = async (opts: Omit<Options<"menu-item">, "collection">) => { export const getMenuItems = async (opts: Omit<Options<"menu-item">, "collection" | "depth">) => {
const payload = await getPayload(); const payload = await getPayload();
return payload.find({ collection: "menu-item", ...opts }); return payload.find({ collection: "menu-item", depth: 1, ...opts });
}; };

@ -1,10 +1,10 @@
import { Cigarette, PawPrint } from "lucide-react"; import { Cigarette, DoorOpen, PawPrint, Truck } from "lucide-react";
import { Container, HStack, Stack } from "@styled-system/jsx"; import { Container, Stack } from "@styled-system/jsx";
import FeatureToggle from "@/components/general/feature-toggle";
import { Metadata } from "next"; import { Metadata } from "next";
import { Params } from "../shared"; import { Params } from "../shared";
import RichText from "@/components/rich-text"; import RichText from "@/components/rich-text";
import { Text } from "@/components/ui/text";
import { getAbout } from "@/api"; import { getAbout } from "@/api";
import { getI18n } from "@/i18n/server"; import { getI18n } from "@/i18n/server";
@ -17,18 +17,14 @@ export default async function About({ params: { locale } }: { params: Params })
<Stack gap={10}> <Stack gap={10}>
<RichText content={about.text!} /> <RichText content={about.text!} />
<Stack> <Stack>
{about.dogsAllowed && ( <FeatureToggle
<HStack alignItems="center"> icon={PawPrint}
<PawPrint /> feature={about.dogsAllowed}
<Text>{t("about.dogsAllowed")}</Text> label={t("about.dogsAllowed")}
</HStack> />
)} <FeatureToggle icon={Cigarette} feature={about.fumoire} label={t("about.fumoire")} />
{about.fumoire && ( <FeatureToggle icon={Truck} feature={about.delivery} label={t("about.delivery")} />
<HStack alignItems="center"> <FeatureToggle icon={DoorOpen} feature={about.takeAway} label={t("about.takeAway")} />
<Cigarette />
<Text>{t("about.fumoire")}</Text>
</HStack>
)}
</Stack> </Stack>
</Stack> </Stack>
</Container> </Container>

@ -59,14 +59,15 @@ export default async function Contact({
<Heading as="h3" size="xl"> <Heading as="h3" size="xl">
{t("general.address")} {t("general.address")}
</Heading> </Heading>
<HStack gap={12} alignItems="start"> <HStack gap={12} alignItems="start" flexWrap="wrap">
<Address locale={locale} /> <Address locale={locale} />
<ContactMethods locale={locale} /> <ContactMethods locale={locale} />
</HStack> </HStack>
{contact.address.embeddedMaps && ( {contact.address.embeddedMaps && (
<styled.div <styled.div
dangerouslySetInnerHTML={{ __html: contact.address.embeddedMaps }} dangerouslySetInnerHTML={{ __html: contact.address.embeddedMaps }}
width={600} maxW={600}
width="100%"
/> />
)} )}
</Stack> </Stack>

@ -1,11 +1,9 @@
import { Box, HStack, Stack } from "@styled-system/jsx"; import { HStack, Stack } from "@styled-system/jsx";
import type { Media, MenuItemTag as MenuItemTagT } from "@/payload-types";
import { HoverCard } from "@/components/ui/hover-card";
import { IconButton } from "@/components/ui/icon-button";
import Image from "next/image";
import { Image as ImageIcon } from "lucide-react";
import { Locale } from "@/i18n/settings"; import { Locale } from "@/i18n/settings";
import { Media } from "@/payload-types"; import MenuItemImage from "@/app/(frontend)/[locale]/menu/menu-item-image";
import MenuItemTag from "./menu-item-tag";
import RichText from "@/components/rich-text"; import RichText from "@/components/rich-text";
import { TabContentBaseProps } from "@ark-ui/react"; import { TabContentBaseProps } from "@ark-ui/react";
import { Tabs } from "@/components/ui/tabs"; import { Tabs } from "@/components/ui/tabs";
@ -30,45 +28,27 @@ export default async function CategoryTabContent({
{menuItems.docs.map((mi) => ( {menuItems.docs.map((mi) => (
<HStack key={mi.id} alignItems="start"> <HStack key={mi.id} alignItems="start">
<Stack marginRight="auto"> <Stack marginRight="auto">
<HStack> <HStack alignItems="start">
<Text>{mi.name}</Text> <Text>{mi.name}</Text>
{mi.image && ( <HStack flexWrap="wrap">
<HoverCard.Root> {mi.tags?.map((tag) => (
<HoverCard.Trigger asChild> <MenuItemTag key={(tag as MenuItemTagT).id} tag={tag as MenuItemTagT} />
<IconButton variant="ghost"> ))}
<ImageIcon /> </HStack>
</IconButton> {mi.image && <MenuItemImage image={mi.image as Media} />}
</HoverCard.Trigger>
<HoverCard.Positioner>
<HoverCard.Content>
<HoverCard.Arrow>
<HoverCard.ArrowTip />
</HoverCard.Arrow>
<Box w={250} h={150} position="relative">
<Image
src={(mi.image as Media).url!}
alt={(mi.image as Media).alt ?? ""}
className={css({ objectFit: "cover" })}
fill
/>
</Box>
</HoverCard.Content>
</HoverCard.Positioner>
</HoverCard.Root>
)}
</HStack> </HStack>
{mi.description && ( {mi.description && (
<RichText content={mi.description} className={css({ color: "fg.muted" })} /> <RichText content={mi.description} className={css({ color: "fg.muted" })} />
)} )}
</Stack> </Stack>
<HStack justify="end"> <Stack css={{ sm: { justifyContent: "end", flexDir: "row" } }}>
{mi.variants.map((v) => ( {mi.variants.map((v) => (
<Stack key={v.id} align="end"> <Stack key={v.id} align="end">
<Text>{formatToCHF(v.price!)}</Text> <Text>{formatToCHF(v.price!)}</Text>
<Text color="fg.muted">{v.title}</Text> <Text color="fg.muted">{v.title}</Text>
</Stack> </Stack>
))} ))}
</HStack> </Stack>
</HStack> </HStack>
))} ))}
</Stack> </Stack>

@ -0,0 +1,34 @@
import { Box } from "@styled-system/jsx";
import { HoverCard } from "@ark-ui/react";
import { IconButton } from "@/components/ui/icon-button";
import Image from "next/image";
import { ImageIcon } from "lucide-react";
import { Media } from "@/payload-types";
import { css } from "@styled-system/css";
export default function MenuItemImage({ image }: { image: Media }) {
return (
<HoverCard.Root>
<HoverCard.Trigger asChild>
<IconButton variant="ghost">
<ImageIcon />
</IconButton>
</HoverCard.Trigger>
<HoverCard.Positioner>
<HoverCard.Content>
<HoverCard.Arrow>
<HoverCard.ArrowTip />
</HoverCard.Arrow>
<Box w={250} h={150} position="relative">
<Image
src={(image as Media).url!}
alt={(image as Media).alt ?? ""}
className={css({ objectFit: "cover" })}
fill
/>
</Box>
</HoverCard.Content>
</HoverCard.Positioner>
</HoverCard.Root>
);
}

@ -0,0 +1,41 @@
import { Box, HStack, Stack } from "@styled-system/jsx";
import { Media, MenuItemTag } from "@/payload-types";
import { HoverCard } from "@/components/ui/hover-card";
import { IconButton } from "@/components/ui/icon-button";
import Image from "next/image";
import { Image as ImageIcon } from "lucide-react";
import { Locale } from "@/i18n/settings";
import MenuItemImage from "@/app/(frontend)/[locale]/menu/menu-item-image";
import RichText from "@/components/rich-text";
import { TabContentBaseProps } from "@ark-ui/react";
import { Tabs } from "@/components/ui/tabs";
import { Text } from "@/components/ui/text";
import { css } from "@styled-system/css";
import { formatToCHF } from "@/utils/formatters";
import { getMenuItems } from "@/api";
export default function MenuItemTag({ tag }: { tag: MenuItemTag }) {
return (
<HoverCard.Root>
<HoverCard.Trigger asChild>
<Box bg="accent.a8" color="white" fontSize="xs" borderRadius="md" p={1}>
{(tag as MenuItemTag).name}
</Box>
</HoverCard.Trigger>
<HoverCard.Positioner>
<HoverCard.Content p={2} pb={0.5}>
<HoverCard.Arrow>
<HoverCard.ArrowTip />
</HoverCard.Arrow>
{(tag as MenuItemTag).description !== undefined && (
<RichText
content={(tag as MenuItemTag).description}
className={css({ fontSize: "sm" })}
/>
)}
</HoverCard.Content>
</HoverCard.Positioner>
</HoverCard.Root>
);
}

@ -1,5 +1,5 @@
import { Box, Container, Stack, styled } from "@styled-system/jsx"; import { Box, Container, HStack, Stack } from "@styled-system/jsx";
import { Info, Palmtree, PartyPopper } from "lucide-react"; import { Cigarette, DoorOpen, Info, Palmtree, PartyPopper, PawPrint, Truck } from "lucide-react";
import { import {
getAbout, getAbout,
getCurrentAnnouncements, getCurrentAnnouncements,
@ -9,6 +9,7 @@ import {
} from "@/api"; } from "@/api";
import { Alert } from "@/components/ui/alert"; import { Alert } from "@/components/ui/alert";
import FeatureToggle from "@/components/general/feature-toggle";
import Gallery from "./gallery"; import Gallery from "./gallery";
import { Heading } from "@/components/ui/heading"; import { Heading } from "@/components/ui/heading";
import Image from "next/image"; import Image from "next/image";
@ -45,14 +46,10 @@ export default async function Home({ params: { locale } }: { params: Params }) {
maxH="80%" maxH="80%"
align="center" align="center"
px={30} px={30}
gap={24} gap={20}
color="white" color="white"
> >
<Stack <Stack maxW={550} width="100%" className={scrollable({ hideScrollbar: false, px: 2 })}>
maxW={550}
width="100%"
className={scrollable({ hideScrollbar: false, px: 2 })}
>
{announcements.docs.length > 0 && ( {announcements.docs.length > 0 && (
<Heading size="xl" color="white"> <Heading size="xl" color="white">
{t("general.announcements")} {t("general.announcements")}
@ -96,6 +93,16 @@ export default async function Home({ params: { locale } }: { params: Params }) {
<Text color="white" maxW={550} width="100%" textAlign="center" fontSize={36}> <Text color="white" maxW={550} width="100%" textAlign="center" fontSize={36}>
{home.tagline} {home.tagline}
</Text> </Text>
<HStack justify="center" flexWrap="wrap">
<FeatureToggle
icon={PawPrint}
feature={about.dogsAllowed}
label={t("about.dogsAllowed")}
/>
<FeatureToggle icon={Cigarette} feature={about.fumoire} label={t("about.fumoire")} />
<FeatureToggle icon={Truck} feature={about.delivery} label={t("about.delivery")} />
<FeatureToggle icon={DoorOpen} feature={about.takeAway} label={t("about.takeAway")} />
</HStack>
</Stack> </Stack>
</Box> </Box>
<Container> <Container>

@ -1,15 +1,15 @@
import { Box, BoxProps } from "@styled-system/jsx";
import { getAbout, getContact } from "@/api"; import { getAbout, getContact } from "@/api";
import { Box } from "@styled-system/jsx";
import { Locale } from "@/i18n/settings"; import { Locale } from "@/i18n/settings";
import { Text } from "@/components/ui/text"; import { Text } from "@/components/ui/text";
export default async function Address({ locale }: { locale: Locale }) { export default async function Address({ locale, ...props }: { locale: Locale } & BoxProps) {
const about = await getAbout({ locale }); const about = await getAbout({ locale });
const contact = await getContact({ locale }); const contact = await getContact({ locale });
return ( return (
<Box> <Box {...props}>
<Text fontWeight="bold">{about.name}</Text> <Text fontWeight="bold">{about.name}</Text>
<Text> <Text>
{contact.address.street} {contact.address.number} {contact.address.street} {contact.address.number}

@ -1,16 +1,17 @@
import { Grid } from "@styled-system/jsx"; import { Grid, GridProps } from "@styled-system/jsx";
import { Link } from "@/components/ui/link"; import { Link } from "@/components/ui/link";
import { Locale } from "@/i18n/settings"; import { Locale } from "@/i18n/settings";
import { Text } from "@/components/ui/text"; import { Text } from "@/components/ui/text";
import { getContact } from "@/api"; import { getContact } from "@/api";
import { getI18n } from "@/i18n/server"; import { getI18n } from "@/i18n/server";
export default async function ContactMethods({ locale }: { locale: Locale }) { export default async function ContactMethods({ locale, ...props }: { locale: Locale } & GridProps) {
const t = await getI18n(); const t = await getI18n();
const contact = await getContact({ locale }); const contact = await getContact({ locale });
return ( return (
<Grid gridTemplateColumns="min-content 1fr" columnGap={2} rowGap={0.5}> <Grid gridTemplateColumns="min-content 1fr" columnGap={2} rowGap={0.5} {...props}>
{contact.phone && ( {contact.phone && (
<> <>
<Text fontWeight="bold">{t("general.phoneNumber")}</Text> <Text fontWeight="bold">{t("general.phoneNumber")}</Text>

@ -0,0 +1,20 @@
import { HStack } from "@styled-system/jsx";
import React from "react";
import { Text } from "@/components/ui/text";
type FeatureToggleProps = {
feature: boolean;
icon: React.ComponentType;
label: string;
};
export default function FeatureToggle({ feature, icon: Icon, label }: FeatureToggleProps) {
return (
feature && (
<HStack alignItems="center">
<Icon />
<Text>{label}</Text>
</HStack>
)
);
}

@ -18,9 +18,15 @@ export default async function Footer({ locale }: { locale: Locale }) {
return ( return (
<styled.footer minH={60} p={8} className={stack({ gap: 4, justify: "end" })}> <styled.footer minH={60} p={8} className={stack({ gap: 4, justify: "end" })}>
<HStack justify="space-between" alignItems="start" flexWrap="wrap"> <Stack
<Address locale={locale} /> css={{
<HStack> flexDir: "column",
alignItems: "center",
sm: { flexDir: "row", alignItems: "start" },
}}
>
<Address locale={locale} flex={1} />
<HStack flex={1} justify="center">
{(contact.socialLinks ?? []).map((sl) => ( {(contact.socialLinks ?? []).map((sl) => (
<IconButton key={sl.id} asChild variant="link"> <IconButton key={sl.id} asChild variant="link">
<a href={sl.link}> <a href={sl.link}>
@ -29,8 +35,10 @@ export default async function Footer({ locale }: { locale: Locale }) {
</IconButton> </IconButton>
))} ))}
</HStack> </HStack>
<ContactMethods locale={locale} /> <HStack flex={1} justify="end">
</HStack> <ContactMethods locale={locale} />
</HStack>
</Stack>
<Stack alignSelf="center" gap={1}> <Stack alignSelf="center" gap={1}>
<Heading size="lg">{t("general.openingTimes")}</Heading> <Heading size="lg">{t("general.openingTimes")}</Heading>
<OpeningTimes locale={locale} /> <OpeningTimes locale={locale} />

@ -162,7 +162,7 @@ export function serializeLexical({ nodes }: Props): JSX.Element {
const media = node.value as Media; const media = node.value as Media;
return ( return (
<styled.div textAlign="center"> <styled.div textAlign="center" mb={4}>
<Image <Image
height={250} height={250}
width={500} width={500}

@ -31,5 +31,15 @@ export const About: GlobalConfig = {
type: "checkbox", type: "checkbox",
required: true, required: true,
}, },
{
name: "delivery",
type: "checkbox",
required: true,
},
{
name: "takeAway",
type: "checkbox",
required: true,
},
], ],
}; };

@ -31,6 +31,8 @@ export default {
about: { about: {
dogsAllowed: "Hunde erlaubt", dogsAllowed: "Hunde erlaubt",
fumoire: "Fumoire verfügbar", fumoire: "Fumoire verfügbar",
takeAway: "Take-away",
delivery: "Lieferung",
}, },
contact: { contact: {
name: "Name", name: "Name",

@ -349,6 +349,8 @@ export interface About {
} | null; } | null;
dogsAllowed: boolean; dogsAllowed: boolean;
fumoire: boolean; fumoire: boolean;
delivery: boolean;
takeAway: boolean;
updatedAt?: string | null; updatedAt?: string | null;
createdAt?: string | null; createdAt?: string | null;
} }

Loading…
Cancel
Save