chore: heygermany customer project
Some checks failed
Main / Setup and Test (push) Has been cancelled
Main / Build Website (push) Has been cancelled

This commit is contained in:
e2e
2026-07-11 08:26:01 +02:00
commit 9c39da4d3f
398 changed files with 38331 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export function getRouter() {
return createRouter({
routeTree,
scrollRestoration: true,
})
}

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import BackOfficeLoginPage from '@/views/[lang]/(app)/backoffice/auth/login/page'
export const Route = createFileRoute('/$lang/_app/backoffice/auth/login')({
component: BackOfficeLoginPage,
})

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import BackOfficeCandidatePage from '@/views/[lang]/(app)/backoffice/candidate/[candidateId]/page'
export const Route = createFileRoute('/$lang/_app/backoffice/candidate/$candidateId')({
component: BackOfficeCandidatePage,
})

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import BackOfficeCandidatesPage from '@/views/[lang]/(app)/backoffice/candidates/page'
export const Route = createFileRoute('/$lang/_app/backoffice/candidates')({
component: BackOfficeCandidatesPage,
})

View File

@@ -0,0 +1,10 @@
import { createFileRoute, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/$lang/_app/backoffice/dashboard')({
beforeLoad: ({ params }) => {
throw redirect({
to: '/$lang/backoffice/candidates',
params: { lang: params.lang },
})
},
})

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import NextStepsHelpPage from '@/views/[lang]/(app)/backoffice/help/nextsteps/page'
export const Route = createFileRoute('/$lang/_app/backoffice/help/nextsteps')({
component: NextStepsHelpPage,
})

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import BackOfficeIndexPage from '@/views/[lang]/(app)/backoffice/page'
export const Route = createFileRoute('/$lang/_app/backoffice/')({
component: BackOfficeIndexPage,
})

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import BackOfficeTranslationsPage from '@/views/[lang]/(app)/backoffice/translations/page'
export const Route = createFileRoute('/$lang/_app/backoffice/translations')({
component: BackOfficeTranslationsPage,
})

View File

@@ -0,0 +1,14 @@
import { Outlet, createFileRoute } from '@tanstack/react-router'
import BackOfficeLayout from '@/views/[lang]/(app)/backoffice/layout'
export const Route = createFileRoute('/$lang/_app/backoffice')({
component: BackOfficeRouteLayout,
})
function BackOfficeRouteLayout() {
return (
<BackOfficeLayout>
<Outlet />
</BackOfficeLayout>
)
}

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import CompanyLoginPage from '@/views/[lang]/(app)/company/auth/login/page'
export const Route = createFileRoute('/$lang/_app/company/auth/login')({
component: CompanyLoginPage,
})

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import CompanyCandidatePage from '@/views/[lang]/(app)/company/candidate/[candidateId]/page'
export const Route = createFileRoute('/$lang/_app/company/candidate/$candidateId')({
component: CompanyCandidatePage,
})

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import CompanyCandidatesPage from '@/views/[lang]/(app)/company/candidates/page'
export const Route = createFileRoute('/$lang/_app/company/candidates')({
component: CompanyCandidatesPage,
})

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import CompanyPage from '@/views/[lang]/(app)/company/page'
export const Route = createFileRoute('/$lang/_app/company/')({
component: CompanyPage,
})

View File

@@ -0,0 +1,14 @@
import { Outlet, createFileRoute } from '@tanstack/react-router'
import CompanyLayout from '@/views/[lang]/(app)/company/layout'
export const Route = createFileRoute('/$lang/_app/company')({
component: CompanyRouteLayout,
})
function CompanyRouteLayout() {
return (
<CompanyLayout>
<Outlet />
</CompanyLayout>
)
}

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import LogoutPage from '@/views/[lang]/(app)/logout/page'
export const Route = createFileRoute('/$lang/_app/logout')({
component: LogoutPage,
})

View File

