add helmet instead of updating document.title (#301)

This commit is contained in:
Guy Ben-Aharon
2024-11-03 11:53:53 +02:00
committed by GitHub
parent bbced225b1
commit 3714ca58ea
10 changed files with 388 additions and 338 deletions

View File

@@ -4,41 +4,8 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Free and Open-source database diagrams editor, visualize and design your database with a single query. Tool to help you draw your DB relationship diagrams and export DDL scripts."
/>
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta
property="og:title"
content="ChartDB - Database schema diagrams visualizer"
/>
<meta
property="og:description"
content="Free and Open-source database diagrams editor, visualize and design your database with a single query. Tool to help you draw your DB relationship diagrams and export DDL scripts."
/>
<meta
property="og:image"
content="https://app.chartdb.io/ChartDB.png"
/>
<meta property="og:url" content="https://app.chartdb.io" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="ChartDB - Database schema diagrams visualizer"
/>
<meta
name="twitter:description"
content="Free and Open-source database diagrams editor, visualize and design your database with a single query. Tool to help you draw your DB relationship diagrams and export DDL scripts."
/>
<meta
name="twitter:image"
content="https://github.com/chartdb/chartdb/raw/main/public/ChartDB.png"
/>
<meta name="robots" content="max-image-preview:large" />
<title>ChartDB - Database schema diagrams visualizer</title>
<title>ChartDB - Create & Visualize Database Schema Diagrams</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link

21
package-lock.json generated
View File

@@ -50,6 +50,7 @@
"node-sql-parser": "^5.3.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-helmet-async": "^2.0.5",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^15.0.1",
"react-resizable-panels": "^2.0.22",
@@ -8463,6 +8464,20 @@
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
"license": "MIT"
},
"node_modules/react-helmet-async": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-2.0.5.tgz",
"integrity": "sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg==",
"license": "Apache-2.0",
"dependencies": {
"invariant": "^2.2.4",
"react-fast-compare": "^3.2.2",
"shallowequal": "^1.1.0"
},
"peerDependencies": {
"react": "^16.6.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-hotkeys-hook": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.1.tgz",
@@ -9052,6 +9067,12 @@
"integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==",
"license": "MIT"
},
"node_modules/shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",

View File

@@ -54,6 +54,7 @@
"node-sql-parser": "^5.3.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-helmet-async": "^2.0.5",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^15.0.1",
"react-resizable-panels": "^2.0.22",

View File

