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_API_URL: string
|
||||||
readonly PUBLIC_GRAPHQL_URL: string
|
readonly PUBLIC_GRAPHQL_URL: string
|
||||||
readonly PUBLIC_UPLOADS_BASE_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
|
readonly BITRIX24_WEBHOOK_URL?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export const privacyPolicyDocument = gql`
|
|||||||
query PrivacyPolicy {
|
query PrivacyPolicy {
|
||||||
siteConfig {
|
siteConfig {
|
||||||
title
|
title
|
||||||
|
description
|
||||||
privacyPolicy
|
privacyPolicy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export const termsOfUseDocument = gql`
|
|||||||
query TermsOfUse {
|
query TermsOfUse {
|
||||||
siteConfig {
|
siteConfig {
|
||||||
title
|
title
|
||||||
|
description
|
||||||
termsOfUse
|
termsOfUse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1632,12 +1632,12 @@ export type SiteConfigQuery = { __typename?: 'Query', siteConfig?: { __typename?
|
|||||||
export type PrivacyPolicyQueryVariables = Exact<{ [key: string]: never; }>;
|
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 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>
|
export class TypedDocumentString<TResult, TVariables>
|
||||||
extends String
|
extends String
|
||||||
@@ -1806,6 +1806,7 @@ export const PrivacyPolicyDocument = new TypedDocumentString(`
|
|||||||
query PrivacyPolicy {
|
query PrivacyPolicy {
|
||||||
siteConfig {
|
siteConfig {
|
||||||
title
|
title
|
||||||
|
description
|
||||||
privacyPolicy
|
privacyPolicy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1814,6 +1815,7 @@ export const TermsOfUseDocument = new TypedDocumentString(`
|
|||||||
query TermsOfUse {
|
query TermsOfUse {
|
||||||
siteConfig {
|
siteConfig {
|
||||||
title
|
title
|
||||||
|
description
|
||||||
termsOfUse
|
termsOfUse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,20 @@ import '../styles/globals.css'
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
siteTitle: string
|
siteTitle: string
|
||||||
|
siteDescription?: string
|
||||||
pageTitle: string
|
pageTitle: string
|
||||||
contentHtml: 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">
|
<div class="min-h-screen bg-white flex flex-col">
|
||||||
<header class="border-b border-gray-200">
|
<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">
|
<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'
|
import ClientRouter from 'astro/components/ClientRouter.astro'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
/** Full <title> / og:title. Comes from Strapi (siteConfig.title, category.title). */
|
||||||
title?: string
|
title?: string
|
||||||
|
/** meta description / og:description. Comes from Strapi. */
|
||||||
description?: string
|
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">
|
<html lang="ru">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<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>
|
<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 />
|
<ClientRouter />
|
||||||
<script is:inline type="text/javascript">
|
<script is:inline type="text/javascript">
|
||||||
// prettier-ignore
|
// 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 class="min-h-screen grid content-center justify-items-center">
|
||||||
<div>404</div>
|
<div>404</div>
|
||||||
<div>Страница не найдена</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 class="min-h-screen grid content-center justify-items-center">
|
||||||
<div>500</div>
|
<div>500</div>
|
||||||
<div>Мы скоро всё починим</div>
|
<div>Мы скоро всё починим</div>
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ const vendors = (category.vendors?.filter(v => v != null) ?? []).map(vendor => (
|
|||||||
<Layout
|
<Layout
|
||||||
title={title ? `${title} | ${siteTitle}` : siteTitle}
|
title={title ? `${title} | ${siteTitle}` : siteTitle}
|
||||||
description={description ?? siteDescription}
|
description={description ?? siteDescription}
|
||||||
|
siteName={siteTitle}
|
||||||
|
type="article"
|
||||||
|
canonicalPath={`/${slug}`}
|
||||||
>
|
>
|
||||||
<div class="min-h-screen bg-white w-full overflow-x-hidden">
|
<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} />
|
<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 is intentionally self-contained — no shared Layout and no Strapi data — so
|
||||||
// it renders even while the rest of the site is gated.
|
// it renders even while the rest of the site is gated.
|
||||||
Astro.response.headers.set('X-Robots-Tag', 'noindex, nofollow')
|
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>
|
<!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="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="robots" content="noindex, nofollow" />
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
<title>СОФТКЛИК — сайт в разработке</title>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="screen">
|
<div class="screen">
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const serviceCategories = menuItems.filter(c => c.group === Enum_Category_Group.
|
|||||||
const sectionAnchors = getHomeSectionAnchors(homePage)
|
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">
|
<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} />
|
<Header client:load siteTitle={siteTitle} contacts={contacts} solutions={solutionCategories} services={serviceCategories} sectionAnchors={sectionAnchors} />
|
||||||
<HomePage client:load homePage={homePage} contacts={contacts} />
|
<HomePage client:load homePage={homePage} contacts={contacts} />
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const contentHtml = markdown ? await renderMarkdown(markdown) : ''
|
|||||||
|
|
||||||
<DocumentLayout
|
<DocumentLayout
|
||||||
siteTitle={siteConfig?.title ?? ''}
|
siteTitle={siteConfig?.title ?? ''}
|
||||||
|
siteDescription={siteConfig?.description ?? ''}
|
||||||
pageTitle="Политика конфиденциальности"
|
pageTitle="Политика конфиденциальности"
|
||||||
contentHtml={contentHtml}
|
contentHtml={contentHtml}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const contentHtml = markdown ? await renderMarkdown(markdown) : ''
|
|||||||
|
|
||||||
<DocumentLayout
|
<DocumentLayout
|
||||||
siteTitle={siteConfig?.title ?? ''}
|
siteTitle={siteConfig?.title ?? ''}
|
||||||
|
siteDescription={siteConfig?.description ?? ''}
|
||||||
pageTitle="Условия использования"
|
pageTitle="Условия использования"
|
||||||
contentHtml={contentHtml}
|
contentHtml={contentHtml}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user