mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
454 lines
19 KiB
TypeScript
454 lines
19 KiB
TypeScript
import React, { FC, PropsWithChildren, SVGProps } from "react";
|
|
import clsx from "clsx";
|
|
import Link from "@docusaurus/Link";
|
|
import { CommonLayout } from "@site/src/refine-theme/common-layout";
|
|
import { CommonHeader } from "@site/src/refine-theme/common-header";
|
|
import { LandingFooter } from "@site/src/refine-theme/landing-footer";
|
|
import { CommonCircleChevronRight } from "./common-circle-chevron-right";
|
|
import { ShareIcon } from "./icons/share";
|
|
import * as Icons from "@site/src/assets/integration-icons";
|
|
import ReactMarkdown from "react-markdown";
|
|
import remarkGfm from "remark-gfm";
|
|
import remarkRehype from "remark-rehype";
|
|
import { CommonRunLocalPrompt } from "./common-run-local-prompt";
|
|
import { GithubIcon } from "./icons/github";
|
|
import { TutorialIcon } from "./icons/tutorial";
|
|
|
|
type Props = {
|
|
data: {
|
|
slug: string;
|
|
title: string;
|
|
description: string;
|
|
images: string[];
|
|
runOnYourLocalPath: string;
|
|
liveDemo: string;
|
|
github: string;
|
|
tutorial: string;
|
|
reactPlatform: string;
|
|
uiFramework: string;
|
|
dataProvider: string;
|
|
authProvider: string;
|
|
};
|
|
};
|
|
|
|
export const TemplatesDetail: FC<Props> = ({ data }) => {
|
|
return (
|
|
<CommonLayout description="Host websites and manage users with unmatched flexibility.">
|
|
<div>
|
|
<CommonHeader />
|
|
<div
|
|
className={clsx(
|
|
"w-full max-w-[592px] landing-sm:max-w-[656px] landing-md:max-w-[896px] landing-lg:max-w-[1200px]",
|
|
"px-4 landing-sm:px-0",
|
|
"mx-auto",
|
|
"flex flex-col",
|
|
"not-prose",
|
|
"mt-8 landing-lg:mt-20",
|
|
"px-6 landing-sm:px-0",
|
|
"pb-8 landing-sm:pb-12 landing-md:pb-16 landing-lg:pb-40",
|
|
)}
|
|
>
|
|
<div>
|
|
<h1
|
|
className={clsx(
|
|
"dark:text-gray-0 text-gray-900",
|
|
"text-4xl landing-sm:text-[56px] landing-sm:leading-[72px]",
|
|
"font-bold",
|
|
"px-0 landing-sm:px-8 landing-md:px-10",
|
|
"mt-6",
|
|
)}
|
|
>
|
|
{data.title}
|
|
</h1>
|
|
</div>
|
|
<div
|
|
className={clsx(
|
|
"flex",
|
|
"mt-8 landing-sm:mt-12 landing-md:mt-16",
|
|
"rounded-lg landing-md:rounded-xl landing-lg:rounded-3xl",
|
|
"-mx-4 landing-sm:-mx-0",
|
|
"landing-sm:px-4",
|
|
"aspect-[1485/736]",
|
|
)}
|
|
>
|
|
<img
|
|
className={clsx(
|
|
"rounded-lg landing-md:rounded-xl landing-lg:rounded-3xl",
|
|
"block",
|
|
"aspect-[1485/736]",
|
|
)}
|
|
src={data.images[0]}
|
|
alt={data.title}
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
className={clsx(
|
|
"flex",
|
|
"items-start",
|
|
"gap-6",
|
|
"mt-8 landing-sm:mt-12 landing-md:mt-16",
|
|
"px-0 landing-sm:pl-10 landing-md:pl-14 landing-lg:pl-10",
|
|
"px-0 landing-sm:pr-10 landing-md:pr-10 landing-lg:pr-10",
|
|
"not-prose",
|
|
)}
|
|
>
|
|
<div
|
|
className={clsx(
|
|
"max-w-full",
|
|
"flex",
|
|
"flex-col",
|
|
"gap-8 landing-sm:gap-10",
|
|
)}
|
|
>
|
|
<div
|
|
className={clsx(
|
|
"flex",
|
|
"flex-wrap",
|
|
"items-center",
|
|
"gap-4 landing-md:gap-6",
|
|
"landing-lg:pl-4",
|
|
)}
|
|
>
|
|
{data.liveDemo && (
|
|
<ProjectLink to={data.liveDemo}>
|
|
<ShareIcon width={16} height={16} />
|
|
<span
|
|
className={clsx(
|
|
"font-semibold",
|
|
"text-xs landing-md:text-base",
|
|
)}
|
|
>
|
|
Live demo
|
|
</span>
|
|
</ProjectLink>
|
|
)}
|
|
{data.tutorial && (
|
|
<ProjectLink to={data.tutorial}>
|
|
<TutorialIcon width={16} height={16} />
|
|
<span
|
|
className={clsx(
|
|
"font-semibold",
|
|
"text-xs landing-md:text-base",
|
|
)}
|
|
>
|
|
Purchase License
|
|
</span>
|
|
</ProjectLink>
|
|
)}
|
|
{data.github && (
|
|
<ProjectLink to={data.github}>
|
|
<GithubIcon width={16} height={16} />
|
|
<span
|
|
className={clsx(
|
|
"font-semibold",
|
|
"text-xs landing-md:text-base",
|
|
)}
|
|
>
|
|
Contribute on GitHub
|
|
</span>
|
|
</ProjectLink>
|
|
)}
|
|
</div>
|
|
<Integrations
|
|
svgId="mobile"
|
|
className={clsx(
|
|
"grid landing-md:hidden",
|
|
"grid-cols-2 sm:grid-cols-4",
|
|
"gap-6",
|
|
"flex-shrink-0",
|
|
)}
|
|
reactPlatform={data.reactPlatform}
|
|
uiFramework={data.uiFramework}
|
|
dataProvider={data.dataProvider}
|
|
authProvider={data.authProvider}
|
|
/>
|
|
<div
|
|
className={clsx(
|
|
"flex",
|
|
"flex-col",
|
|
"landing-lg:pl-4 landing-lg:pr-6",
|
|
)}
|
|
>
|
|
{data.runOnYourLocalPath && (
|
|
<div className={clsx("mb-2 landing-sm:mb-4")}>
|
|
<CommonRunLocalPrompt
|
|
path={data.runOnYourLocalPath}
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className={clsx("not-prose")}>
|
|
<h2
|
|
className={clsx(
|
|
"text-xl landing-sm:text-2xl",
|
|
"dark:text-gray-0 text-gray-900",
|
|
"font-semibold",
|
|
)}
|
|
>
|
|
Description
|
|
</h2>
|
|
</div>
|
|
<ReactMarkdown
|
|
className={clsx(
|
|
"mt-4 landing-sm:gap-6",
|
|
"whitespace-pre-wrap",
|
|
"not-prose",
|
|
"dark:!text-gray-200 !text-gray-900",
|
|
"template-detail-markdown",
|
|
)}
|
|
remarkPlugins={[remarkGfm, remarkRehype]}
|
|
>
|
|
{data.description}
|
|
</ReactMarkdown>
|
|
</div>
|
|
</div>
|
|
<Integrations
|
|
svgId="desktop"
|
|
className={clsx(
|
|
"hidden landing-md:grid",
|
|
"grid-cols-1 landing-lg:grid-cols-2",
|
|
"landing-md:gap-10",
|
|
"pl-0 landing-lg:pl-10",
|
|
"landing-md:ml-6 landing-lg:ml-0",
|
|
"flex-shrink-0",
|
|
"justify-self-end",
|
|
)}
|
|
reactPlatform={data.reactPlatform}
|
|
uiFramework={data.uiFramework}
|
|
dataProvider={data.dataProvider}
|
|
authProvider={data.authProvider}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<LandingFooter />
|
|
</CommonLayout>
|
|
);
|
|
};
|
|
|
|
const ProjectLink: FC<PropsWithChildren<{ to: string }>> = ({
|
|
to,
|
|
children,
|
|
}) => {
|
|
return (
|
|
<Link
|
|
to={to}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className={clsx(
|
|
"select-none",
|
|
"group/project-link-button",
|
|
"relative",
|
|
"w-max",
|
|
"flex",
|
|
"items-center",
|
|
"gap-1 landing-md:gap-2",
|
|
"pl-2 py-2 pr-3",
|
|
"landing-md:pl-3 landing-md:py-3 landing-md:pr-6",
|
|
"rounded-full",
|
|
"dark:bg-refine-cyan-alt/10 bg-refine-blue/10",
|
|
"dark:text-refine-cyan-alt text-refine-blue",
|
|
"hover:no-underline",
|
|
"overflow-hidden",
|
|
)}
|
|
>
|
|
{children}
|
|
<div
|
|
className={clsx(
|
|
"select-none",
|
|
"rounded-3xl",
|
|
"absolute",
|
|
"left-0",
|
|
"top-0",
|
|
"w-full",
|
|
"h-full",
|
|
"scale-[2]",
|
|
"origin-center",
|
|
"transition-[opacity,transform]",
|
|
"duration-300",
|
|
"ease-in-out",
|
|
"opacity-0",
|
|
"group-hover/project-link-button:opacity-100",
|
|
"group-hover/project-link-button:scale-100",
|
|
"pointer-events-none",
|
|
"bg-landing-copy-command-hover-bg-light dark:bg-landing-copy-command-hover-bg-dark",
|
|
)}
|
|
/>
|
|
</Link>
|
|
);
|
|
};
|
|
|
|
const Integrations = (props: {
|
|
className?: string;
|
|
svgId?: string;
|
|
reactPlatform: string;
|
|
uiFramework: string;
|
|
dataProvider: string;
|
|
authProvider: string;
|
|
}) => {
|
|
const {
|
|
reactPlatform,
|
|
uiFramework,
|
|
dataProvider,
|
|
authProvider,
|
|
className,
|
|
} = props;
|
|
|
|
return (
|
|
<div className={clsx(className)}>
|
|
<IntegrationBadge
|
|
svgId={props.svgId + "-react"}
|
|
label="Features"
|
|
integration={reactPlatform}
|
|
/>
|
|
<IntegrationBadge
|
|
svgId={props.svgId + "-ui"}
|
|
label="Limits"
|
|
integration={uiFramework}
|
|
/>
|
|
<IntegrationBadge
|
|
svgId={props.svgId + "-data"}
|
|
label="Integrations"
|
|
integration={dataProvider}
|
|
/>
|
|
<IntegrationBadge
|
|
svgId={props.svgId + "-auth"}
|
|
label="Support"
|
|
integration={authProvider}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const IntegrationBadge = (props: {
|
|
svgId: string;
|
|
label: string;
|
|
integration: string;
|
|
}) => {
|
|
const Icon = integrationToIconMap?.[props.integration] || (() => null);
|
|
|
|
return (
|
|
<div className={clsx("flex", "flex-col", "gap-3", "not-prose")}>
|
|
<h6 className={clsx("text-xs", "dark:text-gray-400 text-gray-600")}>
|
|
{props.label}
|
|
</h6>
|
|
<div
|
|
className={clsx(
|
|
"flex",
|
|
"items-center",
|
|
"h-8",
|
|
"pl-2 pr-4",
|
|
"gap-2",
|
|
"rounded-full",
|
|
"border dark:border-gray-700 border-gray-200",
|
|
"w-min",
|
|
)}
|
|
>
|
|
<Icon id={props.svgId} width={16} height={16} />
|
|
<span
|
|
className={clsx(
|
|
"text-sm",
|
|
"whitespace-nowrap",
|
|
"dark:text-gray-400 text-gray-900",
|
|
)}
|
|
>
|
|
{props.integration}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const integrationToIconMap = {
|
|
Ably: (props: SVGProps<SVGSVGElement>) => <Icons.Ably {...props} />,
|
|
Airtable: (props: SVGProps<SVGSVGElement>) => <Icons.Airtable {...props} />,
|
|
"Ant Design": (props: SVGProps<SVGSVGElement>) => <Icons.Antd {...props} />,
|
|
Appwrite: (props: SVGProps<SVGSVGElement>) => <Icons.Appwrite {...props} />,
|
|
"Chakra UI": (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.Chakra {...props} />
|
|
),
|
|
Directus: (props: SVGProps<SVGSVGElement>) => <Icons.Directus {...props} />,
|
|
Dp: (props: SVGProps<SVGSVGElement>) => <Icons.Dp {...props} />,
|
|
Elide: (props: SVGProps<SVGSVGElement>) => <Icons.Elide {...props} />,
|
|
ElideGraphql: (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.ElideGraphql {...props} />
|
|
),
|
|
EntRefine: (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.EntRefine {...props} />
|
|
),
|
|
Firebase: (props: SVGProps<SVGSVGElement>) => <Icons.Firebase {...props} />,
|
|
GraphQL: (props: SVGProps<SVGSVGElement>) => <Icons.Graphql {...props} />,
|
|
Hasura: (props: SVGProps<SVGSVGElement>) => <Icons.Hasura {...props} />,
|
|
Headless: (props: SVGProps<SVGSVGElement>) => <Icons.Headless {...props} />,
|
|
"React Hook Form": (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.HookForm {...props} />
|
|
),
|
|
Hygraph: (props: SVGProps<SVGSVGElement>) => <Icons.Hygraph {...props} />,
|
|
JSONApi: (props: SVGProps<SVGSVGElement>) => <Icons.JSONApi {...props} />,
|
|
Kbar: (props: SVGProps<SVGSVGElement>) => <Icons.Kbar {...props} />,
|
|
Kinde: (props: SVGProps<SVGSVGElement>) => <Icons.Kinde {...props} />,
|
|
Mantine: (props: SVGProps<SVGSVGElement>) => <Icons.Mantine {...props} />,
|
|
Medusa: (props: SVGProps<SVGSVGElement>) => <Icons.Medusa {...props} />,
|
|
"Material UI": (props: SVGProps<SVGSVGElement>) => <Icons.Mui {...props} />,
|
|
Nest: (props: SVGProps<SVGSVGElement>) => <Icons.Nest {...props} />,
|
|
"NestJsX CRUD": (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.Nest {...props} />
|
|
),
|
|
NestQuery: (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.NestQuery {...props} />
|
|
),
|
|
"Next.js": (props: SVGProps<SVGSVGElement>) => <Icons.Nextjs {...props} />,
|
|
React: (props: SVGProps<SVGSVGElement>) => <Icons.React {...props} />,
|
|
Remix: (props: SVGProps<SVGSVGElement>) => <Icons.Remix {...props} />,
|
|
"Rest API": (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.RestWithoutText {...props} />
|
|
),
|
|
Sanity: (props: SVGProps<SVGSVGElement>) => <Icons.Sanity {...props} />,
|
|
"ShadCN UI": (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.ShadCnUI {...props} />
|
|
),
|
|
SQLite: (props: SVGProps<SVGSVGElement>) => <Icons.SQLite {...props} />,
|
|
Strapi: (props: SVGProps<SVGSVGElement>) => <Icons.Strapi {...props} />,
|
|
Supabase: (props: SVGProps<SVGSVGElement>) => <Icons.Supabase {...props} />,
|
|
TailwindCSS: (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.TailwindCss {...props} />
|
|
),
|
|
UseGenerated: (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.UseGenerated {...props} />
|
|
),
|
|
SlackIcon: (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.SlackIcon {...props} />
|
|
),
|
|
Atlassian: (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.Atlassian {...props} />
|
|
),
|
|
AuthJs: (props: SVGProps<SVGSVGElement>) => <Icons.AuthJs {...props} />,
|
|
Auth0: (props: SVGProps<SVGSVGElement>) => <Icons.Auth0 {...props} />,
|
|
AwsCognito: (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.AwsCognito {...props} />
|
|
),
|
|
AzureActiveDirectory: (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.AzureActiveDirectory {...props} />
|
|
),
|
|
Clerk: (props: SVGProps<SVGSVGElement>) => <Icons.Clerk {...props} />,
|
|
Expo: (props: SVGProps<SVGSVGElement>) => <Icons.Expo {...props} />,
|
|
Google: (props: SVGProps<SVGSVGElement>) => <Icons.Google {...props} />,
|
|
Okta: (props: SVGProps<SVGSVGElement>) => <Icons.Okta {...props} />,
|
|
MongoDB: (props: SVGProps<SVGSVGElement>) => <Icons.MongoDB {...props} />,
|
|
MsSqlServer: (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.MsSqlServer {...props} />
|
|
),
|
|
MySql: (props: SVGProps<SVGSVGElement>) => <Icons.MySql {...props} />,
|
|
PostgreSql: (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.PostgreSql {...props} />
|
|
),
|
|
Oracle: (props: SVGProps<SVGSVGElement>) => <Icons.Oracle {...props} />,
|
|
Custom: (props: SVGProps<SVGSVGElement>) => <Icons.CustomAuth {...props} />,
|
|
Vite: (props: SVGProps<SVGSVGElement>) => <Icons.Vite {...props} />,
|
|
"Nestjs-query": (props: SVGProps<SVGSVGElement>) => (
|
|
<Icons.Graphql {...props} />
|
|
),
|
|
};
|
|
|
|
export type Integration = keyof typeof integrationToIconMap;
|