From 1af8b1a28c2f91b5690b4665bc3b0230306dd718 Mon Sep 17 00:00:00 2001 From: jane Date: Mon, 20 Jul 2026 14:44:06 +0900 Subject: [PATCH] feat: add AIP enterprise pricing page --- .../[locale]/plans/aip-enterprise/page.tsx | 34 +++++ src/components/pages/plans/PlansPage.tsx | 124 ++++++++++++++++-- src/constants/plans.ts | 21 +++ 3 files changed, 168 insertions(+), 11 deletions(-) create mode 100644 src/app/[locale]/plans/aip-enterprise/page.tsx diff --git a/src/app/[locale]/plans/aip-enterprise/page.tsx b/src/app/[locale]/plans/aip-enterprise/page.tsx new file mode 100644 index 00000000..e5a3472c --- /dev/null +++ b/src/app/[locale]/plans/aip-enterprise/page.tsx @@ -0,0 +1,34 @@ +import { notFound } from "next/navigation"; +import type { Metadata } from "next"; +import PlansPage from "@/components/pages/plans/PlansPage"; +import { getLocalePath, isLocale, type Locale } from "@/constants/i18n"; +import { getPlansPageCopy } from "@/copy/contentPages"; +import { withDynamicOgImage } from "@/features/seo/metadata"; + +type AipEnterprisePlansRouteProps = { + params: Promise<{ locale: string }>; +}; + +export async function generateMetadata({ params }: AipEnterprisePlansRouteProps): Promise { + const { locale } = await params; + + if (!isLocale(locale)) return {}; + + const { metadataDescription, metadataTitle } = getPlansPageCopy(locale); + + return withDynamicOgImage({ + title: metadataTitle, + description: metadataDescription, + alternates: { + canonical: getLocalePath(locale, "/plans/aip-enterprise"), + }, + }, { locale, title: metadataTitle, description: metadataDescription }); +} + +export default async function AipEnterprisePlansRoute({ params }: AipEnterprisePlansRouteProps) { + const { locale } = await params; + + if (!isLocale(locale)) notFound(); + + return ; +} diff --git a/src/components/pages/plans/PlansPage.tsx b/src/components/pages/plans/PlansPage.tsx index ac561b2b..969bb76b 100644 --- a/src/components/pages/plans/PlansPage.tsx +++ b/src/components/pages/plans/PlansPage.tsx @@ -2,7 +2,7 @@ import { pageSectionGapClassName, pageXPaddingClassName } from "@/constants/layout"; import { useMemo } from "react"; -import { useRouter } from "next/navigation"; +import { useRouter, useSearchParams } from "next/navigation"; import Button from "@/components/ui/Button"; import Tab from "@/components/ui/Tab"; import TabGroup from "@/components/ui/TabGroup"; @@ -12,6 +12,7 @@ import { getLocalePath, type Locale } from "@/constants/i18n"; import { getPlansPageCopy } from "@/copy/contentPages"; type PlansPageProps = { + enterpriseOnly?: boolean; productKey?: "aip" | "acp"; locale: Locale; }; @@ -110,6 +111,73 @@ function PlanSummaryCard({ ); } +function EnterpriseSummaryCard({ + billingLabel, + ctaLabel, + description, + features, + href, + name, + priceLabel, + tone = "secondary", +}: PlanCard) { + return ( +
+
+
+

{name}

+

{description}

+
+ +
+

{priceLabel}

+ {billingLabel ?

{billingLabel}

: null} +
+ + + + +
+ +
    + {features.map((feature, index) => { + if (isPlanFeatureDivider(feature)) { + return ( +
  • + + {typeof feature === "string" || feature.tone !== "danger" ? "✓" : "✕"} + + {typeof feature === "string" ? feature : feature.value} +
  • + ); + })} +
+
+ ); +} + function getValueToneClass(tone?: ComparisonValue["tone"]) { if (tone === "success") return "text-success"; if (tone === "danger") return "text-destructive"; @@ -199,23 +267,44 @@ function ComparisonTable({ } export default function PlansPage({ + enterpriseOnly = false, productKey = "aip", locale, }: PlansPageProps) { const pricingProducts = pricingProductsByLocale[locale]; const pageCopy = getPlansPageCopy(locale); const router = useRouter(); + const searchParams = useSearchParams(); const activeProductKey: keyof typeof pricingProducts = productKey in pricingProducts ? productKey : "aip"; const activeProduct = useMemo( () => pricingProducts[activeProductKey], [activeProductKey, pricingProducts], ); + const planCards = useMemo( + () => ( + enterpriseOnly + ? activeProduct.cards.filter((card) => card.name === "Enterprise") + : activeProduct.cards + ), + [activeProduct.cards, enterpriseOnly], + ); const activeProductCaption = activeProductKey === "aip" ? "AI Platform" : "Access Control Platform"; function handleProductChange(nextKey: keyof typeof pricingProducts) { if (nextKey === activeProductKey) return; + + if (enterpriseOnly && nextKey === "acp") { + router.push(getLocalePath(locale, "/plans/acp?returnTo=aip-enterprise"), { scroll: false }); + return; + } + + if (searchParams.get("returnTo") === "aip-enterprise" && nextKey === "aip") { + router.push(getLocalePath(locale, "/plans/aip-enterprise"), { scroll: false }); + return; + } + router.push(getLocalePath(locale, `/plans/${nextKey}`), { scroll: false }); } @@ -247,17 +336,30 @@ export default function PlansPage({ {/* 선택된 제품군에 맞는 카드/비교표 렌더링 */}
-
- {activeProduct.cards.map((plan, index) => ( - - ))} +
+ {planCards.map((plan) => { + const href = withLocaleHref(locale, plan.href); + + return enterpriseOnly ? ( + + ) : ( + + ); + })}
- {activeProductKey === "aip" ? ( + {activeProductKey === "aip" && !enterpriseOnly ? (
- + {!enterpriseOnly ? : null}
); } diff --git a/src/constants/plans.ts b/src/constants/plans.ts index a57df62a..2b3da772 100644 --- a/src/constants/plans.ts +++ b/src/constants/plans.ts @@ -102,10 +102,17 @@ export const pricingProductsByLocale: Record = { "Unlimited custom AI agents", "Unlimited RAG knowledge bundles", { type: "divider" }, + "Agent Scheduling", + "Skills", + "Image Generation", + "My Drive", + { type: "divider" }, "Audit logs (max 180 days)", "SSO", "DLP", "Login IP ACL", + "MCP Access Control", + "Agent Access Control", { type: "divider" }, "Custom Branding", "Advanced AI Security Features", @@ -607,10 +614,17 @@ export const pricingProductsByLocale: Record = { "커스텀 AI 에이전트 무제한", "RAG 지식 번들 무제한", { type: "divider" }, + "에이전트 스케줄링", + "스킬", + "이미지 생성", + "My Drive", + { type: "divider" }, "감사 로그 (최대 180일)", "SSO", "DLP", "로그인 IP ACL", + "MCP 접근 제어", + "에이전트 접근 제어", { type: "divider" }, "커스텀 브랜딩", "고급 AI 보안 기능", @@ -895,10 +909,17 @@ export const pricingProductsByLocale: Record = { "カスタムAIエージェント\n無制限", "社内文書学習 (RAG) 無制限", { type: "divider" }, + "エージェントスケジュール", + "スキル", + "画像生成", + "My Drive", + { type: "divider" }, "操作履歴の記録\n(最大180日間)", "シングルサインオン (SSO)", "データ漏洩防止 (DLP)", "IPアドレス制限 (ACL)", + "連携ツールのアクセス管理", + "エージェントのアクセス管理", { type: "divider" }, "カスタムブランディング", "高度な AI セキュリティ機能",