Add public support page (#518)
This commit is contained in:
parent
4ba2d6c175
commit
d403954323
@ -29,6 +29,7 @@ const STATIC_PATHS = [
|
||||
"/open-source-seo",
|
||||
"/google-search-console-mcp",
|
||||
"/roadmap",
|
||||
"/support",
|
||||
...Object.values(FEATURE_PAGE_SLUGS).map((slug) => `/features/${slug}`),
|
||||
];
|
||||
|
||||
|
||||
@ -60,6 +60,7 @@ export function SiteFooter({ className }: { className?: string }) {
|
||||
<div>
|
||||
<p className="font-semibold text-neutral-900">Company</p>
|
||||
<div className="mt-2 flex flex-col gap-1.5">
|
||||
<Link to="/support">Support</Link>
|
||||
<Link to="/roadmap">Roadmap</Link>
|
||||
<Link to="/pricing">Pricing</Link>
|
||||
<a
|
||||
|
||||
@ -23,6 +23,7 @@ import { Route as BlogsSplatRouteImport } from './routes/blogs/$'
|
||||
import { Route as ApiSubscribeRouteImport } from './routes/api/subscribe'
|
||||
import { Route as ApiEventRouteImport } from './routes/api/event'
|
||||
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 MarketingPricingRouteImport } from './routes/_marketing/pricing'
|
||||
import { Route as MarketingOpenSourceSeoRouteImport } from './routes/_marketing/open-source-seo'
|
||||
@ -124,6 +125,11 @@ const ApiBacklinkCheckRoute = ApiBacklinkCheckRouteImport.update({
|
||||
path: '/api/backlink-check',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const MarketingSupportRoute = MarketingSupportRouteImport.update({
|
||||
id: '/support',
|
||||
path: '/support',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingRoadmapRoute = MarketingRoadmapRouteImport.update({
|
||||
id: '/roadmap',
|
||||
path: '/roadmap',
|
||||
@ -318,6 +324,7 @@ export interface FileRoutesByFullPath {
|
||||
'/open-source-seo': typeof MarketingOpenSourceSeoRoute
|
||||
'/pricing': typeof MarketingPricingRoute
|
||||
'/roadmap': typeof MarketingRoadmapRoute
|
||||
'/support': typeof MarketingSupportRoute
|
||||
'/api/backlink-check': typeof ApiBacklinkCheckRoute
|
||||
'/api/event': typeof ApiEventRoute
|
||||
'/api/subscribe': typeof ApiSubscribeRoute
|
||||
@ -363,6 +370,7 @@ export interface FileRoutesByTo {
|
||||
'/open-source-seo': typeof MarketingOpenSourceSeoRoute
|
||||
'/pricing': typeof MarketingPricingRoute
|
||||
'/roadmap': typeof MarketingRoadmapRoute
|
||||
'/support': typeof MarketingSupportRoute
|
||||
'/api/backlink-check': typeof ApiBacklinkCheckRoute
|
||||
'/api/event': typeof ApiEventRoute
|
||||
'/api/subscribe': typeof ApiSubscribeRoute
|
||||
@ -411,6 +419,7 @@ export interface FileRoutesById {
|
||||
'/_marketing/open-source-seo': typeof MarketingOpenSourceSeoRoute
|
||||
'/_marketing/pricing': typeof MarketingPricingRoute
|
||||
'/_marketing/roadmap': typeof MarketingRoadmapRoute
|
||||
'/_marketing/support': typeof MarketingSupportRoute
|
||||
'/api/backlink-check': typeof ApiBacklinkCheckRoute
|
||||
'/api/event': typeof ApiEventRoute
|
||||
'/api/subscribe': typeof ApiSubscribeRoute
|
||||
@ -460,6 +469,7 @@ export interface FileRouteTypes {
|
||||
| '/open-source-seo'
|
||||
| '/pricing'
|
||||
| '/roadmap'
|
||||
| '/support'
|
||||
| '/api/backlink-check'
|
||||
| '/api/event'
|
||||
| '/api/subscribe'
|
||||
@ -505,6 +515,7 @@ export interface FileRouteTypes {
|
||||
| '/open-source-seo'
|
||||
| '/pricing'
|
||||
| '/roadmap'
|
||||
| '/support'
|
||||
| '/api/backlink-check'
|
||||
| '/api/event'
|
||||
| '/api/subscribe'
|
||||
@ -552,6 +563,7 @@ export interface FileRouteTypes {
|
||||
| '/_marketing/open-source-seo'
|
||||
| '/_marketing/pricing'
|
||||
| '/_marketing/roadmap'
|
||||
| '/_marketing/support'
|
||||
| '/api/backlink-check'
|
||||
| '/api/event'
|
||||
| '/api/subscribe'
|
||||
@ -707,6 +719,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof ApiBacklinkCheckRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/_marketing/support': {
|
||||
id: '/_marketing/support'
|
||||
path: '/support'
|
||||
fullPath: '/support'
|
||||
preLoaderRoute: typeof MarketingSupportRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/roadmap': {
|
||||
id: '/_marketing/roadmap'
|
||||
path: '/roadmap'
|
||||
@ -933,6 +952,7 @@ interface MarketingRouteChildren {
|
||||
MarketingOpenSourceSeoRoute: typeof MarketingOpenSourceSeoRoute
|
||||
MarketingPricingRoute: typeof MarketingPricingRoute
|
||||
MarketingRoadmapRoute: typeof MarketingRoadmapRoute
|
||||
MarketingSupportRoute: typeof MarketingSupportRoute
|
||||
MarketingIndexRoute: typeof MarketingIndexRoute
|
||||
MarketingFeaturesAiBrandVisibilityRoute: typeof MarketingFeaturesAiBrandVisibilityRoute
|
||||
MarketingFeaturesAiSearchPromptsRoute: typeof MarketingFeaturesAiSearchPromptsRoute
|
||||
@ -968,6 +988,7 @@ const MarketingRouteChildren: MarketingRouteChildren = {
|
||||
MarketingOpenSourceSeoRoute: MarketingOpenSourceSeoRoute,
|
||||
MarketingPricingRoute: MarketingPricingRoute,
|
||||
MarketingRoadmapRoute: MarketingRoadmapRoute,
|
||||
MarketingSupportRoute: MarketingSupportRoute,
|
||||
MarketingIndexRoute: MarketingIndexRoute,
|
||||
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