@@ -0,0 +1,8 @@
import { Outlet, createFileRoute } from '@tanstack/react-router'
import { NO_INDEX_METADATA } from '@/lib/seo'
import { metadataToHead } from '@/framework/head'
export const Route = createFileRoute('/$lang/_app')({
head: () => metadataToHead(NO_INDEX_METADATA),
component: Outlet,
})

View File

@@ -0,0 +1,17 @@
import { createFileRoute } from '@tanstack/react-router'
import AboutPage from '@/views/[lang]/(main)/about/page'
import { createPageMetadata, getMessage } from '@/lib/seo'
import { metadataToHead } from '@/framework/head'
export const Route = createFileRoute('/$lang/_main/about')({
head: ({ params }) =>
metadataToHead(
createPageMetadata({
locale: params.lang,
pathname: '/about',
title: getMessage(params.lang, 'website.about.title'),
description: getMessage(params.lang, 'website.about.description'),
})
),
component: AboutPage,
})

View File

@@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router'
import MagicLinkPage from '@/views/[lang]/(main)/auth/magic-link/page'
import { NO_INDEX_METADATA } from '@/lib/seo'
import { metadataToHead } from '@/framework/head'
export const Route = createFileRoute('/$lang/_main/auth/magic-link')({
head: () => metadataToHead(NO_INDEX_METADATA),
component: MagicLinkPage,
})

View File

@@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router'
import ApplicationPage from '@/views/[lang]/(main)/jobs/application/page'
import { NO_INDEX_METADATA } from '@/lib/seo'
import { metadataToHead } from '@/framework/head'
export const Route = createFileRoute('/$lang/_main/jobs/application')({
head: () => metadataToHead(NO_INDEX_METADATA),
component: ApplicationPage,
})

View File

@@ -0,0 +1,17 @@
import { createFileRoute } from '@tanstack/react-router'
import ApplyPage from '@/views/[lang]/(main)/jobs/apply/page'
import { createPageMetadata, getMessage } from '@/lib/seo'
import { metadataToHead } from '@/framework/head'
export const Route = createFileRoute('/$lang/_main/jobs/apply')({
head: ({ params }) =>
metadataToHead(
createPageMetadata({
locale: params.lang,
pathname: '/jobs/apply',
title: getMessage(params.lang, 'website.hero.cta'),
description: getMessage(params.lang, 'jobs.apply.getyourresults.description'),
})
),
component: ApplyPage,
})

View File

@@ -0,0 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import DemoResultPage from '@/views/[lang]/(main)/jobs/result/demo/page'
import { metadataToHead } from '@/framework/head'
import { NO_INDEX_METADATA } from '@/lib/seo'
const demoResultMetadata = {
title: 'Qualification Result | HeyGermany',
description: 'Candidate qualification result page.',
...NO_INDEX_METADATA,
}
export const Route = createFileRoute('/$lang/_main/jobs/result/demo')({
head: () => metadataToHead(demoResultMetadata),
component: DemoResultPage,
})

View File

@@ -0,0 +1,17 @@
import { createFileRoute, redirect } from '@tanstack/react-router'
import { NO_INDEX_METADATA } from '@/lib/seo'
import { metadataToHead } from '@/framework/head'
export const Route = createFileRoute('/$lang/_main/jobs/result')({
head: () => metadataToHead(NO_INDEX_METADATA),
beforeLoad: ({ params, location }) => {
if (location.pathname === `/${params.lang}/jobs/result/demo`) {
return
}
throw redirect({
to: '/$lang/jobs/apply',
params: { lang: params.lang },
})
},
})

View File

@@ -0,0 +1,14 @@
import { Outlet, createFileRoute } from '@tanstack/react-router'
import MainLayout from '@/views/[lang]/(main)/layout'
export const Route = createFileRoute('/$lang/_main')({
component: MainRouteLayout,
})
function MainRouteLayout() {
return (
<MainLayout>
<Outlet />
</MainLayout>
)
}

View File