@@ -2,11 +2,48 @@ import React from 'react';
import { RouterProvider } from 'react-router-dom';
import { router } from './router';
import { TooltipProvider } from './components/tooltip/tooltip';
import { Helmet, HelmetProvider } from 'react-helmet-async';
export const App = () => {
return (
<TooltipProvider>
<RouterProvider router={router} />
</TooltipProvider>
<HelmetProvider>
<Helmet>
<meta
name="description"
content="Free and Open-source database diagrams editor, visualize and design your database with a single query. Tool to help you draw your DB relationship diagrams and export DDL scripts."
/>
<meta property="og:type" content="website" />
<meta
property="og:title"
content="ChartDB - Database schema diagrams visualizer"
/>
<meta
property="og:description"
content="Free and Open-source database diagrams editor, visualize and design your database with a single query. Tool to help you draw your DB relationship diagrams and export DDL scripts."
/>
<meta
property="og:image"
content="https://app.chartdb.io/ChartDB.png"
/>
<meta property="og:url" content="https://app.chartdb.io" />
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="ChartDB - Database schema diagrams visualizer"
/>
<meta
name="twitter:description"
content="Free and Open-source database diagrams editor, visualize and design your database with a single query. Tool to help you draw your DB relationship diagrams and export DDL scripts."
/>
<meta
name="twitter:image"
content="https://github.com/chartdb/chartdb/raw/main/public/ChartDB.png"
/>
<title>ChartDB - Database schema diagrams visualizer</title>
</Helmet>
<TooltipProvider>
<RouterProvider router={router} />
</TooltipProvider>
</HelmetProvider>
);
};

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import type { DBTable } from '@/lib/domain/db-table';
import { deepCopy, generateId } from '@/lib/utils';
import { randomColor } from '@/lib/colors';
@@ -28,11 +28,10 @@ import { storageInitialValue } from '../storage-context/storage-context';
export interface ChartDBProviderProps {
diagram?: Diagram;
readonly?: boolean;
skipTitleUpdate?: boolean;
}
export const ChartDBProvider: React.FC<
React.PropsWithChildren<ChartDBProviderProps>
> = ({ children, diagram, readonly, skipTitleUpdate }) => {
> = ({ children, diagram, readonly }) => {
let db = useStorage();
const events = useEventEmitter<ChartDBEvent>();
const navigate = useNavigate();
@@ -64,19 +63,6 @@ export const ChartDBProvider: React.FC<
db = storageInitialValue;
}
useEffect(() => {
if (skipTitleUpdate) {
return;
}
if (diagramName) {
document.title = `ChartDB - ${diagramName} Diagram | Visualize Database Schemas`;
} else {
document.title =
'ChartDB - Create & Visualize Database Schema Diagrams';
}
}, [diagramName, skipTitleUpdate]);
const schemas = useMemo(
() =>
databasesWithSchemas.includes(databaseType)

View File

@@ -34,6 +34,7 @@ import { ExportImageProvider } from '@/context/export-image-context/export-image
import { DialogProvider } from '@/context/dialog-context/dialog-provider';
import { KeyboardShortcutsProvider } from '@/context/keyboard-shortcuts-context/keyboard-shortcuts-provider';
import { Spinner } from '@/components/spinner/spinner';
import { Helmet } from 'react-helmet-async';
const OPEN_STAR_US_AFTER_SECONDS = 30;
const SHOW_STAR_US_AGAIN_AFTER_DAYS = 1;
@@ -47,8 +48,13 @@ export const EditorMobileLayoutLazy = React.lazy(
);
const EditorPageComponent: React.FC = () => {
const { loadDiagram, currentDiagram, schemas, filteredSchemas } =
useChartDB();
const {
loadDiagram,
diagramName,
currentDiagram,
schemas,
filteredSchemas,
} = useChartDB();
const { openSelectSchema, showSidePanel } = useLayout();
const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
const { showLoader, hideLoader } = useFullScreenLoader();
@@ -210,6 +216,13 @@ const EditorPageComponent: React.FC = () => {
return (
<>
<Helmet>
<title>
{diagramName
? `ChartDB - ${diagramName} Diagram | Visualize Database Schemas`
: 'ChartDB - Create & Visualize Database Schema Diagrams'}
</title>
</Helmet>
<section
className={`bg-background ${isDesktop ? 'h-screen w-screen' : 'h-dvh w-dvw'} flex select-none flex-col overflow-x-hidden`}
>

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React from 'react';
import ChartDBLogo from '@/assets/logo-light.png';
import ChartDBDarkLogo from '@/assets/logo-dark.png';
import { examples } from './examples-data/examples-data';
@@ -7,51 +7,56 @@ import { useTheme } from '@/hooks/use-theme';
import { LocalConfigProvider } from '@/context/local-config-context/local-config-provider';
import { StorageProvider } from '@/context/storage-context/storage-provider';
import { ThemeProvider } from '@/context/theme-context/theme-provider';
import { Helmet } from 'react-helmet-async';
const ExamplesPageComponent: React.FC = () => {
const { effectiveTheme } = useTheme();
useEffect(() => {
document.title = 'ChartDB - Example Database Diagrams & Schemas';
}, []);
return (
<section className="flex w-screen flex-col bg-background">
<nav className="flex h-12 flex-row items-center justify-between border-b px-4">
<div className="flex flex-1 justify-start gap-x-3">
<div className="flex items-center font-primary">
<a
href="https://chartdb.io"
className="cursor-pointer"
rel="noreferrer"
>
<img
src={
effectiveTheme === 'light'
? ChartDBLogo
: ChartDBDarkLogo
}
alt="chartDB"
className="h-4 max-w-fit"
/>
</a>
<>
<Helmet>
<title>ChartDB - Example Database Diagrams & Schemas</title>
</Helmet>
<section className="flex w-screen flex-col bg-background">
<nav className="flex h-12 flex-row items-center justify-between border-b px-4">
<div className="flex flex-1 justify-start gap-x-3">
<div className="flex items-center font-primary">
<a
href="https://chartdb.io"
className="cursor-pointer"
rel="noreferrer"
>
<img
src={
effectiveTheme === 'light'
? ChartDBLogo
: ChartDBDarkLogo
}
alt="chartDB"
className="h-4 max-w-fit"
/>
</a>
</div>
</div>
<div className="group flex flex-1 flex-row items-center justify-center"></div>
<div className="hidden flex-1 justify-end sm:flex"></div>
</nav>
<div className="flex flex-col px-3 pt-3 text-center md:px-28 md:text-left">
<h1 className="font-primary text-2xl font-bold">
Examples
</h1>
<h2 className="mt-1 font-primary text-base text-muted-foreground">
A collection of examples to help you get started with
ChartDB.
</h2>
<div className="mt-6 grid grid-flow-row grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{examples.map((example) => (
<ExampleCard key={example.id} example={example} />
))}
</div>
</div>
<div className="group flex flex-1 flex-row items-center justify-center"></div>
<div className="hidden flex-1 justify-end sm:flex"></div>
</nav>
<div className="flex flex-col px-3 pt-3 text-center md:px-28 md:text-left">
<h1 className="font-primary text-2xl font-bold">Examples</h1>
<h2 className="mt-1 font-primary text-base text-muted-foreground">
A collection of examples to help you get started with
ChartDB.
</h2>
<div className="mt-6 grid grid-flow-row grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{examples.map((example) => (
<ExampleCard key={example.id} example={example} />
))}
</div>
</div>
</section>
</section>
</>
);
};

