Compare commits

...

7 Commits

@ -14,16 +14,20 @@
"lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start",
"format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx}\""
"format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx}\"",
"email": "email dev --dir src/emails --port 4000"
},
"dependencies": {
"@ark-ui/react": "^3.9.0",
"@hookform/resolvers": "^3.9.0",
"@icons-pack/react-simple-icons": "^10.0.0",
"@payloadcms/db-mongodb": "beta",
"@payloadcms/next": "beta",
"@payloadcms/plugin-cloud": "beta",
"@payloadcms/richtext-lexical": "beta",
"@payloadcms/translations": "beta",
"@react-email/components": "0.0.23",
"@react-email/render": "1.0.0",
"cross-env": "^7.0.3",
"graphql": "^16.8.1",
"lucide-react": "^0.436.0",
@ -32,7 +36,10 @@
"payload": "beta",
"react": "19.0.0-rc-06d0b89e-20240801",
"react-dom": "19.0.0-rc-06d0b89e-20240801",
"sharp": "0.32.6"
"react-email": "3.0.1",
"react-hook-form": "^7.53.0",
"sharp": "0.32.6",
"zod": "^3.23.8"
},
"devDependencies": {
"@pandacss/dev": "^0.45.1",

@ -1,12 +0,0 @@
"use server";
export const submitContactFormAction = (formData: FormData) => {
console.log(JSON.stringify(formData, null, 2));
console.log(formData.get("message"));
console.log(formData.get("subject"));
console.log(formData.get("date"));
console.log(formData.get("time"));
console.log(formData.get("name"));
console.log(formData.get("email"));
console.log(formData.get("phone"));
};

@ -0,0 +1,53 @@
"use server";
import { getI18n } from "@/i18n/server";
import { getPayload } from "@/utils/payload";
import { getSettings } from "@/api";
import { renderContactConfirmationEmail } from "@/emails/contact-confirmation";
import { renderContactEmail } from "@/emails/contact";
export const submitContactFormAction = async (formData: FormData) => {
const payload = await getPayload();
const { adminLanguage, contactEmailsTo } = await getSettings();
const t = await getI18n();
console.log(
await renderContactEmail(
{
name: formData.get("name"),
email: formData.get("email"),
subject: formData.get("subject"),
message: formData.get("message"),
locale: adminLanguage,
},
{ plainText: true },
),
);
await payload.sendEmail({
to: contactEmailsTo,
subject: t("email.contactSubject", { name: formData.get("name") }),
email: await renderContactEmail(
{
name: formData.get("name"),
email: formData.get("email"),
subject: formData.get("subject"),
message: formData.get("message"),
locale: adminLanguage,
},
{ plainText: true },
),
});
await payload.sendEmail({
to: formData.get("email"),
subject: `Bestätigung Ihrer Kontaktanfrage ${formData.get("name")}`,
email: await renderContactConfirmationEmail({
name: formData.get("name"),
email: formData.get("email"),
subject: formData.get("subject"),
message: formData.get("message"),
locale: adminLanguage,
}),
});
};

@ -1,12 +0,0 @@
"use server";
export const submitReservationFormAction = (formData: FormData) => {
console.log(JSON.stringify(formData, null, 2));
console.log(formData.get("message"));
console.log(formData.get("subject"));
console.log(formData.get("date"));
console.log(formData.get("time"));
console.log(formData.get("name"));
console.log(formData.get("email"));
console.log(formData.get("phone"));
};

@ -0,0 +1,22 @@
"use server";
import { getPayload } from "@/utils/payload";
export const submitReservationFormAction = async (formData: FormData) => {
const payload = await getPayload();
await payload.sendEmail({
to: "moravrav@gmail.com",
subject: `Reservation von ${formData.get("name")}`,
text: `Sie haben eine Reservation von ${formData.get("name")} erhalten:
Datum / Uhrzeit: ${formData.get("date")} / ${formData.get("date")}
Gäste: ${formData.get("guests")}
Nachricht: ${formData.get("message")}
Kontaktdaten:
- E-Mail: ${formData.get("email")}
- Telefonnummer: ${formData.get("phone")}
`,
});
};

@ -3,4 +3,5 @@ export * from "./contact";
export * from "./gallery";
export * from "./home";
export * from "./menu";
export * from "./settings";

@ -0,0 +1,7 @@
import type { Options } from "node_modules/payload/dist/globals/operations/local/findOne";
import { getPayload } from "@/utils/payload";
export const getSettings = async (opts: Omit<Options<"settings">, "slug"> = {}) => {
const payload = await getPayload();
return await payload.findGlobal({ slug: "settings", ...opts });
};

@ -1,12 +1,10 @@
import { Box, HStack, Stack } from "@styled-system/jsx";
import { getAbout, getContact } from "@/api";
import ContactForm from "@/app/(frontend)/[locale]/contact/contact-form";
import { Field } from "@/components/ui/field";
import ContactForm from "./contact-form";
import { Heading } from "@/components/ui/heading";
import { Input } from "@/components/ui/input";
import { Params } from "../shared";
import ReservationForm from "@/app/(frontend)/[locale]/contact/reservation-form";
import ReservationForm from "./reservation-form";
import { Tabs } from "@/components/ui/tabs";
import { Text } from "@/components/ui/text";
import { getI18n } from "@/i18n/server";
@ -69,7 +67,7 @@ export default async function Contact({ params: { locale } }: { params: Params }
<Heading as="h3" size="xl">
{t("general.address")}
</Heading>
<HStack gap={20}>
<HStack gap={20} flexWrap="wrap">
{contact.address.embeddedMaps && (
<styled.div
dangerouslySetInnerHTML={{ __html: contact.address.embeddedMaps }}

@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button";
import DateTimePicker from "./date-time-picker";
import { Field } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { NumberInput } from "@/components/ui/number-input";
import { getI18n } from "@/i18n/server";
import { stack } from "@styled-system/patterns";
import { styled } from "@styled-system/jsx";
@ -31,7 +32,7 @@ export default async function ReservationForm() {
<DateTimePicker />
<Field.Root id="guests">
<Field.Label>{t("reservation.guests")}</Field.Label>
<Field.Input name="guests" placeholder={t("reservation.guests")} />
<NumberInput name="guests" />
</Field.Root>
<Field.Root id="message">
<Field.Label>{t("contact.message")}</Field.Label>

@ -1,5 +1,7 @@
import "../../globals.css";
import { Locale, locales } from "@/i18n/settings";
import Footer from "@/components/layout/footer";
import { I18nProviderClient } from "@/i18n/client";
import { Metadata } from "next";
@ -7,7 +9,6 @@ import Navbar from "@/components/layout/navbar";
import { Params } from "./shared";
import { getAbout } from "@/api";
import localFont from "next/font/local";
import { locales } from "@/i18n/settings";
import { styled } from "@styled-system/jsx";
const moderustic = localFont({
@ -37,8 +38,10 @@ export default async function RootLayout({
);
}
export async function generateStaticParams() {
return locales.map((locale) => ({ locale }));
export async function generateStaticParams(): Promise<{ locale: Locale }[]> {
return locales.map(({ code }) => ({
locale: code,
}));
}
export async function generateMetadata(): Promise<Metadata> {

@ -1,10 +1,10 @@
"use client";
import { defaultLocale, locales } from "@/i18n/settings";
import { useChangeLocale, useCurrentLocale, useI18n } from "@/i18n/client";
import { Button } from "@/components/ui/button";
import { Menu } from "@/components/ui/menu";
import { locales } from "@/i18n/settings";
export default function LanguagePicker(props: Menu.RootProps) {
const changeLocale = useChangeLocale();
@ -22,8 +22,13 @@ export default function LanguagePicker(props: Menu.RootProps) {
<Menu.Content right={0}>
<Menu.ItemGroup>
{locales.map((locale) => (
<Menu.Item asChild key={locale} value={locale} onClick={() => changeLocale(locale)}>
<Button>{t(`general.${locale}`)}</Button>
<Menu.Item
asChild
key={locale.code}
value={locale.code}
onClick={() => changeLocale(locale.code)}
>
<Button>{locale.label[locale.code] ?? locale.label[defaultLocale]}</Button>
</Menu.Item>
))}
</Menu.ItemGroup>

@ -0,0 +1,60 @@
import { Font, Head, Html, Preview, Tailwind, Text } from "@react-email/components";
import { I18nProviderClient, useI18n } from "@/i18n/client";
import { Options, render } from "@react-email/render";
import { defaultLocale } from "@/i18n/settings";
type ContactConfirmationEmailContentProps = {
name: string;
email: string;
subject: string;
message: string;
};
function ContactConfirmationEmailContent(_props: ContactConfirmationEmailContentProps) {
const t = useI18n();
return (
<Html>
<Head>
<title>{t("email.contactConfirmationSubject")}</title>
<Font
fontFamily="Roboto"
fallbackFontFamily="Verdana"
webFont={{
url: "https://fonts.googleapis.com/css2?family=Moderustic:wght@300..800&display=swap",
format: "woff2",
}}
fontWeight={400}
fontStyle="normal"
/>
</Head>
<Preview>{t("email.contactConfirmationSubject")}</Preview>
<Tailwind>
<Text>{t("email.contactConfirmationText")}</Text>
</Tailwind>
</Html>
);
}
type ContactConfirmationEmailProps = {
locale: string;
} & ContactConfirmationEmailContentProps;
export default function ContactConfirmationEmail({
locale = defaultLocale,
...props
}: ContactConfirmationEmailProps) {
return (
<I18nProviderClient locale={locale}>
<ContactConfirmationEmailContent {...props} />
</I18nProviderClient>
);
}
export async function renderContactConfirmationEmail(
props: ContactConfirmationEmailProps,
opts?: Options,
) {
return await render(<ContactConfirmationEmail {...props} />, opts);
}

@ -0,0 +1,75 @@
import {
Column,
Font,
Head,
Html,
Preview,
Row,
Section,
Tailwind,
Text,
} from "@react-email/components";
import { I18nProviderClient, useI18n } from "@/i18n/client";
import { Options, render } from "@react-email/render";
import { defaultLocale } from "@/i18n/settings";
type ContactEmailContentProps = { name: string; email: string; subject: string; message: string };
function ContactEmailContent({
name = "[[Name]]",
subject = "[[Subject]]",
email = "[[Email]]",
message = "[[Message]]",
}: ContactEmailContentProps) {
const t = useI18n();
return (
<Html>
<Head>
<title>{t("email.contactSubject", { name })}</title>
<Font
fontFamily="Roboto"
fallbackFontFamily="Verdana"
webFont={{
url: "https://fonts.googleapis.com/css2?family=Moderustic:wght@300..800&display=swap",
format: "woff2",
}}
fontWeight={400}
fontStyle="normal"
/>
</Head>
<Preview>{t("email.contactSubject", { name })}</Preview>
<Tailwind>
<Text>{t("email.contactTitle", { name })}</Text>
<Text>{subject}</Text>
<Section>
<Row>
<Column>{t("contact.name")}</Column>
<Column>{name}</Column>
</Row>
<Row>
<Column>{t("general.email")}</Column>
<Column>{email}</Column>
</Row>
</Section>
<Text>{t("contact.message")}</Text>
<Text>{message}</Text>
</Tailwind>
</Html>
);
}
type ContactEmailProps = { locale: string } & ContactEmailContentProps;
export default function ContactEmail({ locale = defaultLocale, ...props }: ContactEmailProps) {
return (
<I18nProviderClient locale={locale}>
<ContactEmailContent {...props} />
</I18nProviderClient>
);
}
export async function renderContactEmail(props: ContactEmailProps, opts?: Options) {
return await render(<ContactEmail {...props} />, opts);
}

@ -1,4 +1,3 @@
import { ContactEmbedNotice } from "@/globals/ContactEmbedNotice";
import type { GlobalConfig } from "payload";
export const Contact: GlobalConfig = {

@ -0,0 +1,22 @@
import { defaultLocale, locales } from "@/i18n/settings";
import type { GlobalConfig } from "payload";
export const Settings: GlobalConfig = {
slug: "settings",
access: {},
fields: [
{
name: "adminLanguage",
type: "select",
options: locales.map((l) => ({
value: l.code,
label: l.label[l.code] ?? l.label[defaultLocale],
})),
},
{
name: "contactEmailsTo",
type: "email",
},
],
};

@ -1,9 +1,7 @@
"use client";
import { createI18nClient } from "next-international/client";
import { importedLocales } from "./settings";
export const { useI18n, useScopedI18n, I18nProviderClient, useChangeLocale, useCurrentLocale } =
createI18nClient({
de: () => import("./de"),
en: () => import("./en"),
});
createI18nClient(importedLocales);

@ -10,6 +10,7 @@ export default {
openingTimes: "Öffnungszeiten",
date: "Datum",
time: "Zeit",
submit: "Absenden",
en: "Englisch",
de: "Deutsch",
fr: "Französisch",
@ -34,4 +35,14 @@ export default {
message: "Nachricht",
reservation: "Reservation",
},
reservation: {
guests: "Gäste",
},
email: {
contactConfirmationSubject: "Vielen Dank für Ihre Kontaktanfrage",
contactConfirmationText:
"Wir haben Ihre Kontaktanfrage erhalten und melden uns innerhalb kurzer Zeit bei Ihnen.",
contactSubject: "Kontaktanfrage von {name}",
contactTitle: "Sie haben eine Kontaktanfrage von {name} erhalten:",
},
} as const;

@ -10,6 +10,7 @@ export default {
phoneNumber: "Phone",
date: "Date",
time: "Time",
submit: "Submit",
en: "English",
de: "German",
fr: "French",

@ -0,0 +1 @@
export default {} as const;

@ -0,0 +1 @@
export default {} as const;

@ -1,6 +1,5 @@
import { createI18nServer } from "next-international/server";
import { importedLocales } from "./settings";
export const { getI18n, getScopedI18n, getStaticParams } = createI18nServer({
de: () => import("./de"),
en: () => import("./en"),
});
export const { getI18n, getScopedI18n, getStaticParams, getCurrentLocale } =
createI18nServer(importedLocales);

@ -1,4 +1,49 @@
export const defaultLocale = "de" as const;
export const locales = [defaultLocale, "en"] as const;
export const locales = [
{
label: {
de: "Deutsch",
en: "German",
fr: "Allemand",
it: "Tedesco",
},
code: "de",
},
{
label: {
fr: "Français",
en: "French",
de: "Französisch",
it: "Francese",
},
code: "fr",
},
{
label: {
it: "Italiano",
de: "Italienisch",
en: "Italian",
fr: "Italien",
},
code: "it",
},
{
label: {
de: "Englisch",
en: "English",
it: "Inglese",
fr: "Anglais",
},
code: "en",
},
] as const;
export type Locale = (typeof locales)[number];
export type Locale = (typeof locales)[number]["code"];
export const defaultLocale: Locale = "de";
export const importedLocales = {
de: () => import("./de"),
en: () => import("./en"),
it: () => import("./it"),
fr: () => import("./fr"),
} as const;

@ -4,7 +4,7 @@ import { NextRequest } from "next/server";
import { createI18nMiddleware } from "next-international/middleware";
const I18nMiddleware = createI18nMiddleware({
locales,
locales: locales.map(({ code }) => code),
defaultLocale,
urlMappingStrategy: "rewriteDefault",
});

@ -32,6 +32,7 @@ export interface Config {
about: About;
contact: Contact;
menu: Menu;
settings: Setting;
};
locale: 'de' | 'fr' | 'it' | 'en';
user: User & {
@ -359,6 +360,17 @@ export interface Menu {
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "settings".
*/
export interface Setting {
id: string;
adminLanguage?: ('de' | 'fr' | 'it' | 'en') | null;
contactEmailsTo?: string | null;
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".

@ -1,20 +1,22 @@
import { DefaultTranslationsObject, Language } from "@payloadcms/translations";
import { Locale, buildConfig } from "payload";
import { defaultLocale, locales } from "@/i18n/settings";
import { About } from "./globals/About";
import { Contact } from "./globals/Contact";
import { FoodDeclaration } from "./collections/FoodDeclaration";
import { About } from "@/globals/About";
import { Contact } from "@/globals/Contact";
import { FoodDeclaration } from "@/collections/FoodDeclaration";
import { Gallery } from "@/globals/Gallery";
import { Holiday } from "./collections/Holiday";
import { Home } from "./globals/Home";
import { Media } from "./collections/Media";
import { Menu } from "./globals/Menu";
import { MenuCategory } from "./collections/MenuCategory";
import { MenuItem } from "./collections/MenuItem";
import { MenuItemTag } from "./collections/MenuItemTag";
import { OpeningTime } from "./collections/OpeningTime";
import { Users } from "./collections/Users";
import { Vacation } from "./collections/Vacation";
import { buildConfig } from "payload";
import { Holiday } from "@/collections/Holiday";
import { Home } from "@/globals/Home";
import { Media } from "@/collections/Media";
import { Menu } from "@/globals/Menu";
import { MenuCategory } from "@/collections/MenuCategory";
import { MenuItem } from "@/collections/MenuItem";
import { MenuItemTag } from "@/collections/MenuItemTag";
import { OpeningTime } from "@/collections/OpeningTime";
import { Settings } from "@/globals/Settings";
import { Users } from "@/collections/Users";
import { Vacation } from "@/collections/Vacation";
import { de } from "@payloadcms/translations/languages/de";
import { en } from "@payloadcms/translations/languages/en";
import { fileURLToPath } from "url";
@ -47,7 +49,7 @@ export default buildConfig({
Vacation,
Holiday,
],
globals: [Home, Gallery, About, Contact, Menu],
globals: [Home, Gallery, About, Contact, Menu, Settings],
editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET || "",
typescript: {
@ -70,39 +72,8 @@ export default buildConfig({
},
},
localization: {
locales: [
{
label: {
de: "Deutsch",
en: "German",
},
code: "de",
},
{
label: {
fr: "Français",
en: "French",
de: "Französisch",
},
code: "fr",
},
{
label: {
it: "Italiano",
de: "Italienisch",
en: "Italian",
},
code: "it",
},
{
label: {
de: "Englisch",
en: "English",
},
code: "en",
},
],
defaultLocale: "de",
locales: locales as unknown as Locale[],
defaultLocale,
fallback: true,
},
});

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save