fix: improve SEO
This commit is contained in:
@@ -1,3 +1,6 @@
|
|||||||
# https://www.robotstxt.org/robotstxt.html
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow:
|
Allow: /share/
|
||||||
|
Allow: /login
|
||||||
|
Allow: /onboarding
|
||||||
|
Disallow: /
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { LogoSquare } from '@/components/logo';
|
|
||||||
import {
|
import {
|
||||||
Carousel,
|
Carousel,
|
||||||
CarouselContent,
|
CarouselContent,
|
||||||
@@ -64,18 +63,8 @@ const sellingPoints = [
|
|||||||
export function LoginLeftPanel() {
|
export function LoginLeftPanel() {
|
||||||
return (
|
return (
|
||||||
<div className="relative h-screen overflow-hidden">
|
<div className="relative h-screen overflow-hidden">
|
||||||
<div className="row justify-between items-center p-8">
|
|
||||||
<LogoSquare className="h-8 w-8" />
|
|
||||||
<a
|
|
||||||
href="https://openpanel.dev"
|
|
||||||
className="text-sm text-muted-foreground"
|
|
||||||
>
|
|
||||||
Back to website →
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Carousel */}
|
{/* Carousel */}
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full mt-24">
|
||||||
<Carousel
|
<Carousel
|
||||||
className="w-full h-full [&>div]:h-full [&>div]:min-h-full"
|
className="w-full h-full [&>div]:h-full [&>div]:min-h-full"
|
||||||
opts={{
|
opts={{
|
||||||
|
|||||||
107
apps/start/src/components/login-navbar.tsx
Normal file
107
apps/start/src/components/login-navbar.tsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { useAppContext } from '@/hooks/use-app-context';
|
||||||
|
import { cn } from '@/utils/cn';
|
||||||
|
import { MenuIcon, XIcon } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { LogoSquare } from './logo';
|
||||||
|
import { Button, LinkButton } from './ui/button';
|
||||||
|
|
||||||
|
export function LoginNavbar({ className }: { className?: string }) {
|
||||||
|
const { isSelfHosted } = useAppContext();
|
||||||
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'absolute top-0 left-0 w-full row justify-between items-center p-8 z-10',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<a href="https://openpanel.dev" className="row items-center gap-2">
|
||||||
|
<LogoSquare className="size-8 shrink-0" />
|
||||||
|
<span className="font-medium text-sm text-muted-foreground">
|
||||||
|
{isSelfHosted ? 'Self-hosted analytics' : 'OpenPanel.dev'}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<nav className="max-md:hidden">
|
||||||
|
<ul className="row gap-4 items-center [&>li>a]:text-sm [&>li>a]:text-muted-foreground [&>li>a]:hover:underline">
|
||||||
|
<li>
|
||||||
|
<a href="https://openpanel.dev">OpenPanel Cloud</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://openpanel.dev/compare/mixpanel-alternative">
|
||||||
|
Mixpanel alternative
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://openpanel.dev/compare/mixpanel-alternative">
|
||||||
|
Posthog alternative
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://openpanel.dev/articles/open-source-web-analytics">
|
||||||
|
Open source analytics
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div className="md:hidden relative">
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => setMobileMenuOpen((prev) => !prev)}
|
||||||
|
>
|
||||||
|
{mobileMenuOpen ? <XIcon size={20} /> : <MenuIcon size={20} />}
|
||||||
|
</Button>
|
||||||
|
{mobileMenuOpen && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
className="fixed inset-0 z-40 bg-black/20 backdrop-blur-sm"
|
||||||
|
/>
|
||||||
|
<nav className="absolute right-0 top-full mt-2 z-50 bg-card border border-border rounded-md shadow-lg min-w-48 py-2">
|
||||||
|
<ul className="flex flex-col *:text-sm *:text-muted-foreground">
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://openpanel.dev"
|
||||||
|
className="block px-4 py-2 hover:bg-accent hover:text-accent-foreground"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
OpenPanel Cloud
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://openpanel.dev/compare/mixpanel-alternative"
|
||||||
|
className="block px-4 py-2 hover:bg-accent hover:text-accent-foreground"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
Posthog alternative
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://openpanel.dev/compare/mixpanel-alternative"
|
||||||
|
className="block px-4 py-2 hover:bg-accent hover:text-accent-foreground"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
Mixpanel alternative
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://openpanel.dev/articles/open-source-web-analytics"
|
||||||
|
className="block px-4 py-2 hover:bg-accent hover:text-accent-foreground"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
Open source analytics
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -91,19 +91,8 @@ const onboardingSellingPoints = [
|
|||||||
export function OnboardingLeftPanel() {
|
export function OnboardingLeftPanel() {
|
||||||
return (
|
return (
|
||||||
<div className="sticky top-0 h-screen overflow-hidden">
|
<div className="sticky top-0 h-screen overflow-hidden">
|
||||||
<div className="row justify-between items-center p-8">
|
|
||||||
<LogoSquare className="h-8 w-8" />
|
|
||||||
|
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
Already have an account?{' '}
|
|
||||||
<Link to="/login" className="text-sm text-muted-foreground underline">
|
|
||||||
Sign in
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Carousel */}
|
{/* Carousel */}
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full mt-24">
|
||||||
<Carousel
|
<Carousel
|
||||||
className="w-full h-full [&>div]:h-full [&>div]:min-h-full"
|
className="w-full h-full [&>div]:h-full [&>div]:min-h-full"
|
||||||
opts={{
|
opts={{
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import { z } from 'zod';
|
|||||||
export const Route = createFileRoute('/_login/login')({
|
export const Route = createFileRoute('/_login/login')({
|
||||||
component: LoginPage,
|
component: LoginPage,
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [{ title: createTitle(PAGE_TITLES.LOGIN) }],
|
meta: [
|
||||||
|
{ title: createTitle(PAGE_TITLES.LOGIN) },
|
||||||
|
{ name: 'robots', content: 'noindex, follow' },
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
validateSearch: z.object({
|
validateSearch: z.object({
|
||||||
error: z.string().optional(),
|
error: z.string().optional(),
|
||||||
@@ -26,11 +29,13 @@ function LoginPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="col gap-8 w-full text-left">
|
<div className="col gap-8 w-full text-left">
|
||||||
<div>
|
<div>
|
||||||
<LogoSquare className="size-12 mb-8 md:hidden" />
|
|
||||||
<h1 className="text-3xl font-bold text-foreground mb-2">Sign in</h1>
|
<h1 className="text-3xl font-bold text-foreground mb-2">Sign in</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Don't have an account?{' '}
|
Don't have an account?{' '}
|
||||||
<a href="/onboarding" className="underline">
|
<a
|
||||||
|
href="/onboarding"
|
||||||
|
className="underline font-medium text-foreground"
|
||||||
|
>
|
||||||
Create one today
|
Create one today
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
export const Route = createFileRoute('/_login/reset-password')({
|
export const Route = createFileRoute('/_login/reset-password')({
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [{ title: createTitle(PAGE_TITLES.RESET_PASSWORD) }],
|
meta: [
|
||||||
|
{ title: createTitle(PAGE_TITLES.RESET_PASSWORD) },
|
||||||
|
{ name: 'robots', content: 'noindex, follow' },
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
component: Component,
|
component: Component,
|
||||||
validateSearch: z.object({
|
validateSearch: z.object({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { LoginLeftPanel } from '@/components/login-left-panel';
|
import { LoginLeftPanel } from '@/components/login-left-panel';
|
||||||
|
import { LoginNavbar } from '@/components/login-navbar';
|
||||||
import { SkeletonDashboard } from '@/components/skeleton-dashboard';
|
import { SkeletonDashboard } from '@/components/skeleton-dashboard';
|
||||||
import { Outlet, createFileRoute, redirect } from '@tanstack/react-router';
|
import { Outlet, createFileRoute, redirect } from '@tanstack/react-router';
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ export const Route = createFileRoute('/_login')({
|
|||||||
function AuthLayout() {
|
function AuthLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="relative min-h-screen grid md:grid-cols-2">
|
<div className="relative min-h-screen grid md:grid-cols-2">
|
||||||
|
<LoginNavbar />
|
||||||
<div className="hidden md:block">
|
<div className="hidden md:block">
|
||||||
<LoginLeftPanel />
|
<LoginLeftPanel />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export const Route = createFileRoute('/_public/onboarding')({
|
|||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: createEntityTitle('Create an account', PAGE_TITLES.ONBOARDING) },
|
{ title: createEntityTitle('Create an account', PAGE_TITLES.ONBOARDING) },
|
||||||
|
{ name: 'robots', content: 'noindex, follow' },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
beforeLoad: async ({ context }) => {
|
beforeLoad: async ({ context }) => {
|
||||||
@@ -56,7 +57,6 @@ function Component() {
|
|||||||
return (
|
return (
|
||||||
<div className="col gap-8 w-full text-left">
|
<div className="col gap-8 w-full text-left">
|
||||||
<div>
|
<div>
|
||||||
<LogoSquare className="size-12 mb-8 md:hidden" />
|
|
||||||
<h1 className="text-3xl font-bold text-foreground mb-2">
|
<h1 className="text-3xl font-bold text-foreground mb-2">
|
||||||
Create an account
|
Create an account
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { LoginNavbar } from '@/components/login-navbar';
|
||||||
import { OnboardingLeftPanel } from '@/components/onboarding-left-panel';
|
import { OnboardingLeftPanel } from '@/components/onboarding-left-panel';
|
||||||
import { Outlet, createFileRoute, redirect } from '@tanstack/react-router';
|
import { Outlet, createFileRoute, redirect } from '@tanstack/react-router';
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ export const Route = createFileRoute('/_public')({
|
|||||||
function OnboardingLayout() {
|
function OnboardingLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="relative min-h-screen grid md:grid-cols-2">
|
<div className="relative min-h-screen grid md:grid-cols-2">
|
||||||
|
<LoginNavbar />
|
||||||
<div className="hidden md:block">
|
<div className="hidden md:block">
|
||||||
<OnboardingLeftPanel />
|
<OnboardingLeftPanel />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ShareEnterPassword } from '@/components/auth/share-enter-password';
|
import { ShareEnterPassword } from '@/components/auth/share-enter-password';
|
||||||
import { FullPageEmptyState } from '@/components/full-page-empty-state';
|
import { FullPageEmptyState } from '@/components/full-page-empty-state';
|
||||||
import FullPageLoadingState from '@/components/full-page-loading-state';
|
import FullPageLoadingState from '@/components/full-page-loading-state';
|
||||||
|
import { LoginNavbar } from '@/components/login-navbar';
|
||||||
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
|
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
|
||||||
import { LiveCounter } from '@/components/overview/live-counter';
|
import { LiveCounter } from '@/components/overview/live-counter';
|
||||||
import OverviewMetrics from '@/components/overview/overview-metrics';
|
import OverviewMetrics from '@/components/overview/overview-metrics';
|
||||||
@@ -24,11 +25,32 @@ export const Route = createFileRoute('/share/overview/$shareId')({
|
|||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
validateSearch: shareSearchSchema,
|
validateSearch: shareSearchSchema,
|
||||||
loader: async ({ context, params }) => {
|
loader: async ({ context, params }) => {
|
||||||
await context.queryClient.prefetchQuery(
|
const share = await context.queryClient.ensureQueryData(
|
||||||
context.trpc.share.overview.queryOptions({
|
context.trpc.share.overview.queryOptions({
|
||||||
shareId: params.shareId,
|
shareId: params.shareId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return { share };
|
||||||
|
},
|
||||||
|
head: ({ loaderData }) => {
|
||||||
|
if (!loaderData || !loaderData.share) {
|
||||||
|
return {
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
title: 'Share not found - OpenPanel.dev',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
title: `${loaderData.share.project?.name} - ${loaderData.share.organization?.name} - OpenPanel.dev`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
},
|
},
|
||||||
pendingComponent: FullPageLoadingState,
|
pendingComponent: FullPageLoadingState,
|
||||||
errorComponent: () => (
|
errorComponent: () => (
|
||||||
@@ -78,19 +100,8 @@ function RouteComponent() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{isHeaderVisible && (
|
{isHeaderVisible && (
|
||||||
<div className="mx-auto max-w-7xl justify-between row gap-4 p-4 pb-0">
|
<div className="mx-auto max-w-7xl">
|
||||||
<div className="col gap-1">
|
<LoginNavbar className="relative p-4" />
|
||||||
<span className="text-sm">{share.organization?.name}</span>
|
|
||||||
<h1 className="text-xl font-medium">{share.project?.name}</h1>
|
|
||||||
</div>
|
|
||||||
<a
|
|
||||||
href="https://openpanel.dev?utm_source=openpanel.dev&utm_medium=share"
|
|
||||||
className="col gap-1 items-end"
|
|
||||||
title="OpenPanel"
|
|
||||||
>
|
|
||||||
<span className="text-xs">POWERED BY</span>
|
|
||||||
<span className="text-xl font-medium">openpanel.dev</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="">
|
<div className="">
|
||||||
|
|||||||
Reference in New Issue
Block a user