View File

@@ -7,7 +7,7 @@ import { StorageProvider } from '@/context/storage-context/storage-provider';
import { ThemeProvider } from '@/context/theme-context/theme-provider';
import { Button } from '@/components/button/button';
import { CloudDownload } from 'lucide-react';
import { useNavigate, useParams } from 'react-router-dom';
import { useLoaderData, useNavigate, useParams } from 'react-router-dom';
import type { Template } from '../../templates-data/templates-data';
import {
Breadcrumb,
@@ -34,37 +34,27 @@ import { ChartDBProvider } from '@/context/chartdb-context/chartdb-provider';
import { convertTemplateToNewDiagram } from '@/templates-data/template-utils';
import { useStorage } from '@/hooks/use-storage';
import type { Diagram } from '@/lib/domain/diagram';
import { Helmet } from 'react-helmet-async';
export interface TemplatePageLoaderData {
template: Template | undefined;
}
const TemplatePageComponent: React.FC = () => {
const { addDiagram } = useStorage();
const { templateSlug } = useParams<{ templateSlug: string }>();
const [template, setTemplate] = React.useState<Template>();
const navigate = useNavigate();
const data = useLoaderData() as TemplatePageLoaderData;
const template = data.template;
useEffect(() => {
const loadTemplate = async () => {
const { templates } = await import(
'@/templates-data/templates-data'
);
const template = templates.find((t) => t.slug === templateSlug);
if (!template) {
navigate('/templates');
return;
}
setTemplate(template);
};
loadTemplate();
}, [templateSlug, navigate]);
const { effectiveTheme } = useTheme();
useEffect(() => {
if (template) {
document.title = `ChartDB - ${template.name} - ${template.shortDescription}`;
} else {
document.title = 'ChartDB - Database Schema Template';
if (!template) {
navigate('/templates');
}
}, [template]);
}, [template, navigate]);
const { effectiveTheme } = useTheme();
const cloneTemplate = useCallback(async () => {
if (!template) {
@@ -85,156 +75,168 @@ const TemplatePageComponent: React.FC = () => {
}, [addDiagram, navigate, template]);
return (
<section className="flex h-screen w-screen flex-col bg-background">
<nav className="flex h-12 shrink-0 flex-row items-center justify-between border-b px-4">
<div className="flex flex-1 justify-start gap-x-3">
<div className="flex items-center font-primary">
<a
href="https://chartdb.io"
className="cursor-pointer"
rel="noreferrer"
>
<img
src={
effectiveTheme === 'light'
? ChartDBLogo
: ChartDBDarkLogo
}
alt="chartDB"
className="h-4 max-w-fit"
/>
</a>
</div>
</div>
<div className="group flex flex-1 flex-row items-center justify-center"></div>
<div className="hidden flex-1 justify-end sm:flex"></div>
</nav>
{!template ? (
<Spinner size={'large'} className="mt-20 text-pink-600" />
) : (
<div className="flex flex-1 flex-col p-3 pb-5 text-center md:px-28 md:text-left">
<Breadcrumb className="mb-2">
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href={`/templates`}>
Templates
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink
href={`/templates/${templateSlug}`}
>
{templateSlug}
</BreadcrumbLink>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<div className="flex flex-col items-center gap-4 md:flex-row md:items-start md:justify-between md:gap-0">
<div className="flex flex-col pr-0 md:pr-20">
<h1 className="font-primary text-2xl font-bold">
{template?.name}
</h1>
<h2 className="mt-3">
<span className="font-semibold">
{template?.shortDescription}
{': '}
</span>
{template?.description}
</h2>
</div>
<div className="flex justify-end">
<Button onClick={cloneTemplate}>
<CloudDownload className="mr-2" size="16" />
Clone Template
</Button>
</div>
</div>
<Separator className="my-5" />
<div className="flex w-full flex-1 flex-col gap-4 md:flex-row">
<div className="relative top-0 flex h-fit w-full shrink-0 flex-col gap-4 md:sticky md:top-1 md:w-60">
<div>
<h4 className="mb-1 text-base font-semibold md:text-left">
Metadata
</h4>
<>
<Helmet>
<title>
{template
? `ChartDB - ${template.name} - ${template.shortDescription}`
: 'ChartDB - Database Schema Template'}
</title>
</Helmet>
<div className="text-sm text-muted-foreground">
<div className="inline-flex">
<span className="mr-2">Database:</span>
<Tooltip>
<TooltipTrigger asChild>
<img
src={
databaseSecondaryLogoMap[
<section className="flex h-screen w-screen flex-col bg-background">
<nav className="flex h-12 shrink-0 flex-row items-center justify-between border-b px-4">
<div className="flex flex-1 justify-start gap-x-3">
<div className="flex items-center font-primary">
<a
href="https://chartdb.io"
className="cursor-pointer"
rel="noreferrer"
>
<img
src={
effectiveTheme === 'light'
? ChartDBLogo
: ChartDBDarkLogo
}
alt="chartDB"
className="h-4 max-w-fit"
/>
</a>
</div>
</div>
<div className="group flex flex-1 flex-row items-center justify-center"></div>
<div className="hidden flex-1 justify-end sm:flex"></div>
</nav>
{!template ? (
<Spinner size={'large'} className="mt-20 text-pink-600" />
) : (
<div className="flex flex-1 flex-col p-3 pb-5 text-center md:px-28 md:text-left">
<Breadcrumb className="mb-2">
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href={`/templates`}>
Templates
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink
href={`/templates/${templateSlug}`}
>
{templateSlug}
</BreadcrumbLink>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<div className="flex flex-col items-center gap-4 md:flex-row md:items-start md:justify-between md:gap-0">
<div className="flex flex-col pr-0 md:pr-20">
<h1 className="font-primary text-2xl font-bold">
{template?.name}
</h1>
<h2 className="mt-3">
<span className="font-semibold">
{template?.shortDescription}
{': '}
</span>
{template?.description}
</h2>
</div>
<div className="flex justify-end">
<Button onClick={cloneTemplate}>
<CloudDownload className="mr-2" size="16" />
Clone Template
</Button>
</div>
</div>
<Separator className="my-5" />
<div className="flex w-full flex-1 flex-col gap-4 md:flex-row">
<div className="relative top-0 flex h-fit w-full shrink-0 flex-col gap-4 md:sticky md:top-1 md:w-60">
<div>
<h4 className="mb-1 text-base font-semibold md:text-left">
Metadata
</h4>
<div className="text-sm text-muted-foreground">
<div className="inline-flex">
<span className="mr-2">
Database:
</span>
<Tooltip>
<TooltipTrigger asChild>
<img
src={
databaseSecondaryLogoMap[
template.diagram
.databaseType
]
}
className="h-5 max-w-fit"
alt="database"
/>
</TooltipTrigger>
<TooltipContent>
{
databaseTypeToLabelMap[
template.diagram
.databaseType
]
}
className="h-5 max-w-fit"
alt="database"
/>
</TooltipTrigger>
<TooltipContent>
{
databaseTypeToLabelMap[
template.diagram
.databaseType
]
}
</TooltipContent>
</Tooltip>
</TooltipContent>
</Tooltip>
</div>
</div>
<div className="text-sm text-muted-foreground">
<span>Tables:</span>
<span className="ml-2 font-semibold">
{template?.diagram?.tables
?.length ?? 0}
</span>
</div>
<div className="text-sm text-muted-foreground">
<span>Relationships:</span>
<span className="ml-2 font-semibold">
{template?.diagram?.relationships
?.length ?? 0}
</span>
</div>
</div>
<div className="text-sm text-muted-foreground">
<span>Tables:</span>
<span className="ml-2 font-semibold">
{template?.diagram?.tables?.length ?? 0}
</span>
</div>
<div className="text-sm text-muted-foreground">
<span>Relationships:</span>
<span className="ml-2 font-semibold">
{template?.diagram?.relationships
?.length ?? 0}
</span>
<div>
<h4 className="mb-1 text-base font-semibold md:text-left">
Tags
</h4>
<div className="flex flex-wrap justify-center gap-1 md:justify-start">
{template.tags.map((tag) => (
<Badge
variant="outline"
key={`${template.id}_${tag}`}
>
{tag}
</Badge>
))}
</div>
</div>
</div>
<div>
<h4 className="mb-1 text-base font-semibold md:text-left">
Tags
</h4>
<div className="flex flex-wrap justify-center gap-1 md:justify-start">
{template.tags.map((tag) => (
<Badge
variant="outline"
key={`${template.id}_${tag}`}
>
{tag}
</Badge>
))}
</div>
</div>
</div>
<div className="flex min-h-96 overflow-hidden rounded border md:flex-1 md:rounded-lg">
<div className="size-full">
<ChartDBProvider
diagram={template.diagram}
readonly
skipTitleUpdate
>
<Canvas
<div className="flex min-h-96 overflow-hidden rounded border md:flex-1 md:rounded-lg">
<div className="size-full">
<ChartDBProvider
diagram={template.diagram}
readonly
initialTables={
template.diagram.tables ?? []
}
/>
</ChartDBProvider>
>
<Canvas
readonly
initialTables={
template.diagram.tables ?? []
}
/>
</ChartDBProvider>
</div>
</div>
</div>
</div>
</div>
)}
</section>
)}
</section>
</>
);
};

View File

@@ -11,6 +11,7 @@ import { useMatches, useParams } from 'react-router-dom';
import type { Template } from '@/templates-data/templates-data';
import { Spinner } from '@/components/spinner/spinner';
import { removeDups } from '@/lib/utils';
import { Helmet } from 'react-helmet-async';
const TemplatesPageComponent: React.FC = () => {
const { effectiveTheme } = useTheme();
@@ -25,10 +26,6 @@ const TemplatesPageComponent: React.FC = () => {
const isAllTemplates = matches.some((match) => match.id === 'templates');
const isTags = matches.some((match) => match.id === 'templates_tags');
useEffect(() => {
document.title = 'ChartDB - Database Schema Templates';
}, []);
useEffect(() => {
const loadTemplates = async () => {
const { templates: loadedTemplates } = await import(
@@ -55,88 +52,97 @@ const TemplatesPageComponent: React.FC = () => {
}, [isFeatured, isTags, tag]);
return (
<section className="flex w-screen flex-col bg-background">
<nav className="flex h-12 shrink-0 flex-row items-center justify-between border-b px-4">
<div className="flex flex-1 justify-start gap-x-3">
<div className="flex items-center font-primary">
<a
href="https://chartdb.io"
className="cursor-pointer"
rel="noreferrer"
>
<img
src={
effectiveTheme === 'light'
? ChartDBLogo
: ChartDBDarkLogo
}
alt="chartDB"
className="h-4 max-w-fit"
/>
</a>
</div>
</div>
<div className="group flex flex-1 flex-row items-center justify-center"></div>
<div className="hidden flex-1 justify-end sm:flex"></div>
</nav>
<div className="flex flex-col p-3 text-center md:px-28 md:text-left">
<h1 className="font-primary text-2xl font-bold">
Database Schema Templates
</h1>
<h2 className="mt-1 font-primary text-base text-muted-foreground">
Explore a collection of real-world database schemas drawn
from real-world live applications and open-source projects.
Use these as a foundation or source of inspiration when
designing your apps architecture.
</h2>
{!templates ? (
<Spinner size={'large'} className="mt-20 text-pink-600" />
) : (
<div className="mt-6 flex w-full flex-col-reverse gap-4 md:flex-row">
<div className="relative top-0 flex h-fit w-full shrink-0 flex-col md:sticky md:top-1 md:w-44">
<ListMenu
items={[
{
title: 'Featured',
href: '/templates/featured',
icon: Star,
selected: isFeatured,
},
{
title: 'All Templates',
href: '/templates',
icon: Component,
selected: isAllTemplates,
},
]}
/>
<>
<Helmet>
<title>ChartDB - Database Schema Templates</title>
</Helmet>
<h4 className="mt-4 text-left text-sm font-semibold">
Tags
</h4>
{tags ? (
<ListMenu
className="mt-1 w-44 shrink-0"
items={tags.map((currentTag) => ({
title: currentTag,
href: `/templates/tags/${currentTag}`,
selected: tag === currentTag,
}))}
<section className="flex w-screen flex-col bg-background">
<nav className="flex h-12 shrink-0 flex-row items-center justify-between border-b px-4">
<div className="flex flex-1 justify-start gap-x-3">
<div className="flex items-center font-primary">
<a
href="https://chartdb.io"
className="cursor-pointer"
rel="noreferrer"
>
<img
src={
effectiveTheme === 'light'
? ChartDBLogo
: ChartDBDarkLogo
}
alt="chartDB"
className="h-4 max-w-fit"
/>
) : null}
</div>
<div className="grid flex-1 grid-flow-row grid-cols-1 gap-6 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-4">
{templates.map((template) => (
<TemplateCard
key={`${template.id}`}
template={template}
/>
))}
</a>
</div>
</div>
)}
</div>
</section>
<div className="group flex flex-1 flex-row items-center justify-center"></div>
<div className="hidden flex-1 justify-end sm:flex"></div>
</nav>
<div className="flex flex-col p-3 text-center md:px-28 md:text-left">
<h1 className="font-primary text-2xl font-bold">
Database Schema Templates
</h1>
<h2 className="mt-1 font-primary text-base text-muted-foreground">
Explore a collection of real-world database schemas
drawn from real-world live applications and open-source
projects. Use these as a foundation or source of
inspiration when designing your apps architecture.
</h2>
{!templates ? (
<Spinner
size={'large'}
className="mt-20 text-pink-600"
/>
) : (
<div className="mt-6 flex w-full flex-col-reverse gap-4 md:flex-row">
<div className="relative top-0 flex h-fit w-full shrink-0 flex-col md:sticky md:top-1 md:w-44">
<ListMenu
items={[
{
title: 'Featured',
href: '/templates/featured',
icon: Star,
selected: isFeatured,
},
{
title: 'All Templates',
href: '/templates',
icon: Component,
selected: isAllTemplates,
},
]}
/>
<h4 className="mt-4 text-left text-sm font-semibold">
Tags
</h4>
{tags ? (
<ListMenu
className="mt-1 w-44 shrink-0"
items={tags.map((currentTag) => ({
title: currentTag,
href: `/templates/tags/${currentTag}`,
selected: tag === currentTag,
}))}
/>
) : null}
</div>
<div className="grid flex-1 grid-flow-row grid-cols-1 gap-6 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-4">
{templates.map((template) => (
<TemplateCard
key={`${template.id}`}
template={template}
/>
))}
</div>
</div>
)}
</div>
</section>
</>
);
};

View File

@@ -1,6 +1,7 @@
import React from 'react';
import type { RouteObject } from 'react-router-dom';
import { createBrowserRouter } from 'react-router-dom';
import type { TemplatePageLoaderData } from './pages/template-page/template-page';
const routes: RouteObject[] = [
...['', 'diagrams/:diagramId'].map((path) => ({
@@ -63,6 +64,7 @@ const routes: RouteObject[] = [
},
},
{
id: 'templates_templateSlug',
path: 'templates/:templateSlug',
async lazy() {
const { TemplatePage } = await import(
@@ -72,6 +74,16 @@ const routes: RouteObject[] = [
element: <TemplatePage />,
};
},
loader: async ({ params }): Promise<TemplatePageLoaderData> => {
const { templates } = await import(
'./templates-data/templates-data'
);
return {
template: templates.find(
(template) => template.slug === params.templateSlug
),
};
},
},
{
path: '*',