@@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router'
import VerifyEmailPage from '@/views/[lang]/(main)/verify-email/page'
import { NO_INDEX_METADATA } from '@/lib/seo'
import { metadataToHead } from '@/framework/head'
export const Route = createFileRoute('/$lang/_main/verify-email')({
head: () => metadataToHead(NO_INDEX_METADATA),
component: VerifyEmailPage,
})

View File

@@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router'
import LocalizedHomePage from '@/views/[lang]/(main)/page'
import { createHomepageMetadata } from '@/lib/seo'
import { metadataToHead } from '@/framework/head'
export const Route = createFileRoute('/$lang/')({
head: ({ params }) => metadataToHead(createHomepageMetadata(params.lang)),
component: LocalizedHomePage,
})

View File

@@ -0,0 +1,21 @@
import { createFileRoute } from '@tanstack/react-router'
import ImprintPage from '@/views/[lang]/legal/imprint/page'
import { DEFAULT_LOCALE } from '@/config'
import { createPageMetadata } from '@/lib/seo'
import { metadataToHead } from '@/framework/head'
export const Route = createFileRoute('/$lang/legal/imprint')({
head: ({ params }) =>
metadataToHead(
createPageMetadata({
locale: params.lang,
canonicalLocale: DEFAULT_LOCALE,
availableLocales: [DEFAULT_LOCALE],
pathname: '/legal/imprint',
title: 'Imprint',
description: 'Legal notice and company information for HeyGermany.',
index: params.lang === DEFAULT_LOCALE,
})
),
component: ImprintPage,
})

View File

@@ -0,0 +1,21 @@
import { createFileRoute } from '@tanstack/react-router'
import PrivacyPage from '@/views/[lang]/legal/privacy/page'
import { DEFAULT_LOCALE } from '@/config'
import { createPageMetadata } from '@/lib/seo'
import { metadataToHead } from '@/framework/head'
export const Route = createFileRoute('/$lang/legal/privacy')({
head: ({ params }) =>
metadataToHead(
createPageMetadata({
locale: params.lang,
canonicalLocale: DEFAULT_LOCALE,
availableLocales: [DEFAULT_LOCALE],
pathname: '/legal/privacy',
title: 'Privacy Policy',
description: 'How HeyGermany collects, uses, and protects personal data.',
index: params.lang === DEFAULT_LOCALE,
})
),
component: PrivacyPage,
})

View File

@@ -0,0 +1,21 @@
import { createFileRoute } from '@tanstack/react-router'
import TermsPage from '@/views/[lang]/legal/terms-and-conditions/page'
import { DEFAULT_LOCALE } from '@/config'
import { createPageMetadata } from '@/lib/seo'
import { metadataToHead } from '@/framework/head'
export const Route = createFileRoute('/$lang/legal/terms-and-conditions')({
head: ({ params }) =>
metadataToHead(
createPageMetadata({
locale: params.lang,
canonicalLocale: DEFAULT_LOCALE,
availableLocales: [DEFAULT_LOCALE],
pathname: '/legal/terms-and-conditions',
title: 'Terms and Conditions',
description: 'Terms and conditions for using the HeyGermany platform and services.',
index: params.lang === DEFAULT_LOCALE,
})
),
component: TermsPage,
})

View File

@@ -0,0 +1,14 @@
import { Outlet, createFileRoute } from '@tanstack/react-router'
import LegalLayout from '@/views/[lang]/legal/layout'
export const Route = createFileRoute('/$lang/legal')({
component: LegalRouteLayout,
})
function LegalRouteLayout() {
return (
<LegalLayout>
<Outlet />
</LegalLayout>
)
}

View File

@@ -0,0 +1,31 @@
import { Outlet, createFileRoute, useLocation } from '@tanstack/react-router'
import { LocalizedLayout } from '@/framework/localized-layout'
import { normalizeLocale } from '@/lib/seo'
import LocalizedHomePage from '@/views/[lang]/(main)/page'
import { NotFoundTitle } from '@/components/NotFoundTitle'
export const Route = createFileRoute('/$lang')({
component: LangRoute,
notFoundComponent: LangNotFound,
})
function LangRoute() {
const { lang } = Route.useParams()
return (
<LocalizedLayout lang={normalizeLocale(lang)}>
<Outlet />
</LocalizedLayout>
)
}
function LangNotFound() {
const { lang } = Route.useParams()
const pathname = useLocation({ select: (location) => location.pathname })
if (pathname === `/${lang}`) {
return <LocalizedHomePage />
}
return <NotFoundTitle />
}

