Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/og/pages/de/support.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/og/pages/en/support.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/og/pages/fr/support.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/og/pages/ja/support.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/og/pages/ko/support.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/og/pages/zh-CN/support.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/og/pages/zh-TW/support.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 10 additions & 3 deletions src/app/[lang]/contact/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Link from "next/link"
import { useLang } from "@/core/i18n/lang-provider"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import { Github, Mail, MessageSquare, ExternalLink, ShieldCheck, Map, ThumbsUp } from "lucide-react"
import { Github, Mail, MessageSquare, ExternalLink, ShieldCheck, Map, ThumbsUp, HeartHandshake } from "lucide-react"
import { safeClipboardWrite } from "@/core/clipboard/clipboard"

const EMAIL_ADDRESS = "contact@byteflow.tools"
Expand All @@ -24,6 +24,7 @@ export default function ContactPage() {
{ icon: Map, title: t.common.request_tool, desc: p.contact_request_tool_desc, href: GITHUB_FEATURE_REQUEST_URL, external: true },
{ icon: ThumbsUp, title: t.common.vote_on_requests, desc: p.contact_vote_requests_desc, href: GITHUB_REQUEST_VOTING_URL, external: true },
{ icon: ShieldCheck, title: p.contact_security_title, desc: p.contact_security_desc, href: SECURITY_ADVISORY_URL, external: true },
{ icon: HeartHandshake, title: p.support_title, desc: p.contact_support_desc, href: `/${lang}/support`, external: false },
]

