This commit is contained in:
2
src/env.d.ts
vendored
2
src/env.d.ts
vendored
@@ -4,6 +4,8 @@ interface ImportMetaEnv {
|
||||
readonly PUBLIC_API_URL: string
|
||||
readonly PUBLIC_GRAPHQL_URL: string
|
||||
readonly PUBLIC_UPLOADS_BASE_URL: string
|
||||
/** Public origin of the site, e.g. https://softclick.ru — used for canonical/og:url. */
|
||||
readonly PUBLIC_SITE_URL?: string
|
||||
readonly BITRIX24_WEBHOOK_URL?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ export const privacyPolicyDocument = gql`
|
||||
query PrivacyPolicy {
|
||||
siteConfig {
|
||||
title
|
||||
description
|
||||
privacyPolicy
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export const termsOfUseDocument = gql`
|
||||
query TermsOfUse {
|
||||
siteConfig {
|
||||
title
|
||||
description
|
||||
termsOfUse
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1632,12 +1632,12 @@ export type SiteConfigQuery = { __typename?: 'Query', siteConfig?: { __typename?
|
||||
export type PrivacyPolicyQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type PrivacyPolicyQuery = { __typename?: 'Query', siteConfig?: { __typename?: 'SiteConfig', title: string, privacyPolicy?: string | null } | null };
|
||||
export type PrivacyPolicyQuery = { __typename?: 'Query', siteConfig?: { __typename?: 'SiteConfig', title: string, description?: string | null, privacyPolicy?: string | null } | null };
|
||||
|
||||
export type TermsOfUseQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type TermsOfUseQuery = { __typename?: 'Query', siteConfig?: { __typename?: 'SiteConfig', title: string, termsOfUse?: string | null } | null };
|
||||
export type TermsOfUseQuery = { __typename?: 'Query', siteConfig?: { __typename?: 'SiteConfig', title: string, description?: string | null, termsOfUse?: string | null } | null };
|
||||
|
||||
export class TypedDocumentString<TResult, TVariables>
|
||||
extends String
|
||||
@@ -1806,6 +1806,7 @@ export const PrivacyPolicyDocument = new TypedDocumentString(`
|
||||
query PrivacyPolicy {
|
||||
siteConfig {
|
||||
title
|
||||
description
|
||||
privacyPolicy
|
||||
}
|
||||
}
|
||||
@@ -1814,6 +1815,7 @@ export const TermsOfUseDocument = new TypedDocumentString(`
|
||||
query TermsOfUse {
|
||||
siteConfig {
|
||||
title
|
||||
description
|
||||
termsOfUse
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,20 @@ import '../styles/globals.css'
|
||||
|
||||
interface Props {
|
||||
siteTitle: string
|
||||
siteDescription?: string
|
||||
pageTitle: string
|
||||
contentHtml: string
|
||||
}
|
||||
|
||||
const { siteTitle, pageTitle, contentHtml } = Astro.props
|
||||
const { siteTitle, siteDescription = '', pageTitle, contentHtml } = Astro.props
|
||||
---
|
||||
|
||||
<Layout title={`${pageTitle} | ${siteTitle}`}>
|
||||
<Layout
|
||||
title={`${pageTitle} | ${siteTitle}`}
|
||||
description={siteDescription}
|
||||
siteName={siteTitle}
|
||||
type="article"
|
||||
>
|
||||
<div class="min-h-screen bg-white flex flex-col">
|
||||
<header class="border-b border-gray-200">
|
||||
<div class="max-w-3xl mx-auto px-4 h-16 flex items-center justify-between w-full">
|
||||
|
||||
@@ -2,20 +2,82 @@
|
||||
import ClientRouter from 'astro/components/ClientRouter.astro'
|
||||
|
||||
interface Props {
|
||||
/** Full <title> / og:title. Comes from Strapi (siteConfig.title, category.title). */
|
||||
title?: string
|
||||
/** meta description / og:description. Comes from Strapi. */
|
||||
description?: string
|
||||
/** og:site_name — the brand, without the per-page part. */
|
||||
siteName?: string
|
||||
/** og:type: 'website' for the home page, 'article' for content pages. */
|
||||
type?: 'website' | 'article'
|
||||
/** Absolute or site-root-relative image for og:image / twitter:image. */
|
||||
image?: string
|
||||
/** Pixel size of `image` — crawlers use it to lay the preview out before download. */
|
||||
imageWidth?: number
|
||||
imageHeight?: number
|
||||
/** Override the canonical path (defaults to the current URL without query). */
|
||||
canonicalPath?: string
|
||||
/** Keep the page out of search indexes (error pages, gated pages). */
|
||||
noindex?: boolean
|
||||
}
|
||||
|
||||
const { title = '', description = '' } = Astro.props
|
||||
const {
|
||||
title = '',
|
||||
description = '',
|
||||
siteName = '',
|
||||
type = 'website',
|
||||
image = '/logo.jpg',
|
||||
imageWidth = 1024, // matches public/logo.jpg
|
||||
imageHeight = 1024,
|
||||
canonicalPath,
|
||||
noindex = false,
|
||||
} = Astro.props
|
||||
|
||||
// Absolute URLs are required by Open Graph — relative ones are ignored by most
|
||||
// crawlers and social scrapers. PUBLIC_SITE_URL pins the public origin; behind a
|
||||
// reverse proxy Astro.url alone can resolve to the internal container host.
|
||||
// Prefer process.env so the domain can be set at deploy/runtime (Docker), with
|
||||
// import.meta.env as the `astro dev` fallback (see src/middleware.ts).
|
||||
const siteOrigin =
|
||||
process.env.PUBLIC_SITE_URL || import.meta.env.PUBLIC_SITE_URL || Astro.url.origin
|
||||
const canonicalURL = new URL(canonicalPath ?? Astro.url.pathname, siteOrigin)
|
||||
const imageURL = new URL(image, siteOrigin)
|
||||
---
|
||||
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content={description} />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
<link rel="canonical" href={canonicalURL.href} />
|
||||
{
|
||||
noindex ? (
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
) : (
|
||||
<meta name="robots" content="index, follow, max-image-preview:large" />
|
||||
)
|
||||
}
|
||||
|
||||
<!-- Open Graph (VK, Telegram, WhatsApp, LinkedIn, Facebook) -->
|
||||
<meta property="og:type" content={type} />
|
||||
<meta property="og:url" content={canonicalURL.href} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
{siteName && <meta property="og:site_name" content={siteName} />}
|
||||
<meta property="og:locale" content="ru_RU" />
|
||||
<meta property="og:image" content={imageURL.href} />
|
||||
<meta property="og:image:width" content={String(imageWidth)} />
|
||||
<meta property="og:image:height" content={String(imageHeight)} />
|
||||
<meta property="og:image:alt" content={title} />
|
||||
|
||||
<!-- Twitter/X card -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content={imageURL.href} />
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<ClientRouter />
|
||||
<script is:inline type="text/javascript">
|
||||
// prettier-ignore
|
||||
|
||||
@@ -15,7 +15,12 @@ try {
|
||||
}
|
||||
---
|
||||
|
||||
<Layout title={siteTitle} description={siteDescription}>
|
||||
<Layout
|
||||
title={siteTitle ? `Страница не найдена | ${siteTitle}` : 'Страница не найдена'}
|
||||
description={siteDescription}
|
||||
siteName={siteTitle}
|
||||
noindex
|
||||
>
|
||||
<div class="min-h-screen grid content-center justify-items-center">
|
||||
<div>404</div>
|
||||
<div>Страница не найдена</div>
|
||||
|
||||
@@ -15,7 +15,12 @@ try {
|
||||
}
|
||||
---
|
||||
|
||||
<Layout title={siteTitle} description={siteDescription}>
|
||||
<Layout
|
||||
title={siteTitle ? `Ошибка сервера | ${siteTitle}` : 'Ошибка сервера'}
|
||||
description={siteDescription}
|
||||
siteName={siteTitle}
|
||||
noindex
|
||||
>
|
||||
<div class="min-h-screen grid content-center justify-items-center">
|
||||
<div>500</div>
|
||||
<div>Мы скоро всё починим</div>
|
||||
|
||||
@@ -67,6 +67,9 @@ const vendors = (category.vendors?.filter(v => v != null) ?? []).map(vendor => (
|
||||
<Layout
|
||||
title={title ? `${title} | ${siteTitle}` : siteTitle}
|
||||
description={description ?? siteDescription}
|
||||
siteName={siteTitle}
|
||||
type="article"
|
||||
canonicalPath={`/${slug}`}
|
||||
>
|
||||
<div class="min-h-screen bg-white w-full overflow-x-hidden">
|
||||
<Header client:load siteTitle={siteTitle} contacts={contacts} solutions={solutionCategories} services={serviceCategories} sectionAnchors={sectionAnchors} />
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
// It is intentionally self-contained — no shared Layout and no Strapi data — so
|
||||
// it renders even while the rest of the site is gated.
|
||||
Astro.response.headers.set('X-Robots-Tag', 'noindex, nofollow')
|
||||
|
||||
// Same origin resolution as src/layouts/Layout.astro — og:image must be absolute.
|
||||
const siteOrigin =
|
||||
process.env.PUBLIC_SITE_URL || import.meta.env.PUBLIC_SITE_URL || Astro.url.origin
|
||||
const ogImage = new URL('/logo.jpg', siteOrigin).href
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
@@ -15,6 +20,16 @@ Astro.response.headers.set('X-Robots-Tag', 'noindex, nofollow')
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<title>СОФТКЛИК — сайт в разработке</title>
|
||||
<meta name="description" content="Сайт в разработке. Следите за обновлениями." />
|
||||
<!-- Kept for messenger link previews (the preview link gets shared in chats);
|
||||
indexing is still blocked by the robots meta above. -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="СОФТКЛИК — сайт в разработке" />
|
||||
<meta property="og:description" content="Сайт в разработке. Следите за обновлениями." />
|
||||
<meta property="og:site_name" content="СОФТКЛИК" />
|
||||
<meta property="og:locale" content="ru_RU" />
|
||||
<meta property="og:image" content={ogImage} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="screen">
|
||||
|
||||
@@ -40,7 +40,7 @@ const serviceCategories = menuItems.filter(c => c.group === Enum_Category_Group.
|
||||
const sectionAnchors = getHomeSectionAnchors(homePage)
|
||||
---
|
||||
|
||||
<Layout title={siteTitle} description={siteDescription}>
|
||||
<Layout title={siteTitle} description={siteDescription} siteName={siteTitle} type="website">
|
||||
<div class="min-h-screen bg-white w-full overflow-x-hidden">
|
||||
<Header client:load siteTitle={siteTitle} contacts={contacts} solutions={solutionCategories} services={serviceCategories} sectionAnchors={sectionAnchors} />
|
||||
<HomePage client:load homePage={homePage} contacts={contacts} />
|
||||
|
||||
@@ -15,6 +15,7 @@ const contentHtml = markdown ? await renderMarkdown(markdown) : ''
|
||||
|
||||
<DocumentLayout
|
||||
siteTitle={siteConfig?.title ?? ''}
|
||||
siteDescription={siteConfig?.description ?? ''}
|
||||
pageTitle="Политика конфиденциальности"
|
||||
contentHtml={contentHtml}
|
||||
/>
|
||||
|
||||
@@ -15,6 +15,7 @@ const contentHtml = markdown ? await renderMarkdown(markdown) : ''
|
||||
|
||||
<DocumentLayout
|
||||
siteTitle={siteConfig?.title ?? ''}
|
||||
siteDescription={siteConfig?.description ?? ''}
|
||||
pageTitle="Условия использования"
|
||||
contentHtml={contentHtml}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user