This commit is contained in:
Ben Senescu 2026-04-04 18:47:32 -04:00 committed by Ben Senescu
parent bbed7c760a
commit 5e9ebaf19f
3 changed files with 144 additions and 1 deletions

View File

@ -1,6 +1,12 @@
import * as React from "react"; import * as React from "react";
import { Link, useLocation } from "@tanstack/react-router"; import { Link, useLocation } from "@tanstack/react-router";
import { ChevronsUpDown, CreditCard, Menu, User } from "lucide-react"; import {
ChevronsUpDown,
CircleHelp,
CreditCard,
Menu,
User,
} from "lucide-react";
import { import {
AppContent, AppContent,
MissingSeoSetupModal, MissingSeoSetupModal,
@ -15,6 +21,7 @@ import { BILLING_ROUTE } from "@/shared/billing";
import { getSeoApiKeyStatus } from "@/serverFunctions/config"; import { getSeoApiKeyStatus } from "@/serverFunctions/config";
const DATAFORSEO_HELP_PATH = "/help/dataforseo-api-key"; const DATAFORSEO_HELP_PATH = "/help/dataforseo-api-key";
const SUPPORT_PATH = "/support";
export function AuthenticatedAppLayout({ export function AuthenticatedAppLayout({
children, children,
@ -142,6 +149,7 @@ function TopNav({
onOpenDrawer: () => void; onOpenDrawer: () => void;
}) { }) {
const projectNavItems = projectId ? getProjectNavItems(projectId) : []; const projectNavItems = projectId ? getProjectNavItems(projectId) : [];
const isSupportActive = pathname === SUPPORT_PATH;
return ( return (
<div className="navbar shrink-0 gap-2 border-b border-base-300 bg-base-100"> <div className="navbar shrink-0 gap-2 border-b border-base-300 bg-base-100">
@ -192,6 +200,19 @@ function TopNav({
<div className="flex-1" /> <div className="flex-1" />
<div className="hidden flex-none items-center gap-2 md:flex"> <div className="hidden flex-none items-center gap-2 md:flex">
<div className="tooltip tooltip-bottom" data-tip="Help & Community">
<Link
to={SUPPORT_PATH}
className={`btn btn-ghost btn-circle btn-sm ${
isSupportActive
? "bg-primary/10 text-primary"
: "text-base-content/60 hover:text-base-content"
}`}
>
<CircleHelp className="h-4 w-4" />
</Link>
</div>
<div className="flex items-center rounded-full border border-base-300 bg-base-100/70 px-1 py-1 shadow-sm"> <div className="flex items-center rounded-full border border-base-300 bg-base-100/70 px-1 py-1 shadow-sm">
<div <div
className="tooltip tooltip-left before:whitespace-nowrap" className="tooltip tooltip-left before:whitespace-nowrap"
@ -254,6 +275,14 @@ function AccountMenu({ mobileOnly = false }: { mobileOnly?: boolean }) {
<span className="truncate text-base-content">{email}</span> <span className="truncate text-base-content">{email}</span>
</li> </li>
) : null} ) : null}
{mobileOnly ? (
<li>
<Link to={SUPPORT_PATH} className="flex items-center gap-2">
<CircleHelp className="h-4 w-4" />
Help & Community
</Link>
</li>
) : null}
{isHostedMode ? ( {isHostedMode ? (
<li> <li>
<a href={BILLING_ROUTE} className="flex items-center gap-2"> <a href={BILLING_ROUTE} className="flex items-center gap-2">

View File

@ -20,6 +20,7 @@ import { Route as AppIndexRouteImport } from './routes/_app/index'
import { Route as AuthenticatedSubscribeRouteImport } from './routes/_authenticated.subscribe' import { Route as AuthenticatedSubscribeRouteImport } from './routes/_authenticated.subscribe'
import { Route as AuthSignUpRouteImport } from './routes/_auth.sign-up' import { Route as AuthSignUpRouteImport } from './routes/_auth.sign-up'
import { Route as AuthSignInRouteImport } from './routes/_auth.sign-in' import { Route as AuthSignInRouteImport } from './routes/_auth.sign-in'
import { Route as AppSupportRouteImport } from './routes/_app/support'
import { Route as AppBillingRouteImport } from './routes/_app/billing' import { Route as AppBillingRouteImport } from './routes/_app/billing'
import { Route as ApiAutumnSplatRouteImport } from './routes/api/autumn/$' import { Route as ApiAutumnSplatRouteImport } from './routes/api/autumn/$'
import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$' import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
@ -86,6 +87,11 @@ const AuthSignInRoute = AuthSignInRouteImport.update({
path: '/sign-in', path: '/sign-in',
getParentRoute: () => AuthRoute, getParentRoute: () => AuthRoute,
} as any) } as any)
const AppSupportRoute = AppSupportRouteImport.update({
id: '/support',
path: '/support',
getParentRoute: () => AppRouteRoute,
} as any)
const AppBillingRoute = AppBillingRouteImport.update({ const AppBillingRoute = AppBillingRouteImport.update({
id: '/billing', id: '/billing',
path: '/billing', path: '/billing',
@ -167,6 +173,7 @@ export interface FileRoutesByFullPath {
'/reset-password': typeof ResetPasswordRoute '/reset-password': typeof ResetPasswordRoute
'/verify-email': typeof VerifyEmailRoute '/verify-email': typeof VerifyEmailRoute
'/billing': typeof AppBillingRoute '/billing': typeof AppBillingRoute
'/support': typeof AppSupportRoute
'/sign-in': typeof AuthSignInRoute '/sign-in': typeof AuthSignInRoute
'/sign-up': typeof AuthSignUpRoute '/sign-up': typeof AuthSignUpRoute
'/subscribe': typeof AuthenticatedSubscribeRoute '/subscribe': typeof AuthenticatedSubscribeRoute
@ -190,6 +197,7 @@ export interface FileRoutesByTo {
'/reset-password': typeof ResetPasswordRoute '/reset-password': typeof ResetPasswordRoute
'/verify-email': typeof VerifyEmailRoute '/verify-email': typeof VerifyEmailRoute
'/billing': typeof AppBillingRoute '/billing': typeof AppBillingRoute
'/support': typeof AppSupportRoute
'/sign-in': typeof AuthSignInRoute '/sign-in': typeof AuthSignInRoute
'/sign-up': typeof AuthSignUpRoute '/sign-up': typeof AuthSignUpRoute
'/subscribe': typeof AuthenticatedSubscribeRoute '/subscribe': typeof AuthenticatedSubscribeRoute
@ -215,6 +223,7 @@ export interface FileRoutesById {
'/reset-password': typeof ResetPasswordRoute '/reset-password': typeof ResetPasswordRoute
'/verify-email': typeof VerifyEmailRoute '/verify-email': typeof VerifyEmailRoute
'/_app/billing': typeof AppBillingRoute '/_app/billing': typeof AppBillingRoute
'/_app/support': typeof AppSupportRoute
'/_auth/sign-in': typeof AuthSignInRoute '/_auth/sign-in': typeof AuthSignInRoute
'/_auth/sign-up': typeof AuthSignUpRoute '/_auth/sign-up': typeof AuthSignUpRoute
'/_authenticated/subscribe': typeof AuthenticatedSubscribeRoute '/_authenticated/subscribe': typeof AuthenticatedSubscribeRoute
@ -241,6 +250,7 @@ export interface FileRouteTypes {
| '/reset-password' | '/reset-password'
| '/verify-email' | '/verify-email'
| '/billing' | '/billing'
| '/support'
| '/sign-in' | '/sign-in'
| '/sign-up' | '/sign-up'
| '/subscribe' | '/subscribe'
@ -264,6 +274,7 @@ export interface FileRouteTypes {
| '/reset-password' | '/reset-password'
| '/verify-email' | '/verify-email'
| '/billing' | '/billing'
| '/support'
| '/sign-in' | '/sign-in'
| '/sign-up' | '/sign-up'
| '/subscribe' | '/subscribe'
@ -288,6 +299,7 @@ export interface FileRouteTypes {
| '/reset-password' | '/reset-password'
| '/verify-email' | '/verify-email'
| '/_app/billing' | '/_app/billing'
| '/_app/support'
| '/_auth/sign-in' | '/_auth/sign-in'
| '/_auth/sign-up' | '/_auth/sign-up'
| '/_authenticated/subscribe' | '/_authenticated/subscribe'
@ -398,6 +410,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthSignInRouteImport preLoaderRoute: typeof AuthSignInRouteImport
parentRoute: typeof AuthRoute parentRoute: typeof AuthRoute
} }
'/_app/support': {
id: '/_app/support'
path: '/support'
fullPath: '/support'
preLoaderRoute: typeof AppSupportRouteImport
parentRoute: typeof AppRouteRoute
}
'/_app/billing': { '/_app/billing': {
id: '/_app/billing' id: '/_app/billing'
path: '/billing' path: '/billing'
@ -501,12 +520,14 @@ declare module '@tanstack/react-router' {
interface AppRouteRouteChildren { interface AppRouteRouteChildren {
AppBillingRoute: typeof AppBillingRoute AppBillingRoute: typeof AppBillingRoute
AppSupportRoute: typeof AppSupportRoute
AppIndexRoute: typeof AppIndexRoute AppIndexRoute: typeof AppIndexRoute
AppHelpDataforseoApiKeyRoute: typeof AppHelpDataforseoApiKeyRoute AppHelpDataforseoApiKeyRoute: typeof AppHelpDataforseoApiKeyRoute
} }
const AppRouteRouteChildren: AppRouteRouteChildren = { const AppRouteRouteChildren: AppRouteRouteChildren = {
AppBillingRoute: AppBillingRoute, AppBillingRoute: AppBillingRoute,
AppSupportRoute: AppSupportRoute,
AppIndexRoute: AppIndexRoute, AppIndexRoute: AppIndexRoute,
AppHelpDataforseoApiKeyRoute: AppHelpDataforseoApiKeyRoute, AppHelpDataforseoApiKeyRoute: AppHelpDataforseoApiKeyRoute,
} }

View File

@ -0,0 +1,93 @@
import { createFileRoute } from "@tanstack/react-router";
import { Check, Copy } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
const SUPPORT_EMAIL = "ben@openseo.so";
const DISCORD_URL = "https://discord.gg/c9uGs3cFXr";
const GITHUB_URL = "https://github.com/every-app/open-seo";
export const Route = createFileRoute("/_app/support")({
component: SupportPage,
});
function SupportPage() {
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
await navigator.clipboard.writeText(SUPPORT_EMAIL);
toast.success("Email copied to clipboard");
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="h-full overflow-auto bg-base-100 px-4 py-8 pb-24 md:px-6 md:py-12 md:pb-8">
<div className="mx-auto max-w-xl">
<p className="text-sm font-medium text-base-content/40">
Help & Community
</p>
<h1 className="mt-1 text-2xl font-bold tracking-tight">
We want to hear from you
</h1>
<p className="mt-2 text-sm text-base-content/60">
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>
<div className="mt-8 space-y-3">
<div className="rounded-lg border border-base-300 px-5 py-4">
<p className="text-sm font-semibold">Email</p>
<p className="mt-1 text-sm text-base-content/60">
Send ideas, problems, questions, or feedback directly.
</p>
<button
type="button"
onClick={handleCopy}
className="mt-3 inline-flex items-center gap-2 rounded-md border border-base-300 bg-base-200/50 px-3 py-1.5 text-sm font-medium text-base-content transition-colors hover:bg-base-200"
>
<span className="font-mono text-xs">{SUPPORT_EMAIL}</span>
{copied ? (
<Check className="size-3.5 text-success" />
) : (
<Copy className="size-3.5 text-base-content/40" />
)}
</button>
</div>
<a
href={DISCORD_URL}
target="_blank"
rel="noreferrer"
className="block rounded-lg border border-base-300 px-5 py-4 transition-colors hover:border-base-content/20"
>
<p className="text-sm font-semibold">Discord</p>
<p className="mt-1 text-sm text-base-content/60">
Ask for help, share ideas and learn from the community.
</p>
<span className="mt-3 inline-flex items-center gap-1.5 text-sm font-medium text-base-content">
Join the Discord
<span aria-hidden="true">&rarr;</span>
</span>
</a>
<a
href={`${GITHUB_URL}/issues`}
target="_blank"
rel="noreferrer"
className="block rounded-lg border border-base-300 px-5 py-4 transition-colors hover:border-base-content/20"
>
<p className="text-sm font-semibold">GitHub Issues</p>
<p className="mt-1 text-sm text-base-content/60">
Report bugs or request features on GitHub.
</p>
<span className="mt-3 inline-flex items-center gap-1.5 text-sm font-medium text-base-content">
Open an issue
<span aria-hidden="true">&rarr;</span>
</span>
</a>
</div>
</div>
</div>
);
}