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
151 changes: 151 additions & 0 deletions src/app/(landing)/[locale]/actions/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { ActionTriggerView } from "@/components/ActionTriggerView"
import { ActionCard } from "@/components/cards/ActionCard"
import { Aurora } from "@/components/ui/Aurora"
import { HapticButtonLink } from "@/components/ui/HapticButtonLink"
import { LandingContainer } from "@/components/ui/LandingContainer"
import { LinkButton } from "@/components/ui/LinkButton"
import { getActionBySlug, getActionSlugs, getLandingPage, type ActionsLayoutBlock } from "@/lib/cms"
import { SUPPORTED_LOCALES, isSupportedLocale } from "@/lib/i18n"
import { createMetadata } from "@/lib/siteConfig"
import type { Media } from "@/payload-types"
import { IconArrowLeft, IconExternalLink } from "@tabler/icons-react"
import type { Metadata } from "next"
import Image from "next/image"
import { notFound } from "next/navigation"

export default async function ActionDetailPage({ params }: { params: Promise<{ locale: string, slug: string }> }) {
const { locale, slug } = await params
if (!isSupportedLocale(locale)) notFound()
if (!slug?.trim()) notFound()

const action = await getActionBySlug(slug, locale)
if (!action) notFound()
const actionsPage = await getLandingPage("actions", locale)

const icon = action.icon as Media | undefined
const triggers = action.trigger as Media | undefined
const functionDefs = action.functiondefinitions as Media | undefined
const references = (action.references ?? []).filter((reference): reference is Exclude<typeof reference, number> => typeof reference !== "number")
const tags = (action.tags ?? []).filter((tag): tag is string => Boolean(tag))
const actionsBlock = actionsPage?.layout?.find((block): block is ActionsLayoutBlock => block.blockType === "actions") ?? null
const referencesLabel = actionsBlock?.referencesLabel ?? (locale === "de" ? "Referenzen" : "References")

return (
<>
<Aurora />
<LandingContainer className="pt-32">
<div className="mx-auto flex w-full max-w-4xl flex-col gap-8">
<LinkButton
href={`/${locale}/actions`}
showArrow={false}
className="border-0 hover:bg-white/10 pl-2.5 pr-4 py-1 rounded-xl hover:text-white"
>
<IconArrowLeft size={16} />
{locale === "de" ? "Zurück" : "Back"}
</LinkButton>

<div className="flex flex-col gap-8">

<div className="relative z-10 flex flex-col gap-8">
<div className="flex flex-col gap-4 sm:flex-row sm:justify-between">

<div className="flex flex-col gap-6 sm:flex-row sm:items-start">
{icon?.url && (
<div className="relative size-20 shrink-0 overflow-hidden rounded-3xl border border-white/10 bg-white/5">
<Image
src={icon.url}
alt={icon.alt ?? action.title}
fill
sizes="80px"
className="object-contain p-2"
/>
</div>
)}

<div className="flex flex-col min-w-0 flex-1 gap-2">
<h1 className="mt-1 text-3xl font-semibold tracking-tight text-white sm:text-4xl">{action.title}</h1>
{tags.length > 0 && (
<div className="flex flex-wrap gap-2">
{tags.map((tag) => (
<span
key={tag}
className="rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs text-white/70"
>
{tag}
</span>
))}
</div>
)}
</div>
</div>

{action.documentation?.label && action.documentation?.url && (
<HapticButtonLink
href={ action.documentation.url}
variant="normal"
className="text-sm!"
>
<IconExternalLink size={16}/>
{action.documentation.label}
</HapticButtonLink>
)}

</div>
{action.description && (
<div className="max-w-3xl whitespace-pre-line text-sm leading-6 text-white/75">
{action.description}
</div>
)}

<ActionTriggerView locale={locale} triggers={triggers} functionDefs={functionDefs} />

{references.length > 0 && (
<div className="space-y-3">
<p className="text-sm tracking-wider text-white/50">
{referencesLabel}
</p>
<div className="grid gap-4 md:grid-cols-2">
{references.map((reference) => (
<ActionCard
key={reference.id}
action={reference}
locale={locale}
/>
))}
</div>
</div>
)}
</div>
</div>
</div>
</LandingContainer>
</>
)
}

export async function generateMetadata({ params }: { params: Promise<{ locale: string, slug: string }> }): Promise<Metadata> {
const { locale, slug } = await params
if (!isSupportedLocale(locale)) return createMetadata()
if (!slug?.trim()) return createMetadata()

const action = await getActionBySlug(slug, locale)
if (!action) return createMetadata()

return createMetadata({
title: action.title,
description: action.shortDescription ?? action.description ?? undefined,
})
}

export async function generateStaticParams() {
const all = await Promise.all(
SUPPORTED_LOCALES.map(async (locale) => {
const slugs = await getActionSlugs(locale)
return slugs.map((slug) => ({ locale, slug }))
})
)

return all.flat()
}

export const dynamicParams = false
33 changes: 33 additions & 0 deletions src/app/(landing)/[locale]/actions/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ActionsPageClient } from "@/components/ActionsPageClient"
import { Aurora } from "@/components/ui/Aurora"
import { LandingContainer } from "@/components/ui/LandingContainer"
import { getActions, getLandingPage, type ActionsLayoutBlock } from "@/lib/cms"
import { isSupportedLocale } from "@/lib/i18n"
import { getLandingPageMetadata } from "@/lib/pageMetadata"
import { Metadata } from "next"
import { notFound } from "next/navigation"

export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
return getLandingPageMetadata("actions", locale)
}

export default async function ActionsPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
if (!isSupportedLocale(locale)) notFound()