const handleCopyEmail = async () => {
Expand All @@ -46,7 +47,7 @@ export default function ContactPage() {

<section className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{links.map((link) => (
<a
<Link
key={link.title}
href={link.href}
target={link.external ? "_blank" : undefined}
Expand All @@ -59,7 +60,7 @@ export default function ContactPage() {
</div>
<h2 className="mt-3 text-sm font-semibold">{link.title}</h2>
<p className="mt-1.5 text-xs leading-relaxed text-muted-foreground">{link.desc}</p>
</a>
</Link>
))}
<div className="group rounded-2xl border border-border/70 bg-background/55 p-5 transition-colors duration-200 hover:border-primary/35">
<div className="flex items-center justify-between">
Expand Down Expand Up @@ -97,6 +98,12 @@ export default function ContactPage() {
>
{p.contact_self_hosting_link}
</Link>
<Link
href={`/${lang}/support`}
className="inline-flex min-h-10 items-center gap-2 rounded-md border border-border/75 bg-background/70 px-3 text-sm font-medium hover:border-primary/35 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
{p.support_title}
</Link>
</div>
</section>
</div>
Expand Down
13 changes: 5 additions & 8 deletions src/app/[lang]/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,12 @@ export default function PricingPage() {
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
{p.pricing_support_desc}
</p>
<a
href="https://github.com/baixiangcpp/byteflow.tools"
target="_blank"
rel="noopener noreferrer"
<Link
href={`/${lang}/support`}
className="mt-4 inline-flex min-h-10 items-center gap-2 rounded-md border border-border/75 bg-background/70 px-3 text-sm font-medium hover:border-primary/35 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
{p.pricing_github_project}
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
</a>
{p.support_title}
</Link>
</article>
<article className="rounded-2xl border border-border/70 bg-background/55 p-5">
<ServerCog className="h-5 w-5 text-primary" aria-hidden="true" />
Expand All @@ -110,7 +107,7 @@ export default function PricingPage() {
{p.self_hosting_title}
</Link>
<Link
href={`/${lang}/contact`}
href={`/${lang}/support`}
className="inline-flex min-h-10 items-center gap-2 rounded-md border border-border/75 bg-background/70 px-3 text-sm font-medium hover:border-primary/35 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<Mail className="h-3.5 w-3.5" aria-hidden="true" />
Expand Down
10 changes: 9 additions & 1 deletion src/app/[lang]/self-hosting/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Link from "next/link"
import { notFound } from "next/navigation"
import { Server, ShieldCheck, WifiOff } from "lucide-react"
import { HeartHandshake, Server, ShieldCheck, WifiOff } from "lucide-react"
import type { Locale } from "@/core/i18n/i18n"
import { isValidLocale } from "@/core/i18n/i18n"
import { getTranslation } from "@/core/i18n/translations/catalog"
Expand Down Expand Up @@ -160,6 +160,10 @@ export default async function SelfHostingPage({ params }: { params: Promise<{ la
<ShieldCheck className="h-4 w-4" aria-hidden="true" />
{t.pages.trust_center_title}
</Link>
<Link className="inline-flex min-h-10 items-center gap-2 rounded-md border border-border/75 bg-background/70 px-3 text-sm font-medium hover:border-primary/35 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" href={`/${lang}/support`}>
<HeartHandshake className="h-4 w-4" aria-hidden="true" />
{t.pages.support_title}
</Link>
</div>
</section>

Expand Down Expand Up @@ -189,6 +193,10 @@ export default async function SelfHostingPage({ params }: { params: Promise<{ la
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
{copy.boundaryBody}
</p>
<Link className="mt-4 inline-flex min-h-10 items-center gap-2 rounded-md border border-border/75 bg-background/70 px-3 text-sm font-medium hover:border-primary/35 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" href={`/${lang}/support`}>
<HeartHandshake className="h-3.5 w-3.5" aria-hidden="true" />
{t.pages.support_private_deployment_title}
</Link>
</article>
</section>
</div>
Expand Down
21 changes: 21 additions & 0 deletions src/app/[lang]/support/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Metadata } from "next"
import { notFound } from "next/navigation"
import { isValidLocale } from "@/core/i18n/i18n"
import { getTranslation } from "@/core/i18n/translations/catalog"
import { buildStaticPageMetadata } from "@/core/seo/seo"

export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise<Metadata> {
const { lang } = await params
if (!isValidLocale(lang)) notFound()
const t = getTranslation(lang)
return buildStaticPageMetadata({
lang,
slug: "support",
title: t.pages.support_title,
description: t.pages.support_intro,
})
}

export default function Layout({ children }: { children: React.ReactNode }) {
return <>{children}</>
}
89 changes: 89 additions & 0 deletions src/app/[lang]/support/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import Link from "next/link"
import { notFound } from "next/navigation"
import { ArrowUpRight, CheckCircle2, HeartHandshake, Mail, ServerCog, ShieldCheck } from "lucide-react"
import { isValidLocale } from "@/core/i18n/i18n"
import { getTranslation } from "@/core/i18n/translations/catalog"

const EMAIL_ADDRESS = "contact@byteflow.tools"
const GITHUB_REPOSITORY_URL = "https://github.com/baixiangcpp/byteflow.tools"

export default async function SupportPage({ params }: { params: Promise<{ lang: string }> }) {
const { lang } = await params
if (!isValidLocale(lang)) notFound()
const t = getTranslation(lang)
const p = t.pages

const boundaries = [
p.support_boundary_free,
p.support_boundary_no_tracking,
p.support_boundary_no_payload,
p.support_boundary_no_account,
]

return (
<div className="mx-auto w-full max-w-5xl space-y-8">
<section className="rounded-lg border border-border/70 bg-card/55 p-6 sm:p-7">
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-primary">{p.support_badge}</p>
<h1 className="mt-3 text-3xl font-semibold tracking-tight">{p.support_title}</h1>
<p className="mt-3 max-w-3xl text-sm leading-relaxed text-muted-foreground sm:text-base">
{p.support_intro}
</p>
<div className="mt-5 flex flex-wrap gap-2">
<a
className="inline-flex min-h-10 items-center gap-2 rounded-md border border-border/75 bg-background/70 px-3 text-sm font-medium hover:border-primary/35 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
href={`mailto:${EMAIL_ADDRESS}?subject=${encodeURIComponent(p.support_title)}`}
>
<Mail className="h-4 w-4" aria-hidden="true" />
{p.support_cta_email}
</a>
<Link
className="inline-flex min-h-10 items-center gap-2 rounded-md border border-border/75 bg-background/70 px-3 text-sm font-medium hover:border-primary/35 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
href={`/${lang}/self-hosting`}
>
<ServerCog className="h-4 w-4" aria-hidden="true" />
{p.self_hosting_title}
</Link>
</div>
</section>

<section className="grid gap-4 md:grid-cols-2">
<article className="rounded-lg border border-border/70 bg-background/55 p-5">
<HeartHandshake className="h-5 w-5 text-primary" aria-hidden="true" />
<h2 className="mt-3 text-lg font-semibold">{p.support_sponsor_title}</h2>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
{p.support_sponsor_desc}
</p>
</article>
<article className="rounded-lg border border-border/70 bg-background/55 p-5">
<ServerCog className="h-5 w-5 text-primary" aria-hidden="true" />
<h2 className="mt-3 text-lg font-semibold">{p.support_private_deployment_title}</h2>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
{p.support_private_deployment_desc}
</p>
</article>
</section>

<section className="rounded-lg border border-primary/30 bg-primary/10 p-5 sm:p-6">
<ShieldCheck className="h-5 w-5 text-primary" aria-hidden="true" />
<h2 className="mt-3 text-lg font-semibold">{p.support_boundaries_title}</h2>
<ul className="mt-4 space-y-3">
{boundaries.map((item) => (
<li key={item} className="flex gap-3 text-sm leading-relaxed text-muted-foreground">
<CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-primary" aria-hidden="true" />
<span>{item}</span>
</li>
))}
</ul>
<a
className="mt-5 inline-flex min-h-10 items-center gap-2 rounded-md border border-border/75 bg-background/70 px-3 text-sm font-medium hover:border-primary/35 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
href={GITHUB_REPOSITORY_URL}
target="_blank"
rel="noopener noreferrer"
>
{p.support_cta_github}
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
</a>
</section>
</div>
)
}
2 changes: 1 addition & 1 deletion src/components/layout/route-page-chrome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { getRouteToolBySlug } from "@/generated/route-tool-lookup"
import { ToolTrustHeader } from "@/features/tool-shell/tool-trust-header"
import { ToolFavoriteControl } from "./tool-favorite-control"

const EXCLUDED_CONTENT_INTRO_SLUGS = new Set(["about", "pricing", "contact", "privacy", "terms", "install-app"])
const EXCLUDED_CONTENT_INTRO_SLUGS = new Set(["about", "pricing", "contact", "privacy", "terms", "install-app", "support"])

type RoutePageChromeProps = {
children: React.ReactNode
Expand Down
1 change: 1 addition & 0 deletions src/components/layout/server-footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function ServerFooter({
{ key: "roadmap", slug: "roadmap", label: requireTranslationValue(translations.pages.roadmap_title, "pages.roadmap_title") },
{ key: "changelog", slug: "changelog", label: requireTranslationValue(translations.pages.changelog_title, "pages.changelog_title") },
{ key: "self-hosting", slug: "self-hosting", label: requireTranslationValue(translations.pages.self_hosting_title, "pages.self_hosting_title") },
{ key: "support", slug: "support", label: requireTranslationValue(translations.pages.support_title, "pages.support_title") },
{ key: "terms", slug: "terms", label: requireTranslationValue(translations.pages.terms_title, "pages.terms_title") },
{ key: "contact", slug: "contact", label: requireTranslationValue(translations.pages.contact_title, "pages.contact_title") },
].map((page) => ({
Expand Down
17 changes: 16 additions & 1 deletion src/core/i18n/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -3304,6 +3304,21 @@
"contact_public_planning_desc": "Nutzen Sie oeffentliche Anfragen nur mit bereinigten Beispielen. Posten Sie keine echten Secrets, privaten Payloads, HAR-Dateien, privaten URLs, Logs, Prompts, generierten Ausgaben, Request-Bodies oder Response-Bodies.",
"contact_roadmap_link": "Fahrplan",
"contact_distribution_research_link": "Erweiterungs- und Desktop-Recherche",
"contact_self_hosting_link": "Selbsthosting"
"contact_self_hosting_link": "Selbsthosting",
"support_title": "Datenschutzsichere Unterstützung",
"support_intro": "Unterstützen Sie byteflow.tools über einfache Sponsoring-Anfragen oder private Deployment-Hilfe, während die Kern-Tools kostenlos, Open Source, browser-lokal und ohne Konto bleiben.",
"support_badge": "Unterstützung ohne Ad-Tracking",
"support_sponsor_title": "Sponsoring-Platzierungen",
"support_sponsor_desc": "Sponsoring nutzt nur Plain-Text- oder Logo-Platzierungen mit normalen Links. Es gibt keine Tracking-Pixel, personalisierte Werbung, Ad-Skripte oder Verhaltensprofile.",
"support_private_deployment_title": "Private Deployment-Unterstützung",
"support_private_deployment_desc": "Deployment-Hilfe kann statisches Hosting, interne Bereitstellung, Sicherheitsprüfung, Packaging, Wartung und Offline-Distribution abdecken, ohne ein Cloud-Konto zu verlangen.",
"support_boundaries_title": "Datenschutzgrenzen",
"support_boundary_free": "Die Kern-Tools bleiben kostenlos und Open Source.",
"support_boundary_no_tracking": "Sponsoring-Platzierungen verwenden keine Tracking-Pixel, keine personalisierte Werbung und keine Ad-Skripte.",
"support_boundary_no_payload": "Sponsoren erhalten keinen Payload-Zugriff, keine Suchtexte, keine Nutzerkennungen und keine Verhaltensprofile.",
"support_boundary_no_account": "Für Support oder private Deployment-Hilfe ist kein Cloud-Konto und kein gehosteter Payload-Verlauf erforderlich.",
"support_cta_email": "Support kontaktieren",
"support_cta_github": "Quellcode prüfen",
"contact_support_desc": "Fragen Sie zu Sponsoring, privatem Deployment, Sicherheitsprüfung, Packaging oder Wartung an, ohne private Payloads zu teilen."
}
}
17 changes: 16 additions & 1 deletion src/core/i18n/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3304,6 +3304,21 @@
"contact_public_planning_desc": "Use public requests only with sanitized examples. Do not post real secrets, private payloads, HAR files, private URLs, logs, prompts, generated output, request bodies, or response bodies.",
"contact_roadmap_link": "Roadmap",
"contact_distribution_research_link": "Extension and desktop research",
"contact_self_hosting_link": "Self-hosting"
"contact_self_hosting_link": "Self-hosting",
"support_title": "Privacy-safe support",
"support_intro": "Support byteflow.tools through plain sponsorship inquiries or private deployment help while keeping the core tools free, open source, browser-local, and account-free.",
"support_badge": "Support without ad tracking",
"support_sponsor_title": "Sponsor placements",
"support_sponsor_desc": "Sponsorships are plain-text or logo placements with normal links only. They do not use tracking pixels, personalized ads, ad scripts, or behavioral profiling.",
"support_private_deployment_title": "Private deployment support",
"support_private_deployment_desc": "Deployment support can cover static hosting, internal deployment, security review, packaging, maintenance, and offline distribution guidance without requiring a cloud account.",
"support_boundaries_title": "Privacy boundaries",
"support_boundary_free": "Core tools remain free and open source.",
"support_boundary_no_tracking": "Sponsor placements use no tracking pixels, no personalized ads, and no ad scripts.",
"support_boundary_no_payload": "Sponsors do not receive payload access, query text, user identifiers, or behavioral profiles.",
"support_boundary_no_account": "No cloud account or hosted payload history is required for support or private deployment help.",
"support_cta_email": "Contact support",
"support_cta_github": "Review the source",
"contact_support_desc": "Ask about sponsorship, private deployment, security review, packaging, or maintenance without sharing private payloads."
}
}
Loading