View File

@@ -0,0 +1,74 @@
import { HeadContent, Outlet, Scripts, createRootRoute, useLocation } from '@tanstack/react-router'
import { RootDocument } from '@/framework/root-document'
import { NotFoundTitle } from '@/components/NotFoundTitle'
import { metadataToHead } from '@/framework/head'
import {
DEFAULT_OG_IMAGE,
DEFAULT_SITE_DESCRIPTION,
DEFAULT_SITE_TITLE,
SITE_NAME,
SITE_URL,
getAbsoluteUrl,
} from '@/lib/seo'
import { getResolvedLocale } from '@/lib/request-routing'
const rootMetadata = {
metadataBase: new URL(SITE_URL),
applicationName: SITE_NAME,
title: DEFAULT_SITE_TITLE,
description: DEFAULT_SITE_DESCRIPTION,
manifest: '/site.webmanifest',
icons: {
icon: [
{ url: '/favicon.ico' },
{ url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
{ url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
],
apple: [{ url: '/apple-touch-icon.png', sizes: '180x180' }],
},
openGraph: {
title: DEFAULT_SITE_TITLE,
description: DEFAULT_SITE_DESCRIPTION,
url: SITE_URL,
siteName: SITE_NAME,
type: 'website',
images: [{ url: getAbsoluteUrl(DEFAULT_OG_IMAGE), alt: SITE_NAME }],
},
twitter: {
card: 'summary_large_image',
title: DEFAULT_SITE_TITLE,
description: DEFAULT_SITE_DESCRIPTION,
images: [getAbsoluteUrl(DEFAULT_OG_IMAGE)],
},
}
export const Route = createRootRoute({
head: () => metadataToHead(rootMetadata),
notFoundComponent: NotFoundTitle,
component: RootComponent,
})
function RootComponent() {
const pathname = useLocation({ select: (location) => location.pathname })
const lang = getResolvedLocale(pathname)
const initialTheme =
typeof document === 'undefined'
? 'light'
: ((document.cookie
.split(';')
.map((part) => part.trim())
.find((part) => part.startsWith('mantine-color-scheme='))
?.split('=')[1] ?? 'light') as 'light' | 'dark' | 'auto')
return (
<RootDocument
lang={lang}
initialTheme={initialTheme}
gaId={import.meta.env.VITE_GA_ID ?? import.meta.env.GA_ID}
headContent={<HeadContent />}
bodyEnd={<Scripts />}
>
<Outlet />
</RootDocument>
)
}

View File

@@ -0,0 +1,35 @@
import { createFileRoute } from '@tanstack/react-router'
const getApiOrigin = () => {
return process.env.VITE_API_BASE_URL || process.env.API_URL || 'http://localhost:3000'
}
const proxyAuthRequest = async (request: Request, splat: string | undefined) => {
const target = new URL(`/api/auth/${splat || ''}`, getApiOrigin())
if (request.url.includes('?')) {
target.search = new URL(request.url).search
}
const upstream = await fetch(target, {
method: request.method,
headers: request.headers,
body: request.method === 'GET' || request.method === 'HEAD' ? undefined : await request.blob(),
redirect: 'manual',
})
return new Response(upstream.body, {
status: upstream.status,
statusText: upstream.statusText,
headers: upstream.headers,
})
}
export const Route = createFileRoute('/api/auth/$')({
server: {
handlers: {
GET: async ({ request, params }) => proxyAuthRequest(request, params._splat),
POST: async ({ request, params }) => proxyAuthRequest(request, params._splat),
},
},
})

View File

@@ -0,0 +1,10 @@
import { createFileRoute } from '@tanstack/react-router'
import { getHealthResponse } from '@/lib/health'
export const Route = createFileRoute('/api/health')({
server: {
handlers: {
GET: async () => getHealthResponse(),
},
},
})

View File

@@ -0,0 +1,39 @@
import { createFileRoute } from '@tanstack/react-router'
import PilotPage from '@/views/pilot/page'
import { metadataToHead } from '@/framework/head'
import { DEFAULT_OG_IMAGE, SITE_NAME, getAbsoluteUrl } from '@/lib/seo'
const pilotMetadata = {
title: 'Pilot-Partner werden | HeyGermany',
description:
'HeyGermany sucht Pilot-Partner unter deutschen Pflegeeinrichtungen, die internationale Pflegefachkraefte ohne klassische Agenturmodelle gewinnen wollen.',
alternates: {
canonical: 'https://hey-germany.com/pilot',
},
openGraph: {
title: 'Pilot-Partner werden | HeyGermany',
description:
'Vorqualifizierte internationale Pflegefachkraefte, Full-Service Recruiting und der Aufbau eigener Prozesse fuer nachhaltige Rekrutierung.',
url: 'https://hey-germany.com/pilot',
siteName: SITE_NAME,
type: 'website',
images: [
{
url: getAbsoluteUrl(DEFAULT_OG_IMAGE),
alt: 'Pilot-Partner werden | HeyGermany',
},
],
},
twitter: {
card: 'summary_large_image',
title: 'Pilot-Partner werden | HeyGermany',
description:
'Vorqualifizierte internationale Pflegefachkraefte, Full-Service Recruiting und der Aufbau eigener Prozesse fuer nachhaltige Rekrutierung.',
images: [getAbsoluteUrl(DEFAULT_OG_IMAGE)],
},
}
export const Route = createFileRoute('/pilot')({
head: () => metadataToHead(pilotMetadata),
component: PilotPage,
})

View File

@@ -0,0 +1,29 @@
import { createFileRoute } from '@tanstack/react-router'
import { getRobotsConfig } from '@/lib/robots'
function renderRobotsTxt() {
const robots = getRobotsConfig()
const lines = robots.rules.flatMap((rule) => [
`User-agent: ${rule.userAgent}`,
`Allow: ${rule.allow}`,
...(rule.disallow ?? []).map((path) => `Disallow: ${path}`),
])
lines.push(`Sitemap: ${robots.sitemap}`)
lines.push(`Host: ${robots.host}`)
return `${lines.join('\n')}\n`
}
export const Route = createFileRoute('/robots.txt')({
server: {
handlers: {
GET: async () =>
new Response(renderRobotsTxt(), {
headers: {
'content-type': 'text/plain; charset=utf-8',
},
}),
},
},
})

44
apps/website/src/start.ts Normal file
View File

@@ -0,0 +1,44 @@
import '@mantine/core/styles.css'
import '@mantine/dates/styles.css'
import 'mantine-datatable/styles.layer.css'
import '@/views/[lang]/globals.css'
import { createMiddleware, createStart } from '@tanstack/react-start'
import { redirect } from '@tanstack/react-router'
import { resolveRequestRouting } from '@/lib/request-routing'
export const startInstance = createStart(() => ({
requestMiddleware: [
createStartRequestMiddleware(),
],
}))
function createStartRequestMiddleware() {
return createMiddleware().server(async ({ request, next }) => {
const result = await resolveRequestRouting({
url: request.url,
pathname: new URL(request.url).pathname,
search: new URL(request.url).search,
headers: request.headers,
cookies: new Map(
(request.headers.get('cookie') ?? '')
.split(';')
.map((part) => part.trim())
.filter(Boolean)
.map((part) => {
const index = part.indexOf('=')
return [
part.slice(0, index),
decodeURIComponent(part.slice(index + 1)),
] as const
})
),
})
if (result.type === 'redirect') {
throw redirect({ href: result.location })
}
return next()
})
}