const [actions, actionsPage] = await Promise.all([
getActions(locale),
getLandingPage("actions", locale),
])
const actionsBlock = actionsPage?.layout?.find((block): block is ActionsLayoutBlock => block.blockType === "actions") ?? null

return (
<>
<Aurora />
<LandingContainer className="pt-32">
<ActionsPageClient actions={actions} locale={locale} content={actionsBlock} />
</LandingContainer>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@ function getMediaUrl(value?: number | Media | null) {
return new URL(value.url, resolveSiteUrl()).toString()
}

export default async function Page({ params }: { params: Promise<{ locale: string, slug: string[] }> }) {
export default async function Page({ params }: { params: Promise<{ locale: string, slug: string }> }) {
const { locale, slug } = await params
if (!isSupportedLocale(locale)) notFound()

const normalizedSlug = slug?.join("/")?.trim()
if (!normalizedSlug) notFound()
if (!slug?.trim()) notFound()

const page = await getLandingPage("main", locale)
const layout = page?.layout ?? []
Expand All @@ -38,7 +36,7 @@ export default async function Page({ params }: { params: Promise<{ locale: strin
<LandingContainer>
<div className={"pt-32 w-full max-w-4xl mx-auto"}>
<Suspense fallback={<BlogSkeleton />}>
<BlogPost slug={normalizedSlug} locale={locale} />
<BlogPost slug={slug} locale={locale} />

<div className={"glass-card-shell mt-32 w-full overflow-hidden rounded-3xl bg-[linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02))] shadow-[0_24px_80px_rgba(0,0,0,0.28)]"}>
<div aria-hidden="true" className="glass-card-topline" />
Expand Down Expand Up @@ -85,21 +83,13 @@ export default async function Page({ params }: { params: Promise<{ locale: strin
)
}

export async function generateMetadata({ params }: { params: Promise<{ locale: string, slug: string[] }> }): Promise<Metadata> {
export async function generateMetadata({ params }: { params: Promise<{ locale: string, slug: string }> }): Promise<Metadata> {
const { locale, slug } = await params
if (!isSupportedLocale(locale)) {
return createMetadata()
}
if (!isSupportedLocale(locale)) return createMetadata()
if (!slug?.trim()) return createMetadata()

const normalizedSlug = slug?.join("/")?.trim()
if (!normalizedSlug) {
return createMetadata()
}

const post = await getBlogPostBySlug(normalizedSlug, locale)
if (!post) {
return createMetadata()
}
const post = await getBlogPostBySlug(slug, locale)
if (!post) return createMetadata()

const title = post.meta?.title ?? post.title
const description = post.meta?.description ?? post.shortDescription ?? undefined
Expand Down Expand Up @@ -133,7 +123,7 @@ export async function generateStaticParams() {
const all = await Promise.all(
SUPPORTED_LOCALES.map(async (locale) => {
const slugs = await getBlogSlugs(locale)
return slugs.map((slug) => ({ locale, slug: slug.split("/").filter(Boolean) }))
return slugs.map((slug) => ({ locale, slug }))
})
)
return all.flat()
Expand Down
17 changes: 17 additions & 0 deletions src/app/(landing)/[locale]/jobs/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { JobDetailContent } from "@/components/JobDetailContent"
import { Aurora } from "@/components/ui/Aurora"
import { LandingContainer } from "@/components/ui/LandingContainer"
import { createMetadata } from "@/lib/siteConfig"
import { SUPPORTED_LOCALES, isSupportedLocale } from "@/lib/i18n"
import { getLandingPage, type JobsLayoutBlock } from "@/lib/cms"
import { getJobBySlug, getJobSlugs } from "@/lib/cms"
import { convertLexicalToHTML } from "@payloadcms/richtext-lexical/html"
import type { Metadata } from "next"
import { notFound } from "next/navigation"

export default async function JobDetailPage({ params }: { params: Promise<{ locale: string, slug: string }> }) {
const { locale, slug } = await params
if (!isSupportedLocale(locale)) notFound()
if (!slug?.trim()) notFound()

const job = await getJobBySlug(slug, locale)
if (!job) notFound()
Expand All @@ -34,6 +37,20 @@ export default async function JobDetailPage({ params }: { params: Promise<{ loca
)
}

export async function generateMetadata({ params }: { params: Promise<{ locale: string, slug: string }> }): Promise<Metadata> {
const { locale, slug } = await params
if (!isSupportedLocale(locale)) return createMetadata()
if (!slug?.trim()) return createMetadata()

const job = await getJobBySlug(slug, locale)
if (!job) return createMetadata()

return createMetadata({
title: job.title,
description: job.description ?? undefined,
})
}

export async function generateStaticParams() {
const all = await Promise.all(
SUPPORTED_LOCALES.map(async (locale) => {
Expand Down
46 changes: 46 additions & 0 deletions src/blocks/ActionBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { Block } from "payload"

export const ActionBlock: Block = {
slug: "actions",
labels: {
singular: "Actions",
plural: "Actions Blocks",
},
fields: [
{
name: "heading",
type: "text",
required: true,
localized: true,
defaultValue: "Actions",
},
{
name: "description",
type: "text",
required: true,
localized: true,
defaultValue: "Browse available actions and integrations.",
},
{
name: "searchPlaceholder",
type: "text",
required: true,
localized: true,
defaultValue: "Search actions",
},
{
name: "noActionsFoundLabel",
type: "text",
required: true,
localized: true,
defaultValue: "No actions found for your search.",
},
{
name: "referencesLabel",
type: "text",
required: true,
localized: true,
defaultValue: "References",
},
],
}
Loading