feat: add website pricing page
This commit is contained in:
parent
0fc112fc32
commit
6674c19e87
@ -20,7 +20,8 @@
|
|||||||
"build:openapi": "ts-node --project ./tsconfig.base.json ./scripts/build-openapi-schema.ts && cd packages/client-sdk && pnpm generate:client",
|
"build:openapi": "ts-node --project ./tsconfig.base.json ./scripts/build-openapi-schema.ts && cd packages/client-sdk && pnpm generate:client",
|
||||||
"check:type": "pnpm -r check:type",
|
"check:type": "pnpm -r check:type",
|
||||||
"release": "release-it",
|
"release": "release-it",
|
||||||
"release:patch": "release-it -i patch"
|
"release:patch": "release-it -i patch",
|
||||||
|
"release:packages": "pnpm -r --filter \"./packages/*\" publish --registry=https://registry.npmjs.com/"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@release-it/conventional-changelog": "^8.0.1",
|
"@release-it/conventional-changelog": "^8.0.1",
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"outDir": "./lib",
|
"outDir": "./lib",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*"]
|
"include": ["./src/**/*"]
|
||||||
|
@ -659,6 +659,9 @@ importers:
|
|||||||
react-vertical-timeline-component:
|
react-vertical-timeline-component:
|
||||||
specifier: ^3.6.0
|
specifier: ^3.6.0
|
||||||
version: 3.6.0(react@17.0.2)
|
version: 3.6.0(react@17.0.2)
|
||||||
|
tianji-client-sdk:
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../packages/client-sdk
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@docusaurus/module-type-aliases':
|
'@docusaurus/module-type-aliases':
|
||||||
specifier: 2.4.3
|
specifier: 2.4.3
|
||||||
@ -666,6 +669,9 @@ importers:
|
|||||||
'@tsconfig/docusaurus':
|
'@tsconfig/docusaurus':
|
||||||
specifier: ^1.0.5
|
specifier: ^1.0.5
|
||||||
version: 1.0.5
|
version: 1.0.5
|
||||||
|
'@types/react':
|
||||||
|
specifier: ^18.2.22
|
||||||
|
version: 18.2.78
|
||||||
'@types/react-slick':
|
'@types/react-slick':
|
||||||
specifier: ^0.23.11
|
specifier: ^0.23.11
|
||||||
version: 0.23.11
|
version: 0.23.11
|
||||||
@ -20803,7 +20809,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lilconfig: 2.1.0
|
lilconfig: 2.1.0
|
||||||
postcss: 8.4.33
|
postcss: 8.4.33
|
||||||
ts-node: 10.9.1(@types/node@18.17.12)(typescript@5.4.5)
|
ts-node: 10.9.1(@types/node@18.17.12)(typescript@4.7.4)
|
||||||
yaml: 2.3.2
|
yaml: 2.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@ -26311,6 +26317,7 @@ packages:
|
|||||||
typescript: 5.4.5
|
typescript: 5.4.5
|
||||||
v8-compile-cache-lib: 3.0.1
|
v8-compile-cache-lib: 3.0.1
|
||||||
yn: 3.1.1
|
yn: 3.1.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ts-pattern@4.3.0:
|
/ts-pattern@4.3.0:
|
||||||
resolution: {integrity: sha512-pefrkcd4lmIVR0LA49Imjf9DYLK8vtWhqBPA3Ya1ir8xCW0O2yjL9dsCVvI7pCodLC5q7smNpEtDR2yVulQxOg==}
|
resolution: {integrity: sha512-pefrkcd4lmIVR0LA49Imjf9DYLK8vtWhqBPA3Ya1ir8xCW0O2yjL9dsCVvI7pCodLC5q7smNpEtDR2yVulQxOg==}
|
||||||
|
@ -84,6 +84,7 @@ const config = {
|
|||||||
position: 'left',
|
position: 'left',
|
||||||
label: 'Docs',
|
label: 'Docs',
|
||||||
},
|
},
|
||||||
|
{ to: '/pricing', label: 'Pricing', position: 'left' },
|
||||||
{ to: '/changelog', label: 'Changelog', position: 'left' },
|
{ to: '/changelog', label: 'Changelog', position: 'left' },
|
||||||
{ to: '/api', label: 'API', position: 'left' },
|
{ to: '/api', label: 'API', position: 'left' },
|
||||||
{
|
{
|
||||||
|
@ -27,11 +27,13 @@
|
|||||||
"react-icons": "^4.12.0",
|
"react-icons": "^4.12.0",
|
||||||
"react-responsive-carousel": "^3.2.23",
|
"react-responsive-carousel": "^3.2.23",
|
||||||
"react-slick": "^0.29.0",
|
"react-slick": "^0.29.0",
|
||||||
"react-vertical-timeline-component": "^3.6.0"
|
"react-vertical-timeline-component": "^3.6.0",
|
||||||
|
"tianji-client-sdk": "workspace:^"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "2.4.3",
|
"@docusaurus/module-type-aliases": "2.4.3",
|
||||||
"@tsconfig/docusaurus": "^1.0.5",
|
"@tsconfig/docusaurus": "^1.0.5",
|
||||||
|
"@types/react": "^18.2.22",
|
||||||
"@types/react-slick": "^0.23.11",
|
"@types/react-slick": "^0.23.11",
|
||||||
"@types/react-vertical-timeline-component": "^3.3.6",
|
"@types/react-vertical-timeline-component": "^3.3.6",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
|
198
website/src/pages/pricing.tsx
Normal file
198
website/src/pages/pricing.tsx
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
|
import Layout from '@theme/Layout';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { LuCheck } from 'react-icons/lu';
|
||||||
|
import { Button, Input, message } from 'antd';
|
||||||
|
import Link from '@docusaurus/Link';
|
||||||
|
import { submitSurvey, initOpenapiSDK } from 'tianji-client-sdk';
|
||||||
|
|
||||||
|
initOpenapiSDK('https://tianji.moonrailgun.com/');
|
||||||
|
|
||||||
|
export default function Pricing(): JSX.Element {
|
||||||
|
const { siteConfig } = useDocusaurusContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
title={'Pricing'}
|
||||||
|
description="Pricing of Tianji | Insight into everything"
|
||||||
|
>
|
||||||
|
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 sm:py-16 lg:px-8 lg:py-20">
|
||||||
|
<div className="lg:grid lg:grid-cols-2 lg:items-center lg:gap-8">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-3xl font-extrabold text-gray-900 sm:text-4xl dark:text-gray-100">
|
||||||
|
Pricing
|
||||||
|
</h2>
|
||||||
|
<p className="mt-3 text-lg text-gray-500 sm:mt-4">
|
||||||
|
Choose the plan that's right for your business.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-10 lg:col-start-2 lg:mt-0">
|
||||||
|
<PlanBlock
|
||||||
|
title="Self-Hosting"
|
||||||
|
price={
|
||||||
|
<>
|
||||||
|
$0
|
||||||
|
<span className="ml-1 text-2xl font-medium text-gray-500">
|
||||||
|
/ month
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
desc="Perfect for personal and enterprises with higher requirements for information control."
|
||||||
|
features={[
|
||||||
|
'Unlimited users',
|
||||||
|
'Unlimited storage',
|
||||||
|
'Secure data hosting',
|
||||||
|
'Community support',
|
||||||
|
]}
|
||||||
|
button={
|
||||||
|
<Link to="/">
|
||||||
|
<Button className="w-full" size="large">
|
||||||
|
Install with Docker Compose
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-10 lg:grid lg:grid-cols-2 lg:items-center lg:gap-8">
|
||||||
|
<div className="order-2 lg:order-1 lg:col-start-2">
|
||||||
|
<h2 className="text-3xl font-extrabold text-gray-900 sm:text-4xl dark:text-gray-100">
|
||||||
|
Cloud Hosting
|
||||||
|
</h2>
|
||||||
|
<p className="mt-3 text-lg text-gray-500 sm:mt-4">
|
||||||
|
Join the waitlist for our cloud hosting solution.
|
||||||
|
</p>
|
||||||
|
<Waitlist />
|
||||||
|
</div>
|
||||||
|
<div className="mt-10 lg:col-start-1 lg:mt-0">
|
||||||
|
<PlanBlock
|
||||||
|
title="Cloud Hosting"
|
||||||
|
price={
|
||||||
|
<>
|
||||||
|
<span className="mr-1 text-2xl font-medium text-gray-500">
|
||||||
|
From
|
||||||
|
</span>
|
||||||
|
$-
|
||||||
|
<span className="ml-1 text-2xl font-medium text-gray-500">
|
||||||
|
/ month
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
desc="Perfect for small to medium-sized businesses which help your business quick start and grow."
|
||||||
|
features={[
|
||||||
|
'Pay as you need',
|
||||||
|
'One click to online',
|
||||||
|
'Secure cloud hosting',
|
||||||
|
'Email support',
|
||||||
|
]}
|
||||||
|
button={
|
||||||
|
<Button
|
||||||
|
className="w-full dark:text-gray-400"
|
||||||
|
size="large"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
Coming Soon...
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailRegex =
|
||||||
|
/^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/;
|
||||||
|
|
||||||
|
export const Waitlist: React.FC = React.memo(() => {
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
if (!emailRegex.test(email)) {
|
||||||
|
message.warning('Is not a valid email');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await submitSurvey(
|
||||||
|
'clnzoxcy10001vy2ohi4obbi0',
|
||||||
|
'clvs1lt2e00dloc4fkg8abb0q',
|
||||||
|
{
|
||||||
|
email,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
message.success(
|
||||||
|
'Success, we will contact to you if we publish cloud version'
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
message.error(String(err));
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-6 sm:flex sm:w-full sm:max-w-md">
|
||||||
|
<div className="sr-only">Email address</div>
|
||||||
|
<Input
|
||||||
|
className="w-full min-w-0 flex-auto rounded-md bg-white px-3 py-2 text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:text-sm"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className="mt-3 w-full rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 sm:ml-3 sm:mt-0 sm:w-auto"
|
||||||
|
size="large"
|
||||||
|
loading={loading}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Join Waitlist
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Waitlist.displayName = 'Waitlist';
|
||||||
|
|
||||||
|
export const PlanBlock: React.FC<{
|
||||||
|
title: string;
|
||||||
|
price: React.ReactNode;
|
||||||
|
desc: string;
|
||||||
|
features: string[];
|
||||||
|
button: React.ReactNode;
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
return (
|
||||||
|
<div className="overflow-hidden rounded-lg shadow-md ring-1 ring-black ring-opacity-5">
|
||||||
|
<div className="bg-white px-6 py-8 sm:p-10 sm:pb-6 dark:bg-gray-800">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 sm:-mx-6 dark:text-white">
|
||||||
|
{props.title}
|
||||||
|
</h3>
|
||||||
|
<div className="flex items-baseline text-6xl font-extrabold text-gray-900 dark:text-white">
|
||||||
|
{props.price}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="mt-4 text-lg text-gray-500">{props.desc}</p>
|
||||||
|
<ul
|
||||||
|
className="mt-8 space-y-3 text-base text-gray-500 sm:grid sm:grid-cols-2 sm:gap-x-8 sm:gap-y-5 sm:space-y-0"
|
||||||
|
role="list"
|
||||||
|
>
|
||||||
|
{props.features.map((feature, i) => (
|
||||||
|
<li className="flex items-start" key={i}>
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<LuCheck className="h-6 w-6 text-green-500" />
|
||||||
|
</div>
|
||||||
|
<p className="ml-3">{feature}</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 px-6 pb-8 pt-6 sm:p-10 sm:pt-6 dark:bg-gray-700">
|
||||||
|
{props.button}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
PlanBlock.displayName = 'PlanBlock';
|
@ -3,6 +3,9 @@
|
|||||||
"extends": "@tsconfig/docusaurus/tsconfig.json",
|
"extends": "@tsconfig/docusaurus/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"strict": false
|
"strict": false,
|
||||||
|
"paths": {
|
||||||
|
"@/utils/style": ["../src/client/utils/style"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user