Add public support page (#518)
This commit is contained in:
parent
4ba2d6c175
commit
d403954323
@ -29,6 +29,7 @@ const STATIC_PATHS = [
|
|||||||
"/open-source-seo",
|
"/open-source-seo",
|
||||||
"/google-search-console-mcp",
|
"/google-search-console-mcp",
|
||||||
"/roadmap",
|
"/roadmap",
|
||||||
|
"/support",
|
||||||
...Object.values(FEATURE_PAGE_SLUGS).map((slug) => `/features/${slug}`),
|
...Object.values(FEATURE_PAGE_SLUGS).map((slug) => `/features/${slug}`),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -60,6 +60,7 @@ export function SiteFooter({ className }: { className?: string }) {
|
|||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-neutral-900">Company</p>
|
<p className="font-semibold text-neutral-900">Company</p>
|
||||||
<div className="mt-2 flex flex-col gap-1.5">
|
<div className="mt-2 flex flex-col gap-1.5">
|
||||||
|
<Link to="/support">Support</Link>
|
||||||
<Link to="/roadmap">Roadmap</Link>
|
<Link to="/roadmap">Roadmap</Link>
|
||||||
<Link to="/pricing">Pricing</Link>
|
<Link to="/pricing">Pricing</Link>
|
||||||
<a
|
<a
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { Route as BlogsSplatRouteImport } from './routes/blogs/$'
|
|||||||
import { Route as ApiSubscribeRouteImport } from './routes/api/subscribe'
|
import { Route as ApiSubscribeRouteImport } from './routes/api/subscribe'
|
||||||
import { Route as ApiEventRouteImport } from './routes/api/event'
|
import { Route as ApiEventRouteImport } from './routes/api/event'
|
||||||
import { Route as ApiBacklinkCheckRouteImport } from './routes/api/backlink-check'
|
import { Route as ApiBacklinkCheckRouteImport } from './routes/api/backlink-check'
|
||||||
|
import { Route as MarketingSupportRouteImport } from './routes/_marketing/support'
|
||||||
import { Route as MarketingRoadmapRouteImport } from './routes/_marketing/roadmap'
|
import { Route as MarketingRoadmapRouteImport } from './routes/_marketing/roadmap'
|
||||||
import { Route as MarketingPricingRouteImport } from './routes/_marketing/pricing'
|
import { Route as MarketingPricingRouteImport } from './routes/_marketing/pricing'
|
||||||
import { Route as MarketingOpenSourceSeoRouteImport } from './routes/_marketing/open-source-seo'
|
import { Route as MarketingOpenSourceSeoRouteImport } from './routes/_marketing/open-source-seo'
|
||||||
@ -124,6 +125,11 @@ const ApiBacklinkCheckRoute = ApiBacklinkCheckRouteImport.update({
|
|||||||
path: '/api/backlink-check',
|
path: '/api/backlink-check',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const MarketingSupportRoute = MarketingSupportRouteImport.update({
|
||||||
|
id: '/support',
|
||||||
|
path: '/support',
|
||||||
|
getParentRoute: () => MarketingRoute,
|
||||||
|
} as any)
|
||||||
const MarketingRoadmapRoute = MarketingRoadmapRouteImport.update({
|
const MarketingRoadmapRoute = MarketingRoadmapRouteImport.update({
|
||||||
id: '/roadmap',
|
id: '/roadmap',
|
||||||
path: '/roadmap',
|
path: '/roadmap',
|
||||||
@ -318,6 +324,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/open-source-seo': typeof MarketingOpenSourceSeoRoute
|
'/open-source-seo': typeof MarketingOpenSourceSeoRoute
|
||||||
'/pricing': typeof MarketingPricingRoute
|
'/pricing': typeof MarketingPricingRoute
|
||||||
'/roadmap': typeof MarketingRoadmapRoute
|
'/roadmap': typeof MarketingRoadmapRoute
|
||||||
|
'/support': typeof MarketingSupportRoute
|
||||||
'/api/backlink-check': typeof ApiBacklinkCheckRoute
|
'/api/backlink-check': typeof ApiBacklinkCheckRoute
|
||||||
'/api/event': typeof ApiEventRoute
|
'/api/event': typeof ApiEventRoute
|
||||||
'/api/subscribe': typeof ApiSubscribeRoute
|
'/api/subscribe': typeof ApiSubscribeRoute
|
||||||
@ -363,6 +370,7 @@ export interface FileRoutesByTo {
|
|||||||
'/open-source-seo': typeof MarketingOpenSourceSeoRoute
|
'/open-source-seo': typeof MarketingOpenSourceSeoRoute
|
||||||
'/pricing': typeof MarketingPricingRoute
|
'/pricing': typeof MarketingPricingRoute
|
||||||
'/roadmap': typeof MarketingRoadmapRoute
|
'/roadmap': typeof MarketingRoadmapRoute
|
||||||
|
'/support': typeof MarketingSupportRoute
|
||||||
'/api/backlink-check': typeof ApiBacklinkCheckRoute
|
'/api/backlink-check': typeof ApiBacklinkCheckRoute
|
||||||
'/api/event': typeof ApiEventRoute
|
'/api/event': typeof ApiEventRoute
|
||||||
'/api/subscribe': typeof ApiSubscribeRoute
|
'/api/subscribe': typeof ApiSubscribeRoute
|
||||||
@ -411,6 +419,7 @@ export interface FileRoutesById {
|
|||||||
'/_marketing/open-source-seo': typeof MarketingOpenSourceSeoRoute
|
'/_marketing/open-source-seo': typeof MarketingOpenSourceSeoRoute
|
||||||
'/_marketing/pricing': typeof MarketingPricingRoute
|
'/_marketing/pricing': typeof MarketingPricingRoute
|
||||||
'/_marketing/roadmap': typeof MarketingRoadmapRoute
|
'/_marketing/roadmap': typeof MarketingRoadmapRoute
|
||||||
|
'/_marketing/support': typeof MarketingSupportRoute
|
||||||
'/api/backlink-check': typeof ApiBacklinkCheckRoute
|
'/api/backlink-check': typeof ApiBacklinkCheckRoute
|
||||||
'/api/event': typeof ApiEventRoute
|
'/api/event': typeof ApiEventRoute
|
||||||
'/api/subscribe': typeof ApiSubscribeRoute
|
'/api/subscribe': typeof ApiSubscribeRoute
|
||||||
@ -460,6 +469,7 @@ export interface FileRouteTypes {
|
|||||||
| '/open-source-seo'
|
| '/open-source-seo'
|
||||||
| '/pricing'
|
| '/pricing'
|
||||||
| '/roadmap'
|
| '/roadmap'
|
||||||
|
| '/support'
|
||||||
| '/api/backlink-check'
|
| '/api/backlink-check'
|
||||||
| '/api/event'
|
| '/api/event'
|
||||||
| '/api/subscribe'
|
| '/api/subscribe'
|
||||||
@ -505,6 +515,7 @@ export interface FileRouteTypes {
|
|||||||
| '/open-source-seo'
|
| '/open-source-seo'
|
||||||
| '/pricing'
|
| '/pricing'
|
||||||
| '/roadmap'
|
| '/roadmap'
|
||||||
|
| '/support'
|
||||||
| '/api/backlink-check'
|
| '/api/backlink-check'
|
||||||
| '/api/event'
|
| '/api/event'
|
||||||
| '/api/subscribe'
|
| '/api/subscribe'
|
||||||
@ -552,6 +563,7 @@ export interface FileRouteTypes {
|
|||||||
| '/_marketing/open-source-seo'
|
| '/_marketing/open-source-seo'
|
||||||
| '/_marketing/pricing'
|
| '/_marketing/pricing'
|
||||||
| '/_marketing/roadmap'
|
| '/_marketing/roadmap'
|
||||||
|
| '/_marketing/support'
|
||||||
| '/api/backlink-check'
|
| '/api/backlink-check'
|
||||||
| '/api/event'
|
| '/api/event'
|
||||||
| '/api/subscribe'
|
| '/api/subscribe'
|
||||||
@ -707,6 +719,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ApiBacklinkCheckRouteImport
|
preLoaderRoute: typeof ApiBacklinkCheckRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/_marketing/support': {
|
||||||
|
id: '/_marketing/support'
|
||||||
|
path: '/support'
|
||||||
|
fullPath: '/support'
|
||||||
|
preLoaderRoute: typeof MarketingSupportRouteImport
|
||||||
|
parentRoute: typeof MarketingRoute
|
||||||
|
}
|
||||||
'/_marketing/roadmap': {
|
'/_marketing/roadmap': {
|
||||||
id: '/_marketing/roadmap'
|
id: '/_marketing/roadmap'
|
||||||
path: '/roadmap'
|
path: '/roadmap'
|
||||||
@ -933,6 +952,7 @@ interface MarketingRouteChildren {
|
|||||||
MarketingOpenSourceSeoRoute: typeof MarketingOpenSourceSeoRoute
|
MarketingOpenSourceSeoRoute: typeof MarketingOpenSourceSeoRoute
|
||||||
MarketingPricingRoute: typeof MarketingPricingRoute
|
MarketingPricingRoute: typeof MarketingPricingRoute
|
||||||
MarketingRoadmapRoute: typeof MarketingRoadmapRoute
|
MarketingRoadmapRoute: typeof MarketingRoadmapRoute
|
||||||
|
MarketingSupportRoute: typeof MarketingSupportRoute
|
||||||
MarketingIndexRoute: typeof MarketingIndexRoute
|
MarketingIndexRoute: typeof MarketingIndexRoute
|
||||||
MarketingFeaturesAiBrandVisibilityRoute: typeof MarketingFeaturesAiBrandVisibilityRoute
|
MarketingFeaturesAiBrandVisibilityRoute: typeof MarketingFeaturesAiBrandVisibilityRoute
|
||||||
MarketingFeaturesAiSearchPromptsRoute: typeof MarketingFeaturesAiSearchPromptsRoute
|
MarketingFeaturesAiSearchPromptsRoute: typeof MarketingFeaturesAiSearchPromptsRoute
|
||||||
@ -968,6 +988,7 @@ const MarketingRouteChildren: MarketingRouteChildren = {
|
|||||||
MarketingOpenSourceSeoRoute: MarketingOpenSourceSeoRoute,
|
MarketingOpenSourceSeoRoute: MarketingOpenSourceSeoRoute,
|
||||||
MarketingPricingRoute: MarketingPricingRoute,
|
MarketingPricingRoute: MarketingPricingRoute,
|
||||||
MarketingRoadmapRoute: MarketingRoadmapRoute,
|
MarketingRoadmapRoute: MarketingRoadmapRoute,
|
||||||
|
MarketingSupportRoute: MarketingSupportRoute,
|
||||||
MarketingIndexRoute: MarketingIndexRoute,
|
MarketingIndexRoute: MarketingIndexRoute,
|
||||||
MarketingFeaturesAiBrandVisibilityRoute:
|
MarketingFeaturesAiBrandVisibilityRoute:
|
||||||
MarketingFeaturesAiBrandVisibilityRoute,
|
MarketingFeaturesAiBrandVisibilityRoute,
|
||||||
|
|||||||
163
web/src/routes/_marketing/support.tsx
Normal file
163
web/src/routes/_marketing/support.tsx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { buildPageSeo } from "@/lib/seo";
|
||||||
|
|
||||||
|
const SUPPORT_EMAIL = "ben@openseo.so";
|
||||||
|
const DISCORD_URL = "https://discord.gg/c9uGs3cFXr";
|
||||||
|
const GITHUB_ISSUES_URL = "https://github.com/every-app/open-seo/issues";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_marketing/support")({
|
||||||
|
head: () =>
|
||||||
|
buildPageSeo({
|
||||||
|
title: "Support",
|
||||||
|
description:
|
||||||
|
"Get help with OpenSEO, share feedback, or report an issue by email, Discord, or GitHub.",
|
||||||
|
path: "/support",
|
||||||
|
titleSuffix: "OpenSEO",
|
||||||
|
}),
|
||||||
|
component: SupportPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
function SupportPage() {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
async function copyEmail() {
|
||||||
|
await navigator.clipboard.writeText(SUPPORT_EMAIL);
|
||||||
|
setCopied(true);
|
||||||
|
window.setTimeout(() => setCopied(false), 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className="mx-auto max-w-4xl">
|
||||||
|
<header className="max-w-3xl">
|
||||||
|
<p className="text-sm font-medium text-[var(--color-brand-accent)]">
|
||||||
|
Help & Community
|
||||||
|
</p>
|
||||||
|
<h1 className="mt-3 text-4xl font-semibold leading-tight tracking-tight text-neutral-950 md:text-6xl">
|
||||||
|
We want to hear from you
|
||||||
|
</h1>
|
||||||
|
<p className="mt-5 max-w-2xl text-lg leading-8 text-[var(--color-brand-muted)]">
|
||||||
|
We want to talk to you! We're super open to feedback and want to
|
||||||
|
learn how you work so we can make OpenSEO better.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="mt-12 grid gap-4 md:grid-cols-3">
|
||||||
|
<section className="flex min-h-64 flex-col rounded-xl border border-[var(--color-border-subtle)] bg-white p-6">
|
||||||
|
<p className="font-mono text-xs text-[var(--color-brand-accent)]">
|
||||||
|
01
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-6 text-xl font-semibold tracking-tight text-neutral-950">
|
||||||
|
Email
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||||
|
Send ideas, problems, questions, or feedback directly.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={copyEmail}
|
||||||
|
aria-live="polite"
|
||||||
|
className="mt-auto inline-flex w-fit items-center gap-2 pt-6 text-sm font-medium text-neutral-950 transition-colors hover:text-[var(--color-brand-accent)]"
|
||||||
|
>
|
||||||
|
<span className="font-mono text-xs">{SUPPORT_EMAIL}</span>
|
||||||
|
{copied ? <CheckIcon /> : <CopyIcon />}
|
||||||
|
<span className="sr-only">{copied ? "Copied" : "Copy email"}</span>
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<SupportCard
|
||||||
|
number="02"
|
||||||
|
title="Discord"
|
||||||
|
description="Ask for help, share ideas and learn from the community."
|
||||||
|
href={DISCORD_URL}
|
||||||
|
linkText="Join the Discord"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SupportCard
|
||||||
|
number="03"
|
||||||
|
title="GitHub Issues"
|
||||||
|
description="Report bugs or request features on GitHub."
|
||||||
|
href={GITHUB_ISSUES_URL}
|
||||||
|
linkText="Open an issue"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SupportCard({
|
||||||
|
number,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
href,
|
||||||
|
linkText,
|
||||||
|
}: {
|
||||||
|
number: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
href: string;
|
||||||
|
linkText: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="group flex min-h-64 flex-col rounded-xl border border-[var(--color-border-subtle)] bg-white p-6 transition-all hover:-translate-y-0.5 hover:border-neutral-950 hover:shadow-lg hover:shadow-neutral-900/5"
|
||||||
|
>
|
||||||
|
<p className="font-mono text-xs text-[var(--color-brand-accent)]">
|
||||||
|
{number}
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-6 text-xl font-semibold tracking-tight text-neutral-950">
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
<span className="mt-auto inline-flex items-center gap-2 pt-6 text-sm font-medium text-neutral-950">
|
||||||
|
{linkText}
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="transition-transform group-hover:translate-x-1"
|
||||||
|
>
|
||||||
|
→
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CopyIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="h-3.5 w-3.5 text-neutral-400"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
||||||
|
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CheckIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="h-3.5 w-3.5 text-[var(--color-brand-accent)]"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="m20 6-11 11-5-5" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user