Files
imen_pishghaman/apps/web/app/composables/useSiteSeo.ts
T
S. EsmaeiliandClaude Opus 5.5 789d224ea0 feat: monorepo with NestJS API, admin panel and DB-driven website
Restructure the repo into a pnpm + Turborepo monorepo:

- apps/web: Nuxt 4 site, now server-rendered and reading all content
  from the API; pages cached with SWR and purged on content changes
- apps/api: NestJS 12 + Prisma 7 + PostgreSQL. Auth with short-lived
  access tokens and rotating httpOnly refresh cookies, roles (ADMIN,
  EDITOR), catalog, editable site sections, media upload to S3-compatible
  storage (converted to WebP), contact messages, audit log
- apps/admin: Vue 3 admin panel (Ant Design Vue, Pinia, Tailwind) for
  products, categories, site sections, messages, media and users
- packages/shared: Zod schemas and types shared by API and admin

Also:
- seed script that loads the original 8 categories / 89 products and
  section content
- Docker images for api (with a one-off migrate service), web, admin,
  and an nginx gateway for both domains
- dev and production compose files, daily database backup service
- API e2e tests

Removes the committed static out/ build and the old root Dockerfile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:31:05 +03:30

47 lines
1.5 KiB
TypeScript

import { siteConfig } from "#shared/config/site";
/** Used when the API is unreachable (the error page must still render) */
const FALLBACK = {
name: "ایمن پیشگامان",
title: "ایمن پیشگامان | تجهیزات ایمنی و حفاظت فردی صنعتی",
description: "ایمن پیشگامان — تأمین‌کننده تخصصی تجهیزات حفاظت فردی (PPE) و راهکارهای ایمنی صنعتی.",
};
/** Site-wide <head> defaults shared by the app shell and the error page. */
export function useSiteSeo() {
const site = useSiteStore();
const absUrl = useAbsUrl();
const s = site.data?.sections.settings;
const name = s?.name ?? FALLBACK.name;
const title = s?.title ?? FALLBACK.title;
const description = s?.description ?? FALLBACK.description;
const ogImage = s?.ogImage;
useHead({
titleTemplate: (t) => (t ? `${t} | ${name}` : title),
});
useSeoMeta({
description,
themeColor: siteConfig.themeColor,
formatDetection: "telephone=no",
ogType: "website",
ogSiteName: name,
ogLocale: siteConfig.locale,
ogTitle: title,
ogDescription: description,
twitterCard: "summary_large_image",
twitterTitle: title,
twitterDescription: description,
...(ogImage && {
ogImage: absUrl(ogImage.url),
ogImageWidth: ogImage.width ?? undefined,
ogImageHeight: ogImage.height ?? undefined,
ogImageAlt: ogImage.alt,
twitterImage: absUrl(ogImage.url),
twitterImageAlt: ogImage.alt,
}),
});
}