add standards registry authoring workflow
This commit is contained in:
@@ -5,4 +5,3 @@ const nextConfig = {
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"type-check": "tsc --noEmit"
|
||||
"type-check": "tsc --noEmit",
|
||||
"standards:generate": "tsx src/content/standards/generate-artifacts.ts",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^14.0.4",
|
||||
@@ -26,20 +28,21 @@
|
||||
"react-hook-form": "^7.49.2",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"zod": "^3.22.4",
|
||||
"date-fns": "^3.0.6",
|
||||
"zustand": "^4.4.7"
|
||||
"date-fns": "^3.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.6",
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"typescript": "^5.3.3",
|
||||
"tsx": "^4.20.5",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-next": "^14.0.4",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"postcss": "^8.4.33",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
"baseline-browser-mapping": "^2.10.19",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vitest": "^1.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,699 @@
|
||||
{
|
||||
"standards": [
|
||||
{
|
||||
"slug": "erc-3643",
|
||||
"name": "ERC-3643",
|
||||
"shortName": "Compliant Tokenization",
|
||||
"eyebrow": "Compliant Token Standard",
|
||||
"icon": "shield",
|
||||
"summary": "Issue, manage, and distribute regulated digital assets with identity-based compliance enforced directly at the token level.",
|
||||
"category": "Compliance and tokenization",
|
||||
"status": "Live",
|
||||
"audience": "Financial institutions, issuers, tokenization platforms, and regulated RWA teams.",
|
||||
"standards": [
|
||||
"ERC-3643",
|
||||
"ERC-20"
|
||||
],
|
||||
"publisher": "Tokeny",
|
||||
"auditor": "Hacken",
|
||||
"seo": {
|
||||
"title": "ERC-3643 | The Order",
|
||||
"description": "Compliant tokenization for real-world assets with identity-based compliance, lifecycle automation, and ERC-20 compatibility.",
|
||||
"keywords": [
|
||||
"ERC-3643",
|
||||
"compliant tokenization",
|
||||
"real-world assets",
|
||||
"regulated digital assets"
|
||||
]
|
||||
},
|
||||
"specification": {
|
||||
"label": "EIP-3643 Specification",
|
||||
"href": "https://eips.ethereum.org/EIPS/eip-3643",
|
||||
"kind": "spec"
|
||||
},
|
||||
"docs": [
|
||||
{
|
||||
"label": "Tokeny ERC-3643 Overview",
|
||||
"href": "https://www.erc3643.org/",
|
||||
"kind": "docs"
|
||||
}
|
||||
],
|
||||
"repos": [
|
||||
{
|
||||
"label": "ethereum/EIPs",
|
||||
"href": "https://github.com/ethereum/EIPs",
|
||||
"kind": "repo"
|
||||
},
|
||||
{
|
||||
"label": "TokenySolutions/T-REX",
|
||||
"href": "https://github.com/TokenySolutions/T-REX",
|
||||
"kind": "repo"
|
||||
}
|
||||
],
|
||||
"submissionPoints": [
|
||||
{
|
||||
"label": "Ethereum EIPs Repository",
|
||||
"href": "https://github.com/ethereum/EIPs",
|
||||
"kind": "submission"
|
||||
},
|
||||
{
|
||||
"label": "EIP-3643 Pull Requests",
|
||||
"href": "https://github.com/ethereum/EIPs/pulls",
|
||||
"kind": "submission"
|
||||
}
|
||||
],
|
||||
"compliance": {
|
||||
"native": true,
|
||||
"label": "Native compliance",
|
||||
"summary": "Identity, eligibility, and transfer rules are enforced directly in token behavior rather than being bolted on externally.",
|
||||
"enforcement": [
|
||||
"Identity-aware transfer restrictions",
|
||||
"Eligibility controls for holders and counterparties",
|
||||
"Lifecycle rule updates during issuance and administration"
|
||||
]
|
||||
},
|
||||
"governanceBody": "Ethereum Improvement Proposal process with ecosystem implementation led by Tokeny and related adopters.",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Ethereum EIPs editors",
|
||||
"role": "Canonical specification governance",
|
||||
"href": "https://github.com/ethereum/EIPs"
|
||||
},
|
||||
{
|
||||
"name": "Tokeny",
|
||||
"role": "Reference implementation and ecosystem steward",
|
||||
"href": "https://github.com/TokenySolutions/T-REX"
|
||||
}
|
||||
],
|
||||
"statusHistory": [
|
||||
{
|
||||
"date": "2021-11-09",
|
||||
"title": "EIP-3643 created",
|
||||
"summary": "The compliant tokenization standard was introduced through the Ethereum EIP process."
|
||||
},
|
||||
{
|
||||
"date": "2022-01-26",
|
||||
"title": "T-REX reference implementation publicized",
|
||||
"summary": "Production-oriented implementation guidance and contracts became widely available through Tokeny."
|
||||
},
|
||||
{
|
||||
"date": "2024-01-01",
|
||||
"title": "Ecosystem adoption broadened",
|
||||
"summary": "ERC-3643 continued to mature as a practical framework for regulated asset issuance and lifecycle management."
|
||||
}
|
||||
],
|
||||
"implementationExamples": [
|
||||
{
|
||||
"label": "Tokeny T-REX reference contracts",
|
||||
"summary": "Production-oriented smart contract suite implementing identity-aware compliance controls for ERC-3643 assets.",
|
||||
"href": "https://github.com/TokenySolutions/T-REX"
|
||||
},
|
||||
{
|
||||
"label": "EIP-3643 canonical specification",
|
||||
"summary": "The normative source for interface behavior, goals, and compliance-oriented design expectations.",
|
||||
"href": "https://eips.ethereum.org/EIPS/eip-3643"
|
||||
}
|
||||
],
|
||||
"submissionChecklist": [
|
||||
{
|
||||
"label": "Canonical EIP specification reference",
|
||||
"required": true,
|
||||
"summary": "Point to the accepted EIP text and normative standard behavior."
|
||||
},
|
||||
{
|
||||
"label": "Reference implementation repository",
|
||||
"required": true,
|
||||
"summary": "Include a public implementation repo that demonstrates how the standard is realized in production."
|
||||
},
|
||||
{
|
||||
"label": "Compliance posture declaration",
|
||||
"required": true,
|
||||
"summary": "Document whether compliance is natively enforced or relies on external controls."
|
||||
},
|
||||
{
|
||||
"label": "Submission points and governance path",
|
||||
"required": true,
|
||||
"summary": "Identify the repo, pull request surface, and governance venue for amendments or follow-on proposals."
|
||||
}
|
||||
],
|
||||
"ecosystemSupport": [
|
||||
{
|
||||
"area": "Wallet compatibility",
|
||||
"status": "Moderate",
|
||||
"summary": "Leverages ERC-20 compatibility, though full compliance-aware workflows depend on supporting infrastructure."
|
||||
},
|
||||
{
|
||||
"area": "Exchange and marketplace support",
|
||||
"status": "Specialized",
|
||||
"summary": "Best fit for venues prepared to handle regulated asset onboarding and rule-aware transfer flows."
|
||||
},
|
||||
{
|
||||
"area": "Issuer administration tooling",
|
||||
"status": "Strong",
|
||||
"summary": "Well suited to issuer-facing workflows, lifecycle events, and identity-driven operating models."
|
||||
},
|
||||
{
|
||||
"area": "Regulated asset operations",
|
||||
"status": "Strong",
|
||||
"summary": "Purpose-built for permissioned or regulated issuance where transfer and holder eligibility matter."
|
||||
}
|
||||
],
|
||||
"comparisonTarget": "erc-20",
|
||||
"featured": true,
|
||||
"order": 10,
|
||||
"href": "/standards/erc-3643",
|
||||
"landingPage": {
|
||||
"hero": {
|
||||
"eyebrow": "Compliant Token Standard",
|
||||
"headline": "Compliant Tokenization for Real-World Assets",
|
||||
"subheadline": "ERC-3643 is the token standard for issuing, managing, and distributing regulated digital assets with identity-based compliance built into every transfer.",
|
||||
"supportingCopy": "Designed for regulated markets, ERC-3643 combines token issuance, transfer control, lifecycle automation, and ecosystem compatibility in a single standard. It is built for teams bringing securities, funds, private debt, real estate, and other real-world assets onchain.",
|
||||
"primaryCta": {
|
||||
"label": "Explore ERC-3643",
|
||||
"href": "/docs"
|
||||
},
|
||||
"secondaryCta": {
|
||||
"label": "Compare Standards",
|
||||
"href": "#comparison"
|
||||
},
|
||||
"tertiaryLink": {
|
||||
"label": "View Documentation",
|
||||
"href": "/docs"
|
||||
}
|
||||
},
|
||||
"trustStrip": {
|
||||
"label": "Standards and assurance",
|
||||
"items": [
|
||||
{
|
||||
"label": "Standard",
|
||||
"value": "ERC-3643, ERC-20 compatible"
|
||||
},
|
||||
{
|
||||
"label": "Publisher",
|
||||
"value": "Tokeny"
|
||||
},
|
||||
{
|
||||
"label": "Auditor",
|
||||
"value": "Hacken"
|
||||
},
|
||||
{
|
||||
"label": "Category",
|
||||
"value": "Compliant tokenization"
|
||||
},
|
||||
{
|
||||
"label": "Fit",
|
||||
"value": "Regulated digital assets and RWAs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"valueProps": {
|
||||
"headline": "Built for compliant digital asset issuance",
|
||||
"intro": "ERC-3643 embeds identity, eligibility, and transfer controls directly into the token so compliance is enforced as part of the asset itself, not added as a separate layer.",
|
||||
"cards": [
|
||||
{
|
||||
"title": "Built-In Compliance",
|
||||
"copy": "Ensure only eligible participants can hold and transfer assets through identity-based controls enforced onchain."
|
||||
},
|
||||
{
|
||||
"title": "Lifecycle Automation",
|
||||
"copy": "Support issuance, restrictions, corporate actions, and ongoing administration with extensive built-in functionality."
|
||||
},
|
||||
{
|
||||
"title": "Regulated Asset Ready",
|
||||
"copy": "Well suited for securities, funds, private debt, commodities, real estate, and other tokenized real-world assets."
|
||||
},
|
||||
{
|
||||
"title": "ERC-20 Compatibility",
|
||||
"copy": "Integrates with wallets, marketplaces, exchanges, and dApps while maintaining compliance enforcement."
|
||||
},
|
||||
{
|
||||
"title": "Configurable Rules",
|
||||
"copy": "Adapt compliance policies over time to meet changing operational, legal, or market requirements."
|
||||
},
|
||||
{
|
||||
"title": "Proven Model",
|
||||
"copy": "Validated across more than 120 use cases for compliant issuance and asset management."
|
||||
}
|
||||
]
|
||||
},
|
||||
"useCases": {
|
||||
"headline": "Designed for regulated and real-world asset use cases",
|
||||
"intro": "ERC-3643 can be applied across a broad range of tokenized asset classes and identity-driven applications.",
|
||||
"items": [
|
||||
"Equities",
|
||||
"Loans",
|
||||
"Currencies",
|
||||
"Funds and securities",
|
||||
"Commodities",
|
||||
"Private debt",
|
||||
"Bonds",
|
||||
"Real estate",
|
||||
"Real-world assets",
|
||||
"Identity",
|
||||
"Loyalty"
|
||||
],
|
||||
"supportingLine": "From institutional issuance to programmable asset distribution, ERC-3643 provides a consistent compliance framework across asset classes."
|
||||
},
|
||||
"proofPoints": {
|
||||
"headline": "Trusted for compliant tokenization",
|
||||
"intro": "ERC-3643 is designed for teams that need more than simple token issuance. It provides the operational and compliance foundation required for regulated digital asset programs.",
|
||||
"items": [
|
||||
"More than 120 validated use cases across compliant digital asset issuance and management.",
|
||||
"Over 100 built-in functions for lifecycle operations, automation, and corporate actions.",
|
||||
"Identity-based compliance enforced directly at the token level.",
|
||||
"Broad compatibility with exchanges, marketplaces, wallets, and dApps."
|
||||
]
|
||||
},
|
||||
"comparison": {
|
||||
"headline": "Why ERC-3643 instead of a basic token standard?",
|
||||
"intro": "ERC-20 is foundational for fungible tokens, but it does not natively provide the compliance controls needed for regulated issuance. ERC-3643 extends ERC-20 compatibility with identity-aware transfer rules and operational controls built into the token.",
|
||||
"columns": [
|
||||
"Category",
|
||||
"ERC-3643",
|
||||
"ERC-20"
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"category": "Primary purpose",
|
||||
"left": "Compliant tokenization for regulated assets",
|
||||
"right": "General-purpose fungible tokens"
|
||||
},
|
||||
{
|
||||
"category": "Compliance",
|
||||
"left": "Enforced onchain through identity and rules",
|
||||
"right": "Typically external or manual"
|
||||
},
|
||||
{
|
||||
"category": "Transfer restrictions",
|
||||
"left": "Native",
|
||||
"right": "Not native"
|
||||
},
|
||||
{
|
||||
"category": "Eligibility controls",
|
||||
"left": "Native",
|
||||
"right": "Not native"
|
||||
},
|
||||
{
|
||||
"category": "Lifecycle management",
|
||||
"left": "Extensive built-in functions",
|
||||
"right": "Minimal core functions"
|
||||
},
|
||||
{
|
||||
"category": "Best fit",
|
||||
"left": "Regulated assets and RWAs",
|
||||
"right": "Simple token issuance"
|
||||
}
|
||||
],
|
||||
"secondaryNote": "Compared with ERC-1400-style positioning, ERC-3643 is often easier to present as a practical, issuer-friendly standard for compliant tokenization with strong interoperability and lifecycle flexibility.",
|
||||
"cta": {
|
||||
"label": "View Full Comparison",
|
||||
"href": "#comparison"
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
"headline": "Frequently asked questions",
|
||||
"items": [
|
||||
{
|
||||
"question": "What is ERC-3643?",
|
||||
"answer": "ERC-3643 is an Ethereum token standard for compliant tokenization. It combines ERC-20 compatibility with identity-based compliance controls that govern who can hold, transfer, and interact with tokenized assets."
|
||||
},
|
||||
{
|
||||
"question": "How is ERC-3643 different from ERC-20?",
|
||||
"answer": "ERC-20 defines the baseline behavior for fungible tokens, but it does not natively enforce compliance. ERC-3643 adds identity, eligibility, and transfer restrictions directly into token logic, making it better suited for regulated assets."
|
||||
},
|
||||
{
|
||||
"question": "What types of assets can use ERC-3643?",
|
||||
"answer": "ERC-3643 is well suited for equities, funds, securities, private debt, bonds, commodities, real estate, currencies, and other tokenized real-world assets."
|
||||
},
|
||||
{
|
||||
"question": "Does ERC-3643 still work with the broader ecosystem?",
|
||||
"answer": "Yes. ERC-3643 is designed to remain compatible with much of the broader Ethereum ecosystem, including wallets, dApps, marketplaces, and exchanges, while preserving compliance enforcement."
|
||||
},
|
||||
{
|
||||
"question": "Who is ERC-3643 for?",
|
||||
"answer": "It is particularly relevant for financial institutions, asset issuers, tokenization platforms, fund operators, and enterprises that need compliant digital asset issuance and lifecycle management."
|
||||
}
|
||||
]
|
||||
},
|
||||
"footerCta": {
|
||||
"headline": "Bring regulated assets onchain with greater control",
|
||||
"copy": "Use ERC-3643 to issue compliant digital assets with identity-based transfer enforcement, lifecycle automation, and compatibility across the broader digital asset ecosystem.",
|
||||
"primaryCta": {
|
||||
"label": "Get Started",
|
||||
"href": "/contact"
|
||||
},
|
||||
"secondaryCta": {
|
||||
"label": "Talk to Sales",
|
||||
"href": "/contact"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "erc-20",
|
||||
"name": "ERC-20",
|
||||
"shortName": "Fungible Token Baseline",
|
||||
"eyebrow": "Foundational Token Standard",
|
||||
"icon": "coins",
|
||||
"summary": "The core fungible token standard for broad wallet, exchange, and application compatibility across the Ethereum ecosystem.",
|
||||
"category": "General-purpose tokenization",
|
||||
"status": "Live",
|
||||
"audience": "Protocol teams, issuers, exchanges, treasury teams, and builders focused on interoperability.",
|
||||
"standards": [
|
||||
"ERC-20"
|
||||
],
|
||||
"seo": {
|
||||
"title": "ERC-20 | The Order",
|
||||
"description": "The foundational fungible token standard for broad wallet, exchange, and application compatibility across Ethereum.",
|
||||
"keywords": [
|
||||
"ERC-20",
|
||||
"fungible token",
|
||||
"Ethereum token standard",
|
||||
"token interoperability"
|
||||
]
|
||||
},
|
||||
"specification": {
|
||||
"label": "EIP-20 Specification",
|
||||
"href": "https://eips.ethereum.org/EIPS/eip-20",
|
||||
"kind": "spec"
|
||||
},
|
||||
"docs": [
|
||||
{
|
||||
"label": "OpenZeppelin ERC-20 Guide",
|
||||
"href": "https://docs.openzeppelin.com/contracts/4.x/erc20",
|
||||
"kind": "docs"
|
||||
}
|
||||
],
|
||||
"repos": [
|
||||
{
|
||||
"label": "ethereum/EIPs",
|
||||
"href": "https://github.com/ethereum/EIPs",
|
||||
"kind": "repo"
|
||||
},
|
||||
{
|
||||
"label": "OpenZeppelin/openzeppelin-contracts",
|
||||
"href": "https://github.com/OpenZeppelin/openzeppelin-contracts",
|
||||
"kind": "repo"
|
||||
}
|
||||
],
|
||||
"submissionPoints": [
|
||||
{
|
||||
"label": "Ethereum EIPs Repository",
|
||||
"href": "https://github.com/ethereum/EIPs",
|
||||
"kind": "submission"
|
||||
},
|
||||
{
|
||||
"label": "EIP-20 Pull Requests",
|
||||
"href": "https://github.com/ethereum/EIPs/pulls",
|
||||
"kind": "submission"
|
||||
}
|
||||
],
|
||||
"compliance": {
|
||||
"native": false,
|
||||
"label": "External compliance required",
|
||||
"summary": "ERC-20 does not natively enforce compliance. Regulated issuance typically depends on wrappers, policy layers, or additional contract systems.",
|
||||
"enforcement": [
|
||||
"External policy engines or offchain controls",
|
||||
"Custom transfer restrictions when needed",
|
||||
"Operational overlays for regulated counterparties"
|
||||
]
|
||||
},
|
||||
"governanceBody": "Ethereum Improvement Proposal process with widespread ecosystem stewardship across libraries, wallets, and exchanges.",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Ethereum EIPs editors",
|
||||
"role": "Canonical specification governance",
|
||||
"href": "https://github.com/ethereum/EIPs"
|
||||
},
|
||||
{
|
||||
"name": "OpenZeppelin",
|
||||
"role": "Widely used implementation maintainer",
|
||||
"href": "https://github.com/OpenZeppelin/openzeppelin-contracts"
|
||||
}
|
||||
],
|
||||
"statusHistory": [
|
||||
{
|
||||
"date": "2015-11-19",
|
||||
"title": "EIP-20 proposed",
|
||||
"summary": "The fungible token interface was introduced as a standard for interoperable Ethereum assets."
|
||||
},
|
||||
{
|
||||
"date": "2017-09-20",
|
||||
"title": "EIP-20 finalized",
|
||||
"summary": "ERC-20 became the canonical baseline for fungible token behavior across the ecosystem."
|
||||
},
|
||||
{
|
||||
"date": "2024-01-01",
|
||||
"title": "Baseline ecosystem standard maintained",
|
||||
"summary": "ERC-20 remains the reference layer for fungible token interoperability and exchange integration."
|
||||
}
|
||||
],
|
||||
"implementationExamples": [
|
||||
{
|
||||
"label": "OpenZeppelin ERC-20 implementation",
|
||||
"summary": "Widely used and audited Solidity implementation for fungible token behavior.",
|
||||
"href": "https://github.com/OpenZeppelin/openzeppelin-contracts"
|
||||
},
|
||||
{
|
||||
"label": "EIP-20 canonical specification",
|
||||
"summary": "Normative interface definition for fungible token balances, transfers, and approvals.",
|
||||
"href": "https://eips.ethereum.org/EIPS/eip-20"
|
||||
}
|
||||
],
|
||||
"submissionChecklist": [
|
||||
{
|
||||
"label": "Canonical EIP specification reference",
|
||||
"required": true,
|
||||
"summary": "Link the accepted EIP text as the normative basis for the registry entry."
|
||||
},
|
||||
{
|
||||
"label": "Reference implementation repository",
|
||||
"required": true,
|
||||
"summary": "Provide at least one widely adopted implementation repo to anchor ecosystem usage."
|
||||
},
|
||||
{
|
||||
"label": "Compliance dependency disclosure",
|
||||
"required": true,
|
||||
"summary": "State clearly that compliance is external if the token standard does not enforce it natively."
|
||||
},
|
||||
{
|
||||
"label": "Submission and amendment path",
|
||||
"required": true,
|
||||
"summary": "Document the repository and PR path for future revisions to the standard."
|
||||
}
|
||||
],
|
||||
"ecosystemSupport": [
|
||||
{
|
||||
"area": "Wallet compatibility",
|
||||
"status": "Strong",
|
||||
"summary": "Near-universal support across Ethereum wallets and custody providers."
|
||||
},
|
||||
{
|
||||
"area": "Exchange and marketplace support",
|
||||
"status": "Strong",
|
||||
"summary": "Deep support across centralized and decentralized trading venues."
|
||||
},
|
||||
{
|
||||
"area": "Issuer administration tooling",
|
||||
"status": "Moderate",
|
||||
"summary": "Strong for basic issuance, though advanced compliance workflows usually require add-on systems."
|
||||
},
|
||||
{
|
||||
"area": "Regulated asset operations",
|
||||
"status": "Specialized",
|
||||
"summary": "Can be used as a base layer, but regulated issuance typically needs additional policy and control infrastructure."
|
||||
}
|
||||
],
|
||||
"comparisonTarget": "erc-3643",
|
||||
"featured": false,
|
||||
"order": 20,
|
||||
"href": "/standards/erc-20",
|
||||
"landingPage": {
|
||||
"hero": {
|
||||
"eyebrow": "Foundational Token Standard",
|
||||
"headline": "The Baseline Standard for Fungible Tokens",
|
||||
"subheadline": "ERC-20 defines the core interface for fungible tokens on Ethereum and remains the foundation for interoperable token issuance across wallets, exchanges, and applications.",
|
||||
"supportingCopy": "For teams that need broad ecosystem support and straightforward fungible token behavior, ERC-20 provides the common baseline. It is best suited to general-purpose token issuance and often serves as the interoperability layer beneath more specialized token frameworks.",
|
||||
"primaryCta": {
|
||||
"label": "Explore ERC-20",
|
||||
"href": "/docs"
|
||||
},
|
||||
"secondaryCta": {
|
||||
"label": "Compare Standards",
|
||||
"href": "#comparison"
|
||||
},
|
||||
"tertiaryLink": {
|
||||
"label": "View Documentation",
|
||||
"href": "/docs"
|
||||
}
|
||||
},
|
||||
"trustStrip": {
|
||||
"label": "Standards overview",
|
||||
"items": [
|
||||
{
|
||||
"label": "Standard",
|
||||
"value": "ERC-20"
|
||||
},
|
||||
{
|
||||
"label": "Role",
|
||||
"value": "Baseline fungible token interface"
|
||||
},
|
||||
{
|
||||
"label": "Category",
|
||||
"value": "General-purpose tokenization"
|
||||
},
|
||||
{
|
||||
"label": "Fit",
|
||||
"value": "Interoperable token issuance and ecosystem compatibility"
|
||||
},
|
||||
{
|
||||
"label": "Primary strength",
|
||||
"value": "Simplicity and universal support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"valueProps": {
|
||||
"headline": "Optimized for interoperability and simplicity",
|
||||
"intro": "ERC-20 established the common language for fungible tokens across the Ethereum ecosystem. Its strength is broad adoption, predictable behavior, and straightforward integration.",
|
||||
"cards": [
|
||||
{
|
||||
"title": "Universal Compatibility",
|
||||
"copy": "Supported across wallets, exchanges, custodians, and dApps as the baseline fungible token format."
|
||||
},
|
||||
{
|
||||
"title": "Simple Token Logic",
|
||||
"copy": "Provides a minimal and predictable interface for balances, transfers, approvals, and allowances."
|
||||
},
|
||||
{
|
||||
"title": "Builder Friendly",
|
||||
"copy": "Well understood by developers, infrastructure providers, and integrators across the Ethereum ecosystem."
|
||||
},
|
||||
{
|
||||
"title": "Strong Liquidity Fit",
|
||||
"copy": "Works naturally with trading venues, pools, bridges, and DeFi protocols designed around fungible tokens."
|
||||
},
|
||||
{
|
||||
"title": "Composable Foundation",
|
||||
"copy": "Frequently used as the compatibility layer for more specialized token models and compliance overlays."
|
||||
},
|
||||
{
|
||||
"title": "Fast to Adopt",
|
||||
"copy": "A practical choice for teams that need broad support without embedding complex policy logic directly into the token."
|
||||
}
|
||||
]
|
||||
},
|
||||
"useCases": {
|
||||
"headline": "Best suited to general-purpose token issuance",
|
||||
"intro": "ERC-20 is often the right choice when the goal is broad market compatibility rather than native compliance enforcement.",
|
||||
"items": [
|
||||
"Utility tokens",
|
||||
"Stablecoins",
|
||||
"Governance tokens",
|
||||
"Rewards tokens",
|
||||
"Exchange-listed assets",
|
||||
"Protocol assets",
|
||||
"Treasury instruments",
|
||||
"Liquidity and settlement rails",
|
||||
"Wrapped assets",
|
||||
"Ecosystem incentives"
|
||||
],
|
||||
"supportingLine": "ERC-20 can still be used in regulated contexts, but compliance controls generally need to be added through external systems, wrappers, or custom extensions."
|
||||
},
|
||||
"proofPoints": {
|
||||
"headline": "Why teams still start with ERC-20",
|
||||
"intro": "ERC-20 remains the default standard for fungible token interoperability because it balances simplicity, widespread support, and implementation maturity.",
|
||||
"items": [
|
||||
"Widely supported across the Ethereum ecosystem and token infrastructure stack.",
|
||||
"Simple enough to implement, audit, and integrate without specialized compliance architecture.",
|
||||
"Well suited to liquid, broadly distributed token models and exchange-facing assets.",
|
||||
"Acts as the reference point for comparing more specialized token standards such as ERC-3643."
|
||||
]
|
||||
},
|
||||
"comparison": {
|
||||
"headline": "When is ERC-20 enough, and when is it not?",
|
||||
"intro": "ERC-20 is ideal when broad compatibility and simple fungible token behavior matter most. It becomes less suitable when regulated issuance requires native controls over identity, eligibility, and transfer permissions.",
|
||||
"columns": [
|
||||
"Category",
|
||||
"ERC-20",
|
||||
"ERC-3643"
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"category": "Primary purpose",
|
||||
"left": "General-purpose fungible tokens",
|
||||
"right": "Compliant tokenization for regulated assets"
|
||||
},
|
||||
{
|
||||
"category": "Compliance model",
|
||||
"left": "Usually external, manual, or custom",
|
||||
"right": "Identity-based and enforced onchain"
|
||||
},
|
||||
{
|
||||
"category": "Transfer restrictions",
|
||||
"left": "Not native",
|
||||
"right": "Native"
|
||||
},
|
||||
{
|
||||
"category": "Eligibility controls",
|
||||
"left": "Not native",
|
||||
"right": "Native"
|
||||
},
|
||||
{
|
||||
"category": "Best fit",
|
||||
"left": "Broad ecosystem issuance and liquidity",
|
||||
"right": "Regulated assets and RWAs"
|
||||
},
|
||||
{
|
||||
"category": "Operational complexity",
|
||||
"left": "Lower baseline complexity",
|
||||
"right": "Higher capability with more built-in controls"
|
||||
}
|
||||
],
|
||||
"secondaryNote": "ERC-20 is the baseline standard. For regulated programs, it is often the starting point for comparison rather than the final architecture.",
|
||||
"cta": {
|
||||
"label": "View Standards Library",
|
||||
"href": "/standards"
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
"headline": "Frequently asked questions",
|
||||
"items": [
|
||||
{
|
||||
"question": "What is ERC-20?",
|
||||
"answer": "ERC-20 is the standard interface for fungible tokens on Ethereum. It defines how balances, transfers, approvals, and allowances should behave so tokens can interoperate across the ecosystem."
|
||||
},
|
||||
{
|
||||
"question": "Why is ERC-20 so widely used?",
|
||||
"answer": "Its broad adoption comes from its simplicity and ecosystem support. Most wallets, exchanges, and DeFi applications are built to work with ERC-20 tokens by default."
|
||||
},
|
||||
{
|
||||
"question": "Does ERC-20 include compliance controls?",
|
||||
"answer": "Not natively. Teams that need regulated issuance or controlled transfers usually add compliance through external systems, custom extensions, or more specialized standards."
|
||||
},
|
||||
{
|
||||
"question": "When should a team choose ERC-20?",
|
||||
"answer": "ERC-20 is a strong choice when broad compatibility, liquidity integration, and straightforward token behavior matter more than embedding policy logic directly into the asset."
|
||||
},
|
||||
{
|
||||
"question": "How does ERC-20 relate to ERC-3643?",
|
||||
"answer": "ERC-3643 builds on ERC-20 compatibility while adding identity-based compliance and transfer controls. ERC-20 is the simpler baseline; ERC-3643 is the more compliance-oriented framework."
|
||||
}
|
||||
]
|
||||
},
|
||||
"footerCta": {
|
||||
"headline": "Start with the right token standard for the job",
|
||||
"copy": "Use ERC-20 when you need broad compatibility and simple fungible token behavior, or compare it against specialized standards when compliance and lifecycle controls are part of the requirement.",
|
||||
"primaryCta": {
|
||||
"label": "Explore Standards",
|
||||
"href": "/standards"
|
||||
},
|
||||
"secondaryCta": {
|
||||
"label": "Talk to Sales",
|
||||
"href": "/contact"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://the-order.local/standards/schema.json",
|
||||
"title": "The Order Standards Registry",
|
||||
"description": "Canonical schema for standards registry entries used by the public portal.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/standardRegistryEntry"
|
||||
},
|
||||
"$defs": {
|
||||
"standardIcon": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shield",
|
||||
"coins",
|
||||
"scale",
|
||||
"network",
|
||||
"file-stack"
|
||||
]
|
||||
},
|
||||
"standardLink": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"label",
|
||||
"href",
|
||||
"kind"
|
||||
],
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"href": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"spec",
|
||||
"repo",
|
||||
"docs",
|
||||
"submission"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"seo": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"title",
|
||||
"description"
|
||||
],
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"keywords": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"complianceProfile": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"native",
|
||||
"label",
|
||||
"summary",
|
||||
"enforcement"
|
||||
],
|
||||
"properties": {
|
||||
"native": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"enforcement": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"maintainer": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"role"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"href": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"statusEvent": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"date",
|
||||
"title",
|
||||
"summary"
|
||||
],
|
||||
"properties": {
|
||||
"date": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"implementationExample": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"label",
|
||||
"summary",
|
||||
"href"
|
||||
],
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"href": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"checklistItem": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"label",
|
||||
"required",
|
||||
"summary"
|
||||
],
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"ecosystemSupport": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"area",
|
||||
"status",
|
||||
"summary"
|
||||
],
|
||||
"properties": {
|
||||
"area": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Strong",
|
||||
"Moderate",
|
||||
"Specialized"
|
||||
]
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"marketingLink": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"label",
|
||||
"href"
|
||||
],
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"href": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"standardRegistryEntry": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"slug",
|
||||
"name",
|
||||
"icon",
|
||||
"summary",
|
||||
"category",
|
||||
"status",
|
||||
"audience",
|
||||
"standards",
|
||||
"seo",
|
||||
"specification",
|
||||
"repos",
|
||||
"submissionPoints",
|
||||
"compliance",
|
||||
"governanceBody",
|
||||
"maintainers",
|
||||
"statusHistory",
|
||||
"implementationExamples",
|
||||
"submissionChecklist",
|
||||
"ecosystemSupport",
|
||||
"order",
|
||||
"href",
|
||||
"landingPage"
|
||||
],
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"shortName": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"eyebrow": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"icon": {
|
||||
"$ref": "#/$defs/standardIcon"
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Live",
|
||||
"Preview"
|
||||
]
|
||||
},
|
||||
"audience": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"standards": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"publisher": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"auditor": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"seo": {
|
||||
"$ref": "#/$defs/seo"
|
||||
},
|
||||
"specification": {
|
||||
"$ref": "#/$defs/standardLink"
|
||||
},
|
||||
"docs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/standardLink"
|
||||
}
|
||||
},
|
||||
"repos": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/standardLink"
|
||||
}
|
||||
},
|
||||
"submissionPoints": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/standardLink"
|
||||
}
|
||||
},
|
||||
"compliance": {
|
||||
"$ref": "#/$defs/complianceProfile"
|
||||
},
|
||||
"governanceBody": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"maintainers": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/maintainer"
|
||||
}
|
||||
},
|
||||
"statusHistory": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/statusEvent"
|
||||
}
|
||||
},
|
||||
"implementationExamples": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/implementationExample"
|
||||
}
|
||||
},
|
||||
"submissionChecklist": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/checklistItem"
|
||||
}
|
||||
},
|
||||
"ecosystemSupport": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/ecosystemSupport"
|
||||
}
|
||||
},
|
||||
"comparisonTarget": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"featured": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"order": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"href": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"landingPage": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"hero"
|
||||
],
|
||||
"properties": {
|
||||
"hero": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"headline",
|
||||
"subheadline"
|
||||
],
|
||||
"properties": {
|
||||
"eyebrow": {
|
||||
"type": "string"
|
||||
},
|
||||
"headline": {
|
||||
"type": "string"
|
||||
},
|
||||
"subheadline": {
|
||||
"type": "string"
|
||||
},
|
||||
"supportingCopy": {
|
||||
"type": "string"
|
||||
},
|
||||
"primaryCta": {
|
||||
"$ref": "#/$defs/marketingLink"
|
||||
},
|
||||
"secondaryCta": {
|
||||
"$ref": "#/$defs/marketingLink"
|
||||
},
|
||||
"tertiaryLink": {
|
||||
"$ref": "#/$defs/marketingLink"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../../lib/ui-server';
|
||||
|
||||
export default function AboutPage(): JSX.Element {
|
||||
return (
|
||||
@@ -39,7 +39,7 @@ export default function AboutPage(): JSX.Element {
|
||||
<span className="mr-2">•</span>
|
||||
<span>
|
||||
<strong>eResidency:</strong> Digital residency credentials for individuals seeking
|
||||
to participate in The Order's ecosystem
|
||||
to participate in The Order's ecosystem
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
@@ -113,4 +113,3 @@ export default function AboutPage(): JSX.Element {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getStandardBySlug } from '../../../../content/standards';
|
||||
|
||||
export async function GET(
|
||||
_request: Request,
|
||||
{ params }: { params: { slug: string } }
|
||||
): Promise<NextResponse> {
|
||||
const standard = getStandardBySlug(params.slug);
|
||||
|
||||
if (!standard) {
|
||||
return NextResponse.json({ error: 'Standard not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json(standard);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getSortedStandards } from '../../../content/standards';
|
||||
import { filterStandards } from '../../../content/standards/filter';
|
||||
|
||||
export async function GET(request: Request): Promise<NextResponse> {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const query = searchParams.get('q')?.trim().toLowerCase() ?? '';
|
||||
const category = searchParams.get('category')?.trim().toLowerCase() ?? '';
|
||||
const status = searchParams.get('status')?.trim().toLowerCase() ?? '';
|
||||
const featured = searchParams.get('featured');
|
||||
const compliance = searchParams.get('compliance')?.trim().toLowerCase() ?? '';
|
||||
const limit = Number(searchParams.get('limit') ?? '');
|
||||
|
||||
const standards = filterStandards(getSortedStandards(), {
|
||||
q: query,
|
||||
category,
|
||||
status,
|
||||
featured,
|
||||
compliance,
|
||||
limit: Number.isFinite(limit) && limit > 0 ? limit : null,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
standards,
|
||||
count: standards.length,
|
||||
filters: {
|
||||
q: query || null,
|
||||
category: category || null,
|
||||
status: status || null,
|
||||
featured: featured ?? null,
|
||||
compliance: compliance || null,
|
||||
limit: Number.isFinite(limit) && limit > 0 ? limit : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { standardsRegistryJsonSchema } from '../../../../content/standards/schema-json';
|
||||
|
||||
export async function GET(): Promise<NextResponse> {
|
||||
return NextResponse.json(standardsRegistryJsonSchema);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export default function ContactPage(): JSX.Element {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
success('Message sent successfully!', 'We will get back to you soon.');
|
||||
setFormData({ name: '', email: '', subject: '', message: '' });
|
||||
} catch (err) {
|
||||
} catch {
|
||||
showError('Failed to send message', 'Please try again later.');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
@@ -35,14 +35,14 @@ export default function ContactPage(): JSX.Element {
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">Contact Us</h1>
|
||||
<p className="text-xl text-gray-600">
|
||||
Have questions? We'd love to hear from you.
|
||||
Have questions? We'd love to hear from you.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Send us a message</CardTitle>
|
||||
<CardDescription>Fill out the form below and we'll get back to you as soon as possible.</CardDescription>
|
||||
<CardDescription>Fill out the form below and we'll get back to you as soon as possible.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
@@ -118,4 +118,3 @@ export default function ContactPage(): JSX.Element {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../lib/ui-server';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function DocsPage(): JSX.Element {
|
||||
@@ -8,7 +8,7 @@ export default function DocsPage(): JSX.Element {
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">Documentation</h1>
|
||||
<p className="text-xl text-gray-600">
|
||||
Learn how to use The Order's services and APIs
|
||||
Learn how to use The Order's services and APIs
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function DocsPage(): JSX.Element {
|
||||
<Link href="/apply" className="text-primary hover:underline">
|
||||
application form
|
||||
</Link>
|
||||
. You'll need to provide personal information and identity documents.
|
||||
. You'll need to provide personal information and identity documents.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@@ -43,7 +43,7 @@ export default function DocsPage(): JSX.Element {
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg mb-2">3. Receive Credentials</h3>
|
||||
<p className="text-gray-700">
|
||||
Once approved, you'll receive verifiable credentials that you can use to access
|
||||
Once approved, you'll receive verifiable credentials that you can use to access
|
||||
services and prove your identity.
|
||||
</p>
|
||||
</div>
|
||||
@@ -108,7 +108,7 @@ export default function DocsPage(): JSX.Element {
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Support</CardTitle>
|
||||
<CardDescription>Need help? We're here for you</CardDescription>
|
||||
<CardDescription>Need help? We're here for you</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-700 mb-4">
|
||||
@@ -125,4 +125,3 @@ export default function DocsPage(): JSX.Element {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
|
||||
|
||||
export default function Error({
|
||||
@@ -10,10 +9,6 @@ export default function Error({
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}): JSX.Element {
|
||||
useEffect(() => {
|
||||
console.error(error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center px-4">
|
||||
<Card className="max-w-md w-full text-center">
|
||||
@@ -40,4 +35,3 @@ export default function Error({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ export default function LoginPage(): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { login } = useAuth();
|
||||
const { success, error: showError } = useToast();
|
||||
const isDev = process.env.NODE_ENV !== 'production';
|
||||
const [formData, setFormData] = useState({
|
||||
email: '',
|
||||
password: '',
|
||||
@@ -17,6 +18,18 @@ export default function LoginPage(): JSX.Element {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [loginError, setLoginError] = useState<string | null>(null);
|
||||
|
||||
const completeLogin = (user: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
accessToken: string;
|
||||
roles: string[];
|
||||
}): void => {
|
||||
login(user);
|
||||
success('Login successful', 'Welcome back!');
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
@@ -33,14 +46,12 @@ export default function LoginPage(): JSX.Element {
|
||||
const mockUser = {
|
||||
id: 'user-123',
|
||||
email: formData.email,
|
||||
name: formData.email.split('@')[0],
|
||||
name: formData.email.split('@')[0] || 'User',
|
||||
accessToken: 'mock-access-token-' + Date.now(),
|
||||
roles: ['user'],
|
||||
};
|
||||
|
||||
login(mockUser);
|
||||
success('Login successful', 'Welcome back!');
|
||||
router.push('/');
|
||||
completeLogin(mockUser);
|
||||
} else {
|
||||
throw new Error('Please enter email and password');
|
||||
}
|
||||
@@ -53,6 +64,18 @@ export default function LoginPage(): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDevSeedLogin = (role: 'admin' | 'editor' | 'user'): void => {
|
||||
const mockUser = {
|
||||
id: `dev-${role}`,
|
||||
email: `${role}@local.dev`,
|
||||
name: `Dev ${role[0]!.toUpperCase()}${role.slice(1)}`,
|
||||
accessToken: `dev-token-${role}-${Date.now()}`,
|
||||
roles: role === 'user' ? ['user'] : ['user', role],
|
||||
};
|
||||
|
||||
completeLogin(mockUser);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center py-12 px-4">
|
||||
<Card className="w-full max-w-md">
|
||||
@@ -105,7 +128,7 @@ export default function LoginPage(): JSX.Element {
|
||||
</Button>
|
||||
|
||||
<div className="text-center text-sm text-gray-600">
|
||||
Don't have an account?{' '}
|
||||
Don't have an account?{' '}
|
||||
<a href="/register" className="text-primary hover:underline">
|
||||
Sign up
|
||||
</a>
|
||||
@@ -123,9 +146,28 @@ export default function LoginPage(): JSX.Element {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isDev ? (
|
||||
<div className="mt-6 pt-6 border-t">
|
||||
<p className="text-sm font-medium text-gray-700 text-center mb-4">Development quick access</p>
|
||||
<div className="grid gap-2 sm:grid-cols-3">
|
||||
<Button variant="outline" type="button" onClick={() => handleDevSeedLogin('admin')}>
|
||||
Seed Admin
|
||||
</Button>
|
||||
<Button variant="outline" type="button" onClick={() => handleDevSeedLogin('editor')}>
|
||||
Seed Editor
|
||||
</Button>
|
||||
<Button variant="outline" type="button" onClick={() => handleDevSeedLogin('user')}>
|
||||
Seed User
|
||||
</Button>
|
||||
</div>
|
||||
<p className="mt-3 text-center text-xs text-gray-500">
|
||||
Use `Seed Admin` or `Seed Editor` to access `/standards/admin` locally.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
|
||||
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from '../lib/ui-server';
|
||||
|
||||
export default function NotFound(): JSX.Element {
|
||||
return (
|
||||
@@ -11,19 +11,18 @@ export default function NotFound(): JSX.Element {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-gray-600">
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
</p>
|
||||
<div className="flex gap-4 justify-center">
|
||||
<Link href="/">
|
||||
<Button>Go Home</Button>
|
||||
</Link>
|
||||
<Button variant="outline" onClick={() => window.history.back()}>
|
||||
Go Back
|
||||
</Button>
|
||||
<Link href="/docs">
|
||||
<Button variant="outline">View Docs</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,49 @@
|
||||
import Link from 'next/link';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
|
||||
import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle } from '../lib/ui-server';
|
||||
import { getComparisonTarget, getFeaturedStandards, getSortedStandards } from '../content/standards';
|
||||
import { StandardIcon } from '../components/marketing/standard-icon';
|
||||
|
||||
export default function Home(): JSX.Element {
|
||||
const featuredStandard = getFeaturedStandards()[0] ?? getSortedStandards()[0];
|
||||
const standardsPreview = getSortedStandards().slice(0, 2);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100">
|
||||
<div className="min-h-screen bg-[linear-gradient(180deg,#fff_0%,#f8fafc_42%,#fff_100%)]">
|
||||
<div className="container mx-auto px-4 py-16">
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-5xl font-bold text-gray-900 mb-4">The Order</h1>
|
||||
<p className="text-xl text-gray-600 max-w-2xl mx-auto mb-6">
|
||||
Order of Military Hospitallers - Digital Identity & Governance Platform
|
||||
</p>
|
||||
<p className="text-lg text-gray-500 max-w-2xl mx-auto">
|
||||
Apply for eResidency, verify credentials, and access our services
|
||||
</p>
|
||||
<div className="rounded-[2rem] border border-slate-200 bg-[radial-gradient(circle_at_top_left,_rgba(217,119,6,0.12),_transparent_30%),linear-gradient(180deg,_#fff,_#f8fafc)] px-8 py-14 shadow-sm">
|
||||
<div className="max-w-3xl">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="mb-6 border-amber-300 bg-amber-50 px-4 py-1 text-[11px] uppercase tracking-[0.24em] text-amber-900"
|
||||
>
|
||||
Public Portal
|
||||
</Badge>
|
||||
<h1 className="text-5xl font-bold text-slate-950 mb-4">The Order</h1>
|
||||
<p className="text-xl text-slate-700 max-w-2xl mb-6">
|
||||
Order of Military Hospitallers digital identity, standards, and governance platform.
|
||||
</p>
|
||||
<p className="text-lg text-slate-600 max-w-2xl">
|
||||
Apply for eResidency, verify credentials, review implementation standards, and access
|
||||
public documentation for regulated digital asset programs.
|
||||
</p>
|
||||
<div className="mt-8 flex flex-wrap gap-3">
|
||||
<Link
|
||||
href="/apply"
|
||||
className="inline-flex items-center justify-center rounded-full bg-slate-950 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800"
|
||||
>
|
||||
Begin Application
|
||||
</Link>
|
||||
<Link
|
||||
href="/standards"
|
||||
className="inline-flex items-center justify-center rounded-full border border-slate-300 bg-white px-5 py-3 text-sm font-semibold text-slate-900 transition hover:bg-slate-50"
|
||||
>
|
||||
Explore Standards
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
|
||||
<div className="mt-12 grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Apply for eResidency</CardTitle>
|
||||
@@ -88,6 +116,151 @@ export default function Home(): JSX.Element {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{featuredStandard ? (
|
||||
<section className="mx-auto mt-16 max-w-6xl">
|
||||
<div className="mb-8 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.22em] text-slate-500">
|
||||
Featured standard
|
||||
</p>
|
||||
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-slate-950">
|
||||
Standards built for regulated digital asset programs
|
||||
</h2>
|
||||
</div>
|
||||
<Link
|
||||
href="/standards"
|
||||
className="text-sm font-semibold text-slate-950 underline-offset-4 hover:underline"
|
||||
>
|
||||
Browse all standards
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader className="gap-4">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Badge variant="outline" className="border-slate-300 bg-slate-50 text-slate-700">
|
||||
{featuredStandard.status}
|
||||
</Badge>
|
||||
<Badge
|
||||
className={
|
||||
featuredStandard.compliance.native
|
||||
? 'bg-emerald-600 text-white hover:bg-emerald-600'
|
||||
: 'bg-slate-800 text-white hover:bg-slate-800'
|
||||
}
|
||||
>
|
||||
{featuredStandard.compliance.native ? 'Native compliance' : 'External compliance'}
|
||||
</Badge>
|
||||
<span className="inline-flex h-10 w-10 items-center justify-center rounded-full bg-slate-100 text-sm font-semibold text-slate-700">
|
||||
<StandardIcon name={featuredStandard.icon} />
|
||||
</span>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-slate-500">
|
||||
{featuredStandard.category}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-3xl text-slate-950">{featuredStandard.name}</CardTitle>
|
||||
<CardDescription className="mt-2 max-w-3xl text-base text-slate-600">
|
||||
{featuredStandard.summary}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(280px,0.9fr)]">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="rounded-2xl bg-slate-50 p-4">
|
||||
<div className="text-xs uppercase tracking-[0.18em] text-slate-500">Audience</div>
|
||||
<p className="mt-2 text-sm leading-6 text-slate-800">{featuredStandard.audience}</p>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-slate-50 p-4">
|
||||
<div className="text-xs uppercase tracking-[0.18em] text-slate-500">Standards</div>
|
||||
<p className="mt-2 text-sm leading-6 text-slate-800">
|
||||
{featuredStandard.standards.join(', ')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-between gap-4 rounded-2xl border border-slate-200 bg-slate-50 p-5">
|
||||
<div className="space-y-2 text-sm text-slate-600">
|
||||
{featuredStandard.publisher ? <p>Publisher: {featuredStandard.publisher}</p> : null}
|
||||
{featuredStandard.auditor ? <p>Auditor: {featuredStandard.auditor}</p> : null}
|
||||
{getComparisonTarget(featuredStandard) ? (
|
||||
<p>Compare with: {getComparisonTarget(featuredStandard)?.name}</p>
|
||||
) : null}
|
||||
<p>Submission points: {featuredStandard.submissionPoints.length}</p>
|
||||
</div>
|
||||
<Link
|
||||
href={featuredStandard.href}
|
||||
className="inline-flex items-center text-sm font-semibold text-slate-950 underline-offset-4 hover:underline"
|
||||
>
|
||||
View {featuredStandard.name}
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{standardsPreview.length > 0 ? (
|
||||
<section className="mx-auto mt-10 max-w-6xl">
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
{standardsPreview.map((standard) => (
|
||||
<Card key={standard.slug} className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader className="gap-4">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Badge variant="outline" className="border-slate-300 bg-slate-50 text-slate-700">
|
||||
{standard.status}
|
||||
</Badge>
|
||||
{standard.featured ? (
|
||||
<Badge className="bg-slate-950 text-white hover:bg-slate-950">Featured</Badge>
|
||||
) : null}
|
||||
<Badge
|
||||
className={
|
||||
standard.compliance.native
|
||||
? 'bg-emerald-600 text-white hover:bg-emerald-600'
|
||||
: 'bg-slate-800 text-white hover:bg-slate-800'
|
||||
}
|
||||
>
|
||||
{standard.compliance.native ? 'Native compliance' : 'External compliance'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-3 text-2xl text-slate-950">
|
||||
<span className="inline-flex h-10 w-10 items-center justify-center rounded-full bg-slate-100 text-sm font-semibold text-slate-700">
|
||||
<StandardIcon name={standard.icon} />
|
||||
</span>
|
||||
<span>{standard.name}</span>
|
||||
</CardTitle>
|
||||
<CardDescription className="mt-2 text-base text-slate-600">
|
||||
{standard.summary}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex items-center justify-between gap-4">
|
||||
<div className="space-y-2">
|
||||
<p className="max-w-md text-sm leading-6 text-slate-600">{standard.audience}</p>
|
||||
<p className="text-sm text-slate-500">
|
||||
Submission points: {standard.submissionPoints.length}
|
||||
</p>
|
||||
{getComparisonTarget(standard) ? (
|
||||
<Link
|
||||
href={`${getComparisonTarget(standard)?.href}#comparison`}
|
||||
className="inline-flex items-center text-sm font-medium text-slate-600 underline-offset-4 hover:text-slate-950 hover:underline"
|
||||
>
|
||||
Compare with {getComparisonTarget(standard)?.name}
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
<Link
|
||||
href={standard.href}
|
||||
className="shrink-0 text-sm font-semibold text-slate-950 underline-offset-4 hover:underline"
|
||||
>
|
||||
View standard
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../../lib/ui-server';
|
||||
|
||||
export default function PrivacyPage(): JSX.Element {
|
||||
return (
|
||||
@@ -91,4 +91,3 @@ export default function PrivacyPage(): JSX.Element {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { MarketingLandingPage } from '../../../components/marketing/landing-page';
|
||||
import { getRelatedStandards, getSortedStandards, getStandardBySlug } from '../../../content/standards';
|
||||
import { StandardRegistryDetails } from '../../../components/marketing/standard-registry-details';
|
||||
import { RelatedStandards } from '../../../components/marketing/related-standards';
|
||||
|
||||
export function generateStaticParams(): { slug: string }[] {
|
||||
return getSortedStandards().map((standard) => ({ slug: standard.slug }));
|
||||
}
|
||||
|
||||
export function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string };
|
||||
}): Metadata {
|
||||
const standard = getStandardBySlug(params.slug);
|
||||
|
||||
if (!standard) {
|
||||
return {
|
||||
title: 'Standard Not Found | The Order',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: standard.seo.title,
|
||||
description: standard.seo.description,
|
||||
keywords: standard.seo.keywords,
|
||||
openGraph: {
|
||||
title: standard.seo.title,
|
||||
description: standard.seo.description,
|
||||
url: standard.href,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function StandardDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string };
|
||||
}): JSX.Element {
|
||||
const standard = getStandardBySlug(params.slug);
|
||||
|
||||
if (!standard) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const relatedStandards = getRelatedStandards(standard);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StandardRegistryDetails standard={standard} />
|
||||
<RelatedStandards standards={relatedStandards} />
|
||||
<MarketingLandingPage {...standard.landingPage} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { StandardsAdmin } from '../../../components/marketing/standards-admin';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Standards Admin | The Order',
|
||||
description: 'Authoring and validation interface for standards registry entries.',
|
||||
};
|
||||
|
||||
export default function StandardsAdminPage(): JSX.Element {
|
||||
return <StandardsAdmin />;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { StandardsBrowser } from '../../components/marketing/standards-browser';
|
||||
import { getSortedStandards } from '../../content/standards';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Standards | The Order',
|
||||
description:
|
||||
'Explore standards and frameworks for compliant tokenization, regulated digital asset issuance, and real-world asset programs.',
|
||||
};
|
||||
|
||||
export default function StandardsPage(): JSX.Element {
|
||||
return <StandardsBrowser standards={getSortedStandards()} />;
|
||||
}
|
||||
@@ -1,11 +1,32 @@
|
||||
'use client';
|
||||
|
||||
import { Suspense } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Label } from '@the-order/ui';
|
||||
import { getApiClient } from '@the-order/api-client';
|
||||
|
||||
export default function StatusPage(): JSX.Element {
|
||||
function StatusCardShell({ children }: { children: React.ReactNode }): JSX.Element {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-12">
|
||||
<div className="container mx-auto max-w-2xl px-4">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusLoadingState(): JSX.Element {
|
||||
return (
|
||||
<StatusCardShell>
|
||||
<Card>
|
||||
<CardContent className="py-12 text-center">
|
||||
<p className="text-gray-600">Loading application status...</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</StatusCardShell>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusPageContent(): JSX.Element {
|
||||
const searchParams = useSearchParams();
|
||||
const applicationId = searchParams.get('id');
|
||||
const apiClient = getApiClient();
|
||||
@@ -21,63 +42,47 @@ export default function StatusPage(): JSX.Element {
|
||||
|
||||
if (!applicationId) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-12">
|
||||
<div className="container mx-auto px-4 max-w-2xl">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Application Status</CardTitle>
|
||||
<CardDescription>Please provide an application ID to check status</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<StatusCardShell>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Application Status</CardTitle>
|
||||
<CardDescription>Please provide an application ID to check status</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</StatusCardShell>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-12">
|
||||
<div className="container mx-auto px-4 max-w-2xl">
|
||||
<Card>
|
||||
<CardContent className="py-12 text-center">
|
||||
<p className="text-gray-600">Loading application status...</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <StatusLoadingState />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-12">
|
||||
<div className="container mx-auto px-4 max-w-2xl">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Error</CardTitle>
|
||||
<CardDescription>Failed to load application status</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-red-600">{error instanceof Error ? error.message : 'Unknown error'}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<StatusCardShell>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Error</CardTitle>
|
||||
<CardDescription>Failed to load application status</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-red-600">{error instanceof Error ? error.message : 'Unknown error'}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</StatusCardShell>
|
||||
);
|
||||
}
|
||||
|
||||
if (!application) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-12">
|
||||
<div className="container mx-auto px-4 max-w-2xl">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Application Not Found</CardTitle>
|
||||
<CardDescription>No application found with the provided ID</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<StatusCardShell>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Application Not Found</CardTitle>
|
||||
<CardDescription>No application found with the provided ID</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</StatusCardShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -97,8 +102,7 @@ export default function StatusPage(): JSX.Element {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-12">
|
||||
<div className="container mx-auto px-4 max-w-2xl">
|
||||
<StatusCardShell>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Application Status</CardTitle>
|
||||
@@ -163,9 +167,15 @@ export default function StatusPage(): JSX.Element {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</StatusCardShell>
|
||||
);
|
||||
}
|
||||
|
||||
export default function StatusPage(): JSX.Element {
|
||||
return (
|
||||
<Suspense fallback={<StatusLoadingState />}>
|
||||
<StatusPageContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../../lib/ui-server';
|
||||
|
||||
export default function TermsPage(): JSX.Element {
|
||||
return (
|
||||
@@ -16,7 +16,7 @@ export default function TermsPage(): JSX.Element {
|
||||
</CardHeader>
|
||||
<CardContent className="text-gray-700">
|
||||
<p>
|
||||
By accessing and using The Order's services, you accept and agree to be bound by
|
||||
By accessing and using The Order's services, you accept and agree to be bound by
|
||||
these Terms of Service. If you do not agree, you may not use our services.
|
||||
</p>
|
||||
</CardContent>
|
||||
@@ -99,4 +99,3 @@ export default function TermsPage(): JSX.Element {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,19 @@ import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useAuth } from '../lib/auth';
|
||||
|
||||
export function AuthGuard({ children }: { children: React.ReactNode }): JSX.Element | null {
|
||||
const { isAuthenticated, isLoading } = useAuth();
|
||||
export function AuthGuard({
|
||||
children,
|
||||
requiredRoles,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
requiredRoles?: string[];
|
||||
}): JSX.Element | null {
|
||||
const { isAuthenticated, isLoading, user } = useAuth();
|
||||
const router = useRouter();
|
||||
const hasRequiredRole =
|
||||
!requiredRoles || requiredRoles.length === 0
|
||||
? true
|
||||
: requiredRoles.some((role) => user?.roles?.includes(role));
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && !isAuthenticated) {
|
||||
@@ -14,6 +24,12 @@ export function AuthGuard({ children }: { children: React.ReactNode }): JSX.Elem
|
||||
}
|
||||
}, [isAuthenticated, isLoading, router]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && isAuthenticated && !hasRequiredRole) {
|
||||
router.push('/');
|
||||
}
|
||||
}, [hasRequiredRole, isAuthenticated, isLoading, router]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
@@ -26,6 +42,9 @@ export function AuthGuard({ children }: { children: React.ReactNode }): JSX.Elem
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!hasRequiredRole) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,11 @@ export function Footer(): JSX.Element {
|
||||
About
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/standards" className="text-gray-600 hover:text-gray-900">
|
||||
Standards
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/docs" className="text-gray-600 hover:text-gray-900">
|
||||
Documentation
|
||||
@@ -74,4 +79,3 @@ export function Footer(): JSX.Element {
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useAuth } from '../lib/auth';
|
||||
export function Header(): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { isAuthenticated, user, logout } = useAuth();
|
||||
const canAccessStandardsAdmin = !!user?.roles?.some((role) => role === 'admin' || role === 'editor');
|
||||
|
||||
const handleLogout = (): void => {
|
||||
logout();
|
||||
@@ -25,6 +26,9 @@ export function Header(): JSX.Element {
|
||||
<Link href="/apply" className="text-gray-600 hover:text-gray-900">
|
||||
Apply
|
||||
</Link>
|
||||
<Link href="/standards" className="text-gray-600 hover:text-gray-900">
|
||||
Standards
|
||||
</Link>
|
||||
<Link href="/status" className="text-gray-600 hover:text-gray-900">
|
||||
Status
|
||||
</Link>
|
||||
@@ -36,6 +40,11 @@ export function Header(): JSX.Element {
|
||||
</Link>
|
||||
{isAuthenticated ? (
|
||||
<div className="flex items-center gap-4">
|
||||
{canAccessStandardsAdmin ? (
|
||||
<Link href="/standards/admin" className="text-gray-600 hover:text-gray-900">
|
||||
Registry Admin
|
||||
</Link>
|
||||
) : null}
|
||||
<span className="text-sm text-gray-600">{user?.email || user?.name}</span>
|
||||
<Button variant="outline" size="sm" onClick={handleLogout}>
|
||||
Logout
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
Badge,
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
cn,
|
||||
} from '../../lib/ui-server';
|
||||
|
||||
export interface MarketingLink {
|
||||
label: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface MarketingHero {
|
||||
eyebrow?: string;
|
||||
headline: string;
|
||||
subheadline: string;
|
||||
supportingCopy?: string;
|
||||
primaryCta?: MarketingLink;
|
||||
secondaryCta?: MarketingLink;
|
||||
tertiaryLink?: MarketingLink;
|
||||
}
|
||||
|
||||
export interface MarketingTrustItem {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface MarketingTrustStrip {
|
||||
label?: string;
|
||||
items: MarketingTrustItem[];
|
||||
}
|
||||
|
||||
export interface MarketingCardItem {
|
||||
title: string;
|
||||
copy: string;
|
||||
}
|
||||
|
||||
export interface MarketingValueProps {
|
||||
headline: string;
|
||||
intro?: string;
|
||||
cards: MarketingCardItem[];
|
||||
}
|
||||
|
||||
export interface MarketingUseCases {
|
||||
headline: string;
|
||||
intro?: string;
|
||||
items: string[];
|
||||
supportingLine?: string;
|
||||
}
|
||||
|
||||
export interface MarketingComparisonRow {
|
||||
category: string;
|
||||
left: string;
|
||||
right: string;
|
||||
}
|
||||
|
||||
export interface MarketingComparison {
|
||||
headline: string;
|
||||
intro?: string;
|
||||
columns: [string, string, string];
|
||||
rows: MarketingComparisonRow[];
|
||||
secondaryNote?: string;
|
||||
cta?: MarketingLink;
|
||||
}
|
||||
|
||||
export interface MarketingProofPoints {
|
||||
headline: string;
|
||||
intro?: string;
|
||||
items: string[];
|
||||
}
|
||||
|
||||
export interface MarketingFaqItem {
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
export interface MarketingFaq {
|
||||
headline: string;
|
||||
items: MarketingFaqItem[];
|
||||
}
|
||||
|
||||
export interface MarketingFooterCta {
|
||||
headline: string;
|
||||
copy: string;
|
||||
primaryCta?: MarketingLink;
|
||||
secondaryCta?: MarketingLink;
|
||||
}
|
||||
|
||||
export interface MarketingLandingPageProps {
|
||||
hero: MarketingHero;
|
||||
trustStrip?: MarketingTrustStrip;
|
||||
valueProps?: MarketingValueProps;
|
||||
useCases?: MarketingUseCases;
|
||||
proofPoints?: MarketingProofPoints;
|
||||
comparison?: MarketingComparison;
|
||||
faq?: MarketingFaq;
|
||||
footerCta?: MarketingFooterCta;
|
||||
}
|
||||
|
||||
function SectionShell({
|
||||
id,
|
||||
className,
|
||||
children,
|
||||
}: {
|
||||
id?: string;
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<section id={id} className={cn('px-4 py-16 sm:px-6 lg:px-8', className)}>
|
||||
<div className="mx-auto w-full max-w-6xl">{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionIntro({
|
||||
headline,
|
||||
intro,
|
||||
align = 'left',
|
||||
tone = 'default',
|
||||
}: {
|
||||
headline: string;
|
||||
intro?: string;
|
||||
align?: 'left' | 'center';
|
||||
tone?: 'default' | 'inverse';
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div className={cn('max-w-3xl', align === 'center' && 'mx-auto text-center')}>
|
||||
<h2
|
||||
className={cn(
|
||||
'text-balance text-3xl font-semibold tracking-tight sm:text-4xl',
|
||||
tone === 'default' ? 'text-slate-950' : 'text-white'
|
||||
)}
|
||||
>
|
||||
{headline}
|
||||
</h2>
|
||||
{intro ? (
|
||||
<p
|
||||
className={cn(
|
||||
'mt-4 text-pretty text-base leading-7 sm:text-lg',
|
||||
tone === 'default' ? 'text-slate-600' : 'text-slate-300'
|
||||
)}
|
||||
>
|
||||
{intro}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CtaLink({
|
||||
href,
|
||||
label,
|
||||
variant = 'primary',
|
||||
}: MarketingLink & {
|
||||
variant?: 'primary' | 'secondary' | 'link';
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<Link
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center rounded-full text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',
|
||||
variant === 'primary' &&
|
||||
'bg-slate-950 px-5 py-3 text-white hover:bg-slate-800',
|
||||
variant === 'secondary' &&
|
||||
'border border-slate-300 bg-white px-5 py-3 text-slate-900 hover:border-slate-400 hover:bg-slate-50',
|
||||
variant === 'link' &&
|
||||
'px-0 py-0 text-slate-700 underline-offset-4 hover:text-slate-950 hover:underline'
|
||||
)}
|
||||
href={href}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export function MarketingHeroSection({
|
||||
eyebrow,
|
||||
headline,
|
||||
subheadline,
|
||||
supportingCopy,
|
||||
primaryCta,
|
||||
secondaryCta,
|
||||
tertiaryLink,
|
||||
}: MarketingHero): JSX.Element {
|
||||
return (
|
||||
<SectionShell className="overflow-hidden pb-12 pt-20 sm:pt-24">
|
||||
<div className="relative rounded-[2rem] border border-slate-200 bg-[radial-gradient(circle_at_top_left,_rgba(217,119,6,0.12),_transparent_30%),radial-gradient(circle_at_bottom_right,_rgba(15,23,42,0.08),_transparent_35%),linear-gradient(180deg,_#fff,_#f8fafc)] px-6 py-12 shadow-sm sm:px-10 sm:py-16">
|
||||
<div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-amber-400/60 to-transparent" />
|
||||
<div className="grid gap-10 lg:grid-cols-[minmax(0,1.2fr)_minmax(280px,0.8fr)] lg:items-end">
|
||||
<div className="max-w-3xl">
|
||||
{eyebrow ? (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="mb-6 border-amber-300 bg-amber-50 px-4 py-1 text-[11px] uppercase tracking-[0.24em] text-amber-900"
|
||||
>
|
||||
{eyebrow}
|
||||
</Badge>
|
||||
) : null}
|
||||
<h1 className="text-balance text-4xl font-semibold tracking-tight text-slate-950 sm:text-5xl lg:text-6xl">
|
||||
{headline}
|
||||
</h1>
|
||||
<p className="mt-6 max-w-2xl text-pretty text-lg leading-8 text-slate-700 sm:text-xl">
|
||||
{subheadline}
|
||||
</p>
|
||||
{supportingCopy ? (
|
||||
<p className="mt-5 max-w-2xl text-pretty text-base leading-7 text-slate-600">
|
||||
{supportingCopy}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="mt-8 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center">
|
||||
{primaryCta ? <CtaLink {...primaryCta} variant="primary" /> : null}
|
||||
{secondaryCta ? <CtaLink {...secondaryCta} variant="secondary" /> : null}
|
||||
{tertiaryLink ? <CtaLink {...tertiaryLink} variant="link" /> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="border-slate-200 bg-white/90 shadow-lg shadow-slate-200/70">
|
||||
<CardHeader>
|
||||
<CardDescription className="text-xs uppercase tracking-[0.22em] text-slate-500">
|
||||
Operational focus
|
||||
</CardDescription>
|
||||
<CardTitle className="text-2xl text-slate-950">Designed for regulated issuance</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 text-sm leading-6 text-slate-600">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="rounded-2xl bg-slate-50 p-4">
|
||||
<div className="text-2xl font-semibold text-slate-950">120+</div>
|
||||
<div className="mt-1 text-xs uppercase tracking-[0.18em] text-slate-500">Validated use cases</div>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-slate-50 p-4">
|
||||
<div className="text-2xl font-semibold text-slate-950">100+</div>
|
||||
<div className="mt-1 text-xs uppercase tracking-[0.18em] text-slate-500">Built-in functions</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
Combine identity-aware transfer rules, lifecycle automation, and ERC-20 compatibility in one
|
||||
implementation-ready standard.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</SectionShell>
|
||||
);
|
||||
}
|
||||
|
||||
export function MarketingTrustStripSection({ label, items }: MarketingTrustStrip): JSX.Element {
|
||||
return (
|
||||
<SectionShell className="py-8">
|
||||
<div className="rounded-[1.5rem] border border-slate-200 bg-white px-6 py-6 shadow-sm">
|
||||
<div className="flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between">
|
||||
{label ? (
|
||||
<div className="min-w-[200px]">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.22em] text-slate-500">{label}</p>
|
||||
</div>
|
||||
) : null}
|
||||
<dl className="grid flex-1 gap-4 sm:grid-cols-2 xl:grid-cols-5">
|
||||
{items.map((item) => (
|
||||
<div key={item.label} className="rounded-2xl bg-slate-50 px-4 py-4">
|
||||
<dt className="text-xs uppercase tracking-[0.18em] text-slate-500">{item.label}</dt>
|
||||
<dd className="mt-2 text-sm font-medium leading-6 text-slate-900">{item.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</SectionShell>
|
||||
);
|
||||
}
|
||||
|
||||
export function MarketingValueGridSection({ headline, intro, cards }: MarketingValueProps): JSX.Element {
|
||||
return (
|
||||
<SectionShell id="capabilities">
|
||||
<SectionIntro headline={headline} intro={intro} />
|
||||
<div className="mt-10 grid gap-6 md:grid-cols-2 xl:grid-cols-3">
|
||||
{cards.map((card) => (
|
||||
<Card key={card.title} className="border-slate-200 bg-white shadow-sm transition-transform hover:-translate-y-0.5">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl text-slate-950">{card.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm leading-7 text-slate-600">{card.copy}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</SectionShell>
|
||||
);
|
||||
}
|
||||
|
||||
export function MarketingUseCasesSection({ headline, intro, items, supportingLine }: MarketingUseCases): JSX.Element {
|
||||
return (
|
||||
<SectionShell id="use-cases" className="bg-slate-950">
|
||||
<SectionIntro headline={headline} intro={intro} tone="inverse" />
|
||||
<div className="mt-10 flex flex-wrap gap-3">
|
||||
{items.map((item) => (
|
||||
<span
|
||||
key={item}
|
||||
className="rounded-full border border-white/15 bg-white/5 px-4 py-2 text-sm font-medium text-slate-100 backdrop-blur"
|
||||
>
|
||||
{item}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{supportingLine ? <p className="mt-6 max-w-3xl text-sm leading-7 text-slate-300">{supportingLine}</p> : null}
|
||||
</SectionShell>
|
||||
);
|
||||
}
|
||||
|
||||
export function MarketingProofPointsSection({ headline, intro, items }: MarketingProofPoints): JSX.Element {
|
||||
return (
|
||||
<SectionShell id="proof-points">
|
||||
<div className="grid gap-8 lg:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)] lg:items-start">
|
||||
<SectionIntro headline={headline} intro={intro} />
|
||||
<div className="grid gap-4">
|
||||
{items.map((item, index) => (
|
||||
<Card key={item} className="border-slate-200 bg-slate-50 shadow-none">
|
||||
<CardContent className="flex gap-4 p-6">
|
||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-white text-sm font-semibold text-slate-950 shadow-sm">
|
||||
{index + 1}
|
||||
</div>
|
||||
<p className="text-sm leading-7 text-slate-700">{item}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</SectionShell>
|
||||
);
|
||||
}
|
||||
|
||||
export function MarketingComparisonSection({
|
||||
headline,
|
||||
intro,
|
||||
columns,
|
||||
rows,
|
||||
secondaryNote,
|
||||
cta,
|
||||
}: MarketingComparison): JSX.Element {
|
||||
return (
|
||||
<SectionShell id="comparison">
|
||||
<SectionIntro headline={headline} intro={intro} />
|
||||
<div className="mt-10 overflow-hidden rounded-[1.5rem] border border-slate-200 bg-white shadow-sm">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-slate-50 hover:bg-slate-50">
|
||||
<TableHead>{columns[0]}</TableHead>
|
||||
<TableHead>{columns[1]}</TableHead>
|
||||
<TableHead>{columns[2]}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{rows.map((row) => (
|
||||
<TableRow key={row.category}>
|
||||
<TableCell className="font-medium text-slate-950">{row.category}</TableCell>
|
||||
<TableCell className="text-slate-700">{row.left}</TableCell>
|
||||
<TableCell className="text-slate-600">{row.right}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
{secondaryNote ? <p className="mt-5 max-w-4xl text-sm leading-7 text-slate-600">{secondaryNote}</p> : null}
|
||||
{cta ? (
|
||||
<div className="mt-8">
|
||||
<CtaLink {...cta} variant="secondary" />
|
||||
</div>
|
||||
) : null}
|
||||
</SectionShell>
|
||||
);
|
||||
}
|
||||
|
||||
export function MarketingFaqSection({ headline, items }: MarketingFaq): JSX.Element {
|
||||
return (
|
||||
<SectionShell id="faq" className="bg-slate-50">
|
||||
<SectionIntro headline={headline} align="center" />
|
||||
<div className="mx-auto mt-10 grid max-w-4xl gap-4">
|
||||
{items.map((item) => (
|
||||
<details
|
||||
key={item.question}
|
||||
className="group rounded-[1.25rem] border border-slate-200 bg-white px-6 py-5 shadow-sm open:border-slate-300"
|
||||
>
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-4 text-left text-base font-semibold text-slate-950">
|
||||
<span>{item.question}</span>
|
||||
<span className="text-slate-400 transition group-open:rotate-45">+</span>
|
||||
</summary>
|
||||
<p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600">{item.answer}</p>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</SectionShell>
|
||||
);
|
||||
}
|
||||
|
||||
export function MarketingFooterCtaSection({
|
||||
headline,
|
||||
copy,
|
||||
primaryCta,
|
||||
secondaryCta,
|
||||
}: MarketingFooterCta): JSX.Element {
|
||||
return (
|
||||
<SectionShell className="pt-10">
|
||||
<div className="rounded-[2rem] border border-slate-200 bg-slate-950 px-6 py-12 sm:px-10 sm:py-16">
|
||||
<div className="mx-auto max-w-3xl text-center">
|
||||
<h2 className="text-balance text-3xl font-semibold tracking-tight text-white sm:text-4xl">{headline}</h2>
|
||||
<p className="mt-4 text-pretty text-base leading-7 text-slate-300 sm:text-lg">{copy}</p>
|
||||
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
|
||||
{primaryCta ? <CtaLink {...primaryCta} variant="primary" /> : null}
|
||||
{secondaryCta ? <CtaLink {...secondaryCta} variant="secondary" /> : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SectionShell>
|
||||
);
|
||||
}
|
||||
|
||||
export function MarketingLandingPage({
|
||||
hero,
|
||||
trustStrip,
|
||||
valueProps,
|
||||
useCases,
|
||||
proofPoints,
|
||||
comparison,
|
||||
faq,
|
||||
footerCta,
|
||||
}: MarketingLandingPageProps): JSX.Element {
|
||||
return (
|
||||
<div className="min-h-screen bg-[linear-gradient(180deg,#fff_0%,#f8fafc_45%,#fff_100%)]">
|
||||
<MarketingHeroSection {...hero} />
|
||||
{trustStrip ? <MarketingTrustStripSection {...trustStrip} /> : null}
|
||||
{valueProps ? <MarketingValueGridSection {...valueProps} /> : null}
|
||||
{useCases ? <MarketingUseCasesSection {...useCases} /> : null}
|
||||
{proofPoints ? <MarketingProofPointsSection {...proofPoints} /> : null}
|
||||
{comparison ? <MarketingComparisonSection {...comparison} /> : null}
|
||||
{faq ? <MarketingFaqSection {...faq} /> : null}
|
||||
{footerCta ? <MarketingFooterCtaSection {...footerCta} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import Link from 'next/link';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../lib/ui-server';
|
||||
import type { StandardRegistryEntry } from '../../content/standards';
|
||||
import { StandardIcon } from './standard-icon';
|
||||
|
||||
export function RelatedStandards({
|
||||
standards,
|
||||
}: {
|
||||
standards: StandardRegistryEntry[];
|
||||
}): JSX.Element | null {
|
||||
if (standards.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="px-4 py-10 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<div className="mb-8 max-w-3xl">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.22em] text-slate-500">Related standards</p>
|
||||
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-slate-950">
|
||||
Compare adjacent standards in the registry
|
||||
</h2>
|
||||
<p className="mt-4 text-base leading-7 text-slate-600">
|
||||
These standards are related by explicit comparison targets, shared categories, or overlapping standard families.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
{standards.map((standard) => (
|
||||
<Card key={standard.slug} className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-3 text-2xl text-slate-950">
|
||||
<span className="inline-flex h-10 w-10 items-center justify-center rounded-full bg-slate-100 text-slate-700">
|
||||
<StandardIcon name={standard.icon} />
|
||||
</span>
|
||||
<span>{standard.name}</span>
|
||||
</CardTitle>
|
||||
<CardDescription className="text-base text-slate-600">{standard.shortName ?? standard.category}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-sm leading-7 text-slate-600">{standard.summary}</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link
|
||||
href={standard.href}
|
||||
className="inline-flex items-center text-sm font-semibold text-slate-950 underline-offset-4 hover:underline"
|
||||
>
|
||||
View standard
|
||||
</Link>
|
||||
<Link
|
||||
href={`${standard.href}#comparison`}
|
||||
className="inline-flex items-center text-sm font-medium text-slate-600 underline-offset-4 hover:text-slate-950 hover:underline"
|
||||
>
|
||||
Jump to comparison
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Coins, FileStack, Network, Scale, ShieldCheck } from 'lucide-react';
|
||||
import type { StandardIconName } from '../../content/standards/schema';
|
||||
|
||||
const iconClassName = 'h-5 w-5';
|
||||
|
||||
export function StandardIcon({
|
||||
name,
|
||||
className = iconClassName,
|
||||
}: {
|
||||
name: StandardIconName;
|
||||
className?: string;
|
||||
}): JSX.Element {
|
||||
switch (name) {
|
||||
case 'shield':
|
||||
return <ShieldCheck className={className} aria-hidden="true" />;
|
||||
case 'coins':
|
||||
return <Coins className={className} aria-hidden="true" />;
|
||||
case 'scale':
|
||||
return <Scale className={className} aria-hidden="true" />;
|
||||
case 'network':
|
||||
return <Network className={className} aria-hidden="true" />;
|
||||
case 'file-stack':
|
||||
return <FileStack className={className} aria-hidden="true" />;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
import Link from 'next/link';
|
||||
import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../lib/ui-server';
|
||||
import type { StandardRegistryEntry } from '../../content/standards';
|
||||
import { StandardIcon } from './standard-icon';
|
||||
|
||||
function ExternalLinkList({
|
||||
title,
|
||||
links,
|
||||
}: {
|
||||
title: string;
|
||||
links: StandardRegistryEntry['repos'] | StandardRegistryEntry['submissionPoints'] | StandardRegistryEntry['docs'];
|
||||
}): JSX.Element | null {
|
||||
if (!links || links.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl text-slate-950">{title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{links.map((link) => (
|
||||
<a
|
||||
key={`${link.kind}-${link.href}`}
|
||||
href={link.href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="block rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-700 transition hover:border-slate-300 hover:bg-slate-100"
|
||||
>
|
||||
<span className="font-semibold text-slate-950">{link.label}</span>
|
||||
<span className="mt-1 block break-all text-xs text-slate-500">{link.href}</span>
|
||||
</a>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function StandardRegistryDetails({
|
||||
standard,
|
||||
}: {
|
||||
standard: StandardRegistryEntry;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<section className="px-4 py-10 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<div className="mb-8 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div className="max-w-3xl">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.22em] text-slate-500">Registry details</p>
|
||||
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-slate-950">
|
||||
Canonical metadata, compliance posture, and submission points
|
||||
</h2>
|
||||
<p className="mt-4 text-base leading-7 text-slate-600">
|
||||
This registry entry is designed to be implementation-friendly: canonical spec links,
|
||||
repository references, and the official submission points are captured directly in content.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/standards" className="text-sm font-semibold text-slate-950 underline-offset-4 hover:underline">
|
||||
Back to standards
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]">
|
||||
<div className="grid gap-6">
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Badge variant="outline" className="border-slate-300 bg-slate-50 text-slate-700">
|
||||
{standard.status}
|
||||
</Badge>
|
||||
<Badge
|
||||
className={standard.compliance.native ? 'bg-emerald-600 text-white hover:bg-emerald-600' : 'bg-slate-800 text-white hover:bg-slate-800'}
|
||||
>
|
||||
{standard.compliance.label}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className="mt-2 flex items-center gap-3 text-2xl text-slate-950">
|
||||
<span className="inline-flex h-10 w-10 items-center justify-center rounded-full bg-slate-100 text-sm font-semibold text-slate-700">
|
||||
<StandardIcon name={standard.icon} />
|
||||
</span>
|
||||
<span>{standard.name}</span>
|
||||
</CardTitle>
|
||||
<CardDescription className="text-base text-slate-600">{standard.summary}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="rounded-2xl bg-slate-50 p-4">
|
||||
<div className="text-xs uppercase tracking-[0.18em] text-slate-500">Category</div>
|
||||
<p className="mt-2 text-sm leading-6 text-slate-800">{standard.category}</p>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-slate-50 p-4">
|
||||
<div className="text-xs uppercase tracking-[0.18em] text-slate-500">Audience</div>
|
||||
<p className="mt-2 text-sm leading-6 text-slate-800">{standard.audience}</p>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-slate-50 p-4">
|
||||
<div className="text-xs uppercase tracking-[0.18em] text-slate-500">Canonical standard</div>
|
||||
<a
|
||||
href={standard.specification.href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mt-2 inline-flex text-sm font-semibold text-slate-950 underline-offset-4 hover:underline"
|
||||
>
|
||||
{standard.specification.label}
|
||||
</a>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-slate-50 p-4">
|
||||
<div className="text-xs uppercase tracking-[0.18em] text-slate-500">Submission coverage</div>
|
||||
<p className="mt-2 text-sm leading-6 text-slate-800">
|
||||
{standard.submissionPoints.length} submission point{standard.submissionPoints.length === 1 ? '' : 's'} tracked
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl text-slate-950">Compliance profile</CardTitle>
|
||||
<CardDescription className="text-base text-slate-600">
|
||||
{standard.compliance.summary}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-3">
|
||||
{standard.compliance.enforcement.map((item) => (
|
||||
<li key={item} className="rounded-2xl bg-slate-50 px-4 py-3 text-sm leading-6 text-slate-700">
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl text-slate-950">Governance and maintainers</CardTitle>
|
||||
<CardDescription className="text-base text-slate-600">
|
||||
{standard.governanceBody}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{standard.maintainers.map((maintainer) => (
|
||||
<div key={`${maintainer.name}-${maintainer.role}`} className="rounded-2xl bg-slate-50 px-4 py-4">
|
||||
<div className="text-sm font-semibold text-slate-950">{maintainer.name}</div>
|
||||
<div className="mt-1 text-sm text-slate-600">{maintainer.role}</div>
|
||||
{maintainer.href ? (
|
||||
<a
|
||||
href={maintainer.href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mt-2 inline-flex text-sm font-medium text-slate-700 underline-offset-4 hover:text-slate-950 hover:underline"
|
||||
>
|
||||
View maintainer
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl text-slate-950">Status history</CardTitle>
|
||||
<CardDescription className="text-base text-slate-600">
|
||||
Important milestones that help explain how the standard evolved and where it sits today.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{standard.statusHistory.map((event) => (
|
||||
<div key={`${event.date}-${event.title}`} className="rounded-2xl bg-slate-50 px-4 py-4">
|
||||
<div className="text-xs uppercase tracking-[0.18em] text-slate-500">{event.date}</div>
|
||||
<div className="mt-2 text-sm font-semibold text-slate-950">{event.title}</div>
|
||||
<div className="mt-1 text-sm leading-6 text-slate-600">{event.summary}</div>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6">
|
||||
<ExternalLinkList title="Canonical repositories" links={standard.repos} />
|
||||
<ExternalLinkList title="Submission points" links={standard.submissionPoints} />
|
||||
<ExternalLinkList title="Documentation" links={standard.docs} />
|
||||
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl text-slate-950">Implementation examples</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{standard.implementationExamples.map((example) => (
|
||||
<a
|
||||
key={example.href}
|
||||
href={example.href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="block rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 transition hover:border-slate-300 hover:bg-slate-100"
|
||||
>
|
||||
<div className="text-sm font-semibold text-slate-950">{example.label}</div>
|
||||
<div className="mt-1 text-sm leading-6 text-slate-600">{example.summary}</div>
|
||||
</a>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl text-slate-950">Submission checklist</CardTitle>
|
||||
<CardDescription className="text-base text-slate-600">
|
||||
Minimum elements tracked in the registry to keep the entry complete and submission-ready.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{standard.submissionChecklist.map((item) => (
|
||||
<div key={item.label} className="rounded-2xl bg-slate-50 px-4 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge
|
||||
className={
|
||||
item.required
|
||||
? 'bg-amber-600 text-white hover:bg-amber-600'
|
||||
: 'bg-slate-700 text-white hover:bg-slate-700'
|
||||
}
|
||||
>
|
||||
{item.required ? 'Required' : 'Optional'}
|
||||
</Badge>
|
||||
<div className="text-sm font-semibold text-slate-950">{item.label}</div>
|
||||
</div>
|
||||
<div className="mt-2 text-sm leading-6 text-slate-600">{item.summary}</div>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl text-slate-950">Ecosystem support</CardTitle>
|
||||
<CardDescription className="text-base text-slate-600">
|
||||
Snapshot of how the standard tends to perform across core infrastructure and operating contexts.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{standard.ecosystemSupport.map((item) => (
|
||||
<div key={item.area} className="rounded-2xl bg-slate-50 px-4 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-sm font-semibold text-slate-950">{item.area}</div>
|
||||
<Badge
|
||||
className={
|
||||
item.status === 'Strong'
|
||||
? 'bg-emerald-600 text-white hover:bg-emerald-600'
|
||||
: item.status === 'Moderate'
|
||||
? 'bg-sky-700 text-white hover:bg-sky-700'
|
||||
: 'bg-violet-700 text-white hover:bg-violet-700'
|
||||
}
|
||||
>
|
||||
{item.status}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-2 text-sm leading-6 text-slate-600">{item.summary}</div>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
|
||||
import { standardsRegistry, type StandardRegistryEntry } from '../../content/standards';
|
||||
import { standardRegistryEntrySchema } from '../../content/standards/schema';
|
||||
import { standardsRegistryTemplate } from '../../content/standards/template';
|
||||
import { AuthGuard } from '../AuthGuard';
|
||||
|
||||
function prettyPrint(value: unknown): string {
|
||||
return `${JSON.stringify(value, null, 2)}\n`;
|
||||
}
|
||||
|
||||
const DRAFT_STORAGE_KEY = 'standards-admin-draft';
|
||||
const SELECTION_STORAGE_KEY = 'standards-admin-selection';
|
||||
|
||||
export function StandardsAdmin(): JSX.Element {
|
||||
const [selection, setSelection] = useState<string>('template');
|
||||
const [draft, setDraft] = useState<string>(prettyPrint(standardsRegistryTemplate));
|
||||
const [copyState, setCopyState] = useState<'idle' | 'copied'>('idle');
|
||||
|
||||
const sourceEntries: Record<string, StandardRegistryEntry | typeof standardsRegistryTemplate> = useMemo(() => {
|
||||
const entries = Object.fromEntries(standardsRegistry.map((standard) => [standard.slug, standard]));
|
||||
return {
|
||||
template: standardsRegistryTemplate,
|
||||
...entries,
|
||||
};
|
||||
}, []);
|
||||
|
||||
const validation = useMemo(() => {
|
||||
try {
|
||||
const parsedJson = JSON.parse(draft);
|
||||
const parsedStandard = standardRegistryEntrySchema.safeParse(parsedJson);
|
||||
|
||||
if (!parsedStandard.success) {
|
||||
return {
|
||||
valid: false,
|
||||
message: parsedStandard.error.issues[0]?.message ?? 'Validation failed.',
|
||||
parsed: null as StandardRegistryEntry | null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
message: 'Entry is valid against the standards registry schema.',
|
||||
parsed: parsedStandard.data as StandardRegistryEntry,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
valid: false,
|
||||
message: error instanceof Error ? error.message : 'Invalid JSON.',
|
||||
parsed: null as StandardRegistryEntry | null,
|
||||
};
|
||||
}
|
||||
}, [draft]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
const storedSelection = localStorage.getItem(SELECTION_STORAGE_KEY);
|
||||
const storedDraft = localStorage.getItem(DRAFT_STORAGE_KEY);
|
||||
|
||||
if (storedSelection && sourceEntries[storedSelection]) {
|
||||
setSelection(storedSelection);
|
||||
}
|
||||
|
||||
if (storedDraft) {
|
||||
setDraft(storedDraft);
|
||||
} else if (storedSelection && sourceEntries[storedSelection]) {
|
||||
setDraft(prettyPrint(sourceEntries[storedSelection]));
|
||||
}
|
||||
}, [sourceEntries]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
localStorage.setItem(DRAFT_STORAGE_KEY, draft);
|
||||
}, [draft]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
localStorage.setItem(SELECTION_STORAGE_KEY, selection);
|
||||
}, [selection]);
|
||||
|
||||
const handleCopy = async (): Promise<void> => {
|
||||
await navigator.clipboard.writeText(draft);
|
||||
setCopyState('copied');
|
||||
window.setTimeout(() => setCopyState('idle'), 1500);
|
||||
};
|
||||
|
||||
const handleExport = (): void => {
|
||||
const blob = new Blob([draft], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = `${selection || 'standard-entry'}.json`;
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const handleReset = (): void => {
|
||||
const nextDraft = prettyPrint(sourceEntries[selection] ?? standardsRegistryTemplate);
|
||||
setDraft(nextDraft);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthGuard requiredRoles={['admin', 'editor']}>
|
||||
<div className="min-h-screen bg-[linear-gradient(180deg,#fff_0%,#f8fafc_45%,#fff_100%)] px-4 py-16 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-6xl space-y-8">
|
||||
<div className="max-w-3xl">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="mb-6 border-amber-300 bg-amber-50 px-4 py-1 text-[11px] uppercase tracking-[0.24em] text-amber-900"
|
||||
>
|
||||
Registry Authoring
|
||||
</Badge>
|
||||
<h1 className="text-4xl font-semibold tracking-tight text-slate-950 sm:text-5xl">
|
||||
Standards registry editor
|
||||
</h1>
|
||||
<p className="mt-6 text-base leading-8 text-slate-600">
|
||||
Use this authoring surface to start from the template or an existing standard, edit raw JSON,
|
||||
validate it against the registry schema, and prepare a submission-ready entry.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 lg:grid-cols-[320px_minmax(0,1fr)]">
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl text-slate-950">Source entry</CardTitle>
|
||||
<CardDescription className="text-base text-slate-600">
|
||||
Load the template or an existing standard into the editor.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<select
|
||||
className="w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 outline-none transition focus:border-slate-500"
|
||||
value={selection}
|
||||
onChange={(event) => {
|
||||
const nextSelection = event.target.value;
|
||||
setSelection(nextSelection);
|
||||
setDraft(prettyPrint(sourceEntries[nextSelection]));
|
||||
}}
|
||||
>
|
||||
<option value="template">Template</option>
|
||||
{standardsRegistry.map((standard) => (
|
||||
<option key={standard.slug} value={standard.slug}>
|
||||
{standard.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<div className="rounded-2xl bg-slate-50 px-4 py-4 text-sm leading-7 text-slate-600">
|
||||
Validation status:
|
||||
<div className="mt-3 flex items-center gap-3">
|
||||
<Badge
|
||||
className={
|
||||
validation.valid
|
||||
? 'bg-emerald-600 text-white hover:bg-emerald-600'
|
||||
: 'bg-rose-600 text-white hover:bg-rose-600'
|
||||
}
|
||||
>
|
||||
{validation.valid ? 'Valid' : 'Invalid'}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="mt-3">{validation.message}</p>
|
||||
</div>
|
||||
|
||||
{validation.parsed ? (
|
||||
<div className="rounded-2xl bg-slate-50 px-4 py-4 text-sm leading-7 text-slate-600">
|
||||
<div><strong className="text-slate-950">Slug:</strong> {validation.parsed.slug}</div>
|
||||
<div><strong className="text-slate-950">Href:</strong> {validation.parsed.href}</div>
|
||||
<div><strong className="text-slate-950">Compliance:</strong> {validation.parsed.compliance.label}</div>
|
||||
<div><strong className="text-slate-950">Submission points:</strong> {validation.parsed.submissionPoints.length}</div>
|
||||
</div>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl text-slate-950">JSON editor</CardTitle>
|
||||
<CardDescription className="text-base text-slate-600">
|
||||
Edit the raw registry entry. This surface validates structure, required fields, and normalized enums.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="mb-4 flex flex-wrap gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void handleCopy();
|
||||
}}
|
||||
className="rounded-full bg-slate-950 px-4 py-2 text-sm font-semibold text-white"
|
||||
>
|
||||
{copyState === 'copied' ? 'Copied' : 'Copy JSON'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleExport}
|
||||
className="rounded-full border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-900"
|
||||
>
|
||||
Export JSON
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleReset}
|
||||
className="rounded-full border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-900"
|
||||
>
|
||||
Reset draft
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
className="min-h-[720px] w-full rounded-2xl border border-slate-300 bg-slate-950 p-4 font-mono text-sm leading-6 text-slate-100 outline-none transition focus:border-slate-500"
|
||||
value={draft}
|
||||
onChange={(event) => setDraft(event.target.value)}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AuthGuard>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
|
||||
import type { StandardRegistryEntry } from '../../content/standards';
|
||||
import { StandardIcon } from './standard-icon';
|
||||
import Link from 'next/link';
|
||||
import { getComparisonTarget } from '../../content/standards';
|
||||
|
||||
export function StandardsBrowser({
|
||||
standards,
|
||||
}: {
|
||||
standards: StandardRegistryEntry[];
|
||||
}): JSX.Element {
|
||||
const [query, setQuery] = useState('');
|
||||
const [complianceFilter, setComplianceFilter] = useState<'all' | 'native' | 'external'>('all');
|
||||
const [statusFilter, setStatusFilter] = useState<'all' | 'live' | 'preview'>('all');
|
||||
const [featuredFilter, setFeaturedFilter] = useState<'all' | 'featured' | 'non-featured'>('all');
|
||||
const [categoryFilter, setCategoryFilter] = useState<string>('all');
|
||||
const [viewMode, setViewMode] = useState<'list' | 'table'>('list');
|
||||
|
||||
const normalizedQuery = query.trim().toLowerCase();
|
||||
const categories = Array.from(new Set(standards.map((standard) => standard.category))).sort();
|
||||
const filteredStandards = standards.filter((standard) => {
|
||||
const matchesQuery =
|
||||
normalizedQuery.length === 0 ||
|
||||
standard.name.toLowerCase().includes(normalizedQuery) ||
|
||||
(standard.shortName?.toLowerCase().includes(normalizedQuery) ?? false) ||
|
||||
standard.summary.toLowerCase().includes(normalizedQuery) ||
|
||||
standard.category.toLowerCase().includes(normalizedQuery) ||
|
||||
standard.standards.some((item) => item.toLowerCase().includes(normalizedQuery));
|
||||
|
||||
const matchesCompliance =
|
||||
complianceFilter === 'all' ||
|
||||
(complianceFilter === 'native' && standard.compliance.native) ||
|
||||
(complianceFilter === 'external' && !standard.compliance.native);
|
||||
|
||||
const matchesStatus =
|
||||
statusFilter === 'all' ||
|
||||
(statusFilter === 'live' && standard.status === 'Live') ||
|
||||
(statusFilter === 'preview' && standard.status === 'Preview');
|
||||
|
||||
const matchesFeatured =
|
||||
featuredFilter === 'all' ||
|
||||
(featuredFilter === 'featured' && !!standard.featured) ||
|
||||
(featuredFilter === 'non-featured' && !standard.featured);
|
||||
|
||||
const matchesCategory = categoryFilter === 'all' || standard.category === categoryFilter;
|
||||
|
||||
return matchesQuery && matchesCompliance && matchesStatus && matchesFeatured && matchesCategory;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[linear-gradient(180deg,#fff_0%,#f8fafc_45%,#fff_100%)]">
|
||||
<section className="px-4 pt-10 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl text-slate-950">Search and filter standards</CardTitle>
|
||||
<CardDescription className="text-base text-slate-600">
|
||||
Narrow the registry by keyword or by whether compliance is native to the standard.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4 lg:grid-cols-2">
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-medium text-slate-700">Search</span>
|
||||
<input
|
||||
className="w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 outline-none transition focus:border-slate-500"
|
||||
type="search"
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
placeholder="Search by name, category, standard, or summary"
|
||||
/>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-medium text-slate-700">Compliance mode</span>
|
||||
<select
|
||||
className="w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 outline-none transition focus:border-slate-500"
|
||||
value={complianceFilter}
|
||||
onChange={(event) =>
|
||||
setComplianceFilter(event.target.value as 'all' | 'native' | 'external')
|
||||
}
|
||||
>
|
||||
<option value="all">All standards</option>
|
||||
<option value="native">Native compliance</option>
|
||||
<option value="external">External compliance</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-medium text-slate-700">Status</span>
|
||||
<select
|
||||
className="w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 outline-none transition focus:border-slate-500"
|
||||
value={statusFilter}
|
||||
onChange={(event) => setStatusFilter(event.target.value as 'all' | 'live' | 'preview')}
|
||||
>
|
||||
<option value="all">All statuses</option>
|
||||
<option value="live">Live</option>
|
||||
<option value="preview">Preview</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-medium text-slate-700">Category</span>
|
||||
<select
|
||||
className="w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 outline-none transition focus:border-slate-500"
|
||||
value={categoryFilter}
|
||||
onChange={(event) => setCategoryFilter(event.target.value)}
|
||||
>
|
||||
<option value="all">All categories</option>
|
||||
{categories.map((category) => (
|
||||
<option key={category} value={category}>
|
||||
{category}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-medium text-slate-700">Featured</span>
|
||||
<select
|
||||
className="w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 outline-none transition focus:border-slate-500"
|
||||
value={featuredFilter}
|
||||
onChange={(event) =>
|
||||
setFeaturedFilter(event.target.value as 'all' | 'featured' | 'non-featured')
|
||||
}
|
||||
>
|
||||
<option value="all">All entries</option>
|
||||
<option value="featured">Featured only</option>
|
||||
<option value="non-featured">Non-featured only</option>
|
||||
</select>
|
||||
</label>
|
||||
</CardContent>
|
||||
<CardContent className="flex flex-wrap items-center gap-3 pt-0">
|
||||
<Badge variant="outline" className="border-slate-300 bg-slate-50 text-slate-700">
|
||||
{filteredStandards.length} result{filteredStandards.length === 1 ? '' : 's'}
|
||||
</Badge>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`rounded-full px-3 py-1 text-sm font-medium ${
|
||||
viewMode === 'list' ? 'bg-slate-950 text-white' : 'bg-slate-100 text-slate-700'
|
||||
}`}
|
||||
>
|
||||
List
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setViewMode('table')}
|
||||
className={`rounded-full px-3 py-1 text-sm font-medium ${
|
||||
viewMode === 'table' ? 'bg-slate-950 text-white' : 'bg-slate-100 text-slate-700'
|
||||
}`}
|
||||
>
|
||||
Table
|
||||
</button>
|
||||
</div>
|
||||
{query ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setQuery('')}
|
||||
className="text-sm font-medium text-slate-600 underline-offset-4 hover:text-slate-950 hover:underline"
|
||||
>
|
||||
Clear search
|
||||
</button>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{filteredStandards.length > 0 ? (
|
||||
viewMode === 'list' ? (
|
||||
<section className="px-4 py-12 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto grid max-w-6xl gap-6 lg:grid-cols-2">
|
||||
{filteredStandards.map((standard) => {
|
||||
const comparisonTarget = getComparisonTarget(standard);
|
||||
return (
|
||||
<Card key={standard.slug} className="border-slate-200 bg-white shadow-sm">
|
||||
<CardHeader className="gap-4">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Badge variant="outline" className="border-slate-300 bg-slate-50 text-slate-700">
|
||||
{standard.status}
|
||||
</Badge>
|
||||
<Badge
|
||||
className={
|
||||
standard.compliance.native
|
||||
? 'bg-emerald-600 text-white hover:bg-emerald-600'
|
||||
: 'bg-slate-800 text-white hover:bg-slate-800'
|
||||
}
|
||||
>
|
||||
{standard.compliance.native ? 'Native compliance' : 'External compliance'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-3 text-2xl text-slate-950">
|
||||
<span className="inline-flex h-10 w-10 items-center justify-center rounded-full bg-slate-100 text-slate-700">
|
||||
<StandardIcon name={standard.icon} />
|
||||
</span>
|
||||
<span>{standard.name}</span>
|
||||
</CardTitle>
|
||||
<CardDescription className="mt-2 text-base text-slate-600">
|
||||
{standard.shortName ?? standard.summary}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-sm leading-7 text-slate-600">{standard.summary}</p>
|
||||
<div className="flex flex-wrap gap-5">
|
||||
<Link
|
||||
href={standard.href}
|
||||
className="inline-flex items-center text-sm font-semibold text-slate-950 underline-offset-4 hover:underline"
|
||||
>
|
||||
View standard
|
||||
</Link>
|
||||
{comparisonTarget ? (
|
||||
<Link
|
||||
href={`${comparisonTarget.href}#comparison`}
|
||||
className="inline-flex items-center text-sm font-medium text-slate-600 underline-offset-4 hover:text-slate-950 hover:underline"
|
||||
>
|
||||
Compare with {comparisonTarget.name}
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<section className="px-4 py-12 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-6xl overflow-hidden rounded-[1.5rem] border border-slate-200 bg-white shadow-sm">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[760px] text-left text-sm">
|
||||
<thead className="bg-slate-50 text-slate-500">
|
||||
<tr>
|
||||
<th className="px-4 py-3 font-medium">Standard</th>
|
||||
<th className="px-4 py-3 font-medium">Category</th>
|
||||
<th className="px-4 py-3 font-medium">Compliance</th>
|
||||
<th className="px-4 py-3 font-medium">Status</th>
|
||||
<th className="px-4 py-3 font-medium">Submission points</th>
|
||||
<th className="px-4 py-3 font-medium">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredStandards.map((standard) => {
|
||||
const comparisonTarget = getComparisonTarget(standard);
|
||||
return (
|
||||
<tr key={standard.slug} className="border-t border-slate-200">
|
||||
<td className="px-4 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="inline-flex h-9 w-9 items-center justify-center rounded-full bg-slate-100 text-slate-700">
|
||||
<StandardIcon name={standard.icon} />
|
||||
</span>
|
||||
<div>
|
||||
<div className="font-semibold text-slate-950">{standard.name}</div>
|
||||
<div className="text-slate-500">{standard.shortName ?? standard.summary}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-4 text-slate-700">{standard.category}</td>
|
||||
<td className="px-4 py-4 text-slate-700">{standard.compliance.label}</td>
|
||||
<td className="px-4 py-4 text-slate-700">{standard.status}</td>
|
||||
<td className="px-4 py-4 text-slate-700">{standard.submissionPoints.length}</td>
|
||||
<td className="px-4 py-4">
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link
|
||||
href={standard.href}
|
||||
className="font-semibold text-slate-950 underline-offset-4 hover:underline"
|
||||
>
|
||||
View
|
||||
</Link>
|
||||
{comparisonTarget ? (
|
||||
<Link
|
||||
href={`${comparisonTarget.href}#comparison`}
|
||||
className="text-slate-600 underline-offset-4 hover:text-slate-950 hover:underline"
|
||||
>
|
||||
Compare
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
) : (
|
||||
<section className="px-4 py-20 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<Card className="border-slate-200 bg-white shadow-sm">
|
||||
<CardContent className="flex flex-col items-center justify-center px-6 py-16 text-center">
|
||||
<span className="inline-flex h-14 w-14 items-center justify-center rounded-full bg-slate-100 text-slate-700">
|
||||
<StandardIcon name="file-stack" className="h-6 w-6" />
|
||||
</span>
|
||||
<h2 className="mt-6 text-2xl font-semibold text-slate-950">No standards matched</h2>
|
||||
<p className="mt-3 max-w-xl text-sm leading-7 text-slate-600">
|
||||
Try a broader keyword or switch the compliance filter back to show all standards in the registry.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import Link from 'next/link';
|
||||
import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../lib/ui-server';
|
||||
import { getComparisonTarget, type StandardRegistryEntry } from '../../content/standards';
|
||||
import { StandardIcon } from './standard-icon';
|
||||
|
||||
export function StandardsIndexPage({
|
||||
standards,
|
||||
compactHero = false,
|
||||
}: {
|
||||
standards: StandardRegistryEntry[];
|
||||
compactHero?: boolean;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div className={compactHero ? '' : 'min-h-screen bg-[linear-gradient(180deg,#fff_0%,#f8fafc_45%,#fff_100%)]'}>
|
||||
<section className={`px-4 sm:px-6 lg:px-8 ${compactHero ? 'py-12' : 'py-20'}`}>
|
||||
<div className="mx-auto max-w-6xl">
|
||||
{!compactHero ? (
|
||||
<div className="max-w-3xl">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="mb-6 border-amber-300 bg-amber-50 px-4 py-1 text-[11px] uppercase tracking-[0.24em] text-amber-900"
|
||||
>
|
||||
Standards Library
|
||||
</Badge>
|
||||
<h1 className="text-balance text-4xl font-semibold tracking-tight text-slate-950 sm:text-5xl">
|
||||
Standards and frameworks for regulated digital asset programs
|
||||
</h1>
|
||||
<p className="mt-6 max-w-2xl text-pretty text-lg leading-8 text-slate-600">
|
||||
Explore token standards, compliance frameworks, and implementation-ready guidance for
|
||||
digital asset issuance across regulated and real-world asset use cases.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className={`grid gap-6 lg:grid-cols-2 ${compactHero ? '' : 'mt-12'}`}>
|
||||
{standards.map((standard) => {
|
||||
const comparisonTarget = getComparisonTarget(standard);
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={standard.slug}
|
||||
className="border-slate-200 bg-white shadow-sm transition-transform hover:-translate-y-0.5"
|
||||
>
|
||||
<CardHeader className="gap-4">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Badge variant="outline" className="border-slate-300 bg-slate-50 text-slate-700">
|
||||
{standard.status}
|
||||
</Badge>
|
||||
{standard.featured ? (
|
||||
<Badge className="bg-slate-950 text-white hover:bg-slate-950">Featured</Badge>
|
||||
) : null}
|
||||
<Badge
|
||||
className={
|
||||
standard.compliance.native
|
||||
? 'bg-emerald-600 text-white hover:bg-emerald-600'
|
||||
: 'bg-slate-800 text-white hover:bg-slate-800'
|
||||
}
|
||||
>
|
||||
{standard.compliance.native ? 'Native compliance' : 'External compliance'}
|
||||
</Badge>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-slate-500">
|
||||
{standard.category}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-3 text-2xl text-slate-950">
|
||||
<span className="inline-flex h-10 w-10 items-center justify-center rounded-full bg-slate-100 text-sm font-semibold text-slate-700">
|
||||
<StandardIcon name={standard.icon} />
|
||||
</span>
|
||||
<span>{standard.name}</span>
|
||||
</CardTitle>
|
||||
{standard.shortName ? (
|
||||
<CardDescription className="mt-2 text-base text-slate-600">
|
||||
{standard.shortName}
|
||||
</CardDescription>
|
||||
) : null}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-5">
|
||||
<p className="text-sm leading-7 text-slate-600">{standard.summary}</p>
|
||||
<dl className="grid gap-3 sm:grid-cols-2">
|
||||
<div className="rounded-2xl bg-slate-50 p-4">
|
||||
<dt className="text-xs uppercase tracking-[0.18em] text-slate-500">Audience</dt>
|
||||
<dd className="mt-2 text-sm leading-6 text-slate-800">{standard.audience}</dd>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-slate-50 p-4">
|
||||
<dt className="text-xs uppercase tracking-[0.18em] text-slate-500">Standards</dt>
|
||||
<dd className="mt-2 text-sm leading-6 text-slate-800">
|
||||
{standard.standards.join(', ')}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-slate-50 p-4">
|
||||
<dt className="text-xs uppercase tracking-[0.18em] text-slate-500">Specification</dt>
|
||||
<dd className="mt-2 text-sm leading-6 text-slate-800">{standard.specification.label}</dd>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-slate-50 p-4">
|
||||
<dt className="text-xs uppercase tracking-[0.18em] text-slate-500">Submission points</dt>
|
||||
<dd className="mt-2 text-sm leading-6 text-slate-800">{standard.submissionPoints.length}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div className="flex flex-wrap gap-4 text-sm text-slate-600">
|
||||
{standard.publisher ? <span>Publisher: {standard.publisher}</span> : null}
|
||||
{standard.auditor ? <span>Auditor: {standard.auditor}</span> : null}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-5">
|
||||
<Link
|
||||
href={standard.href}
|
||||
className="inline-flex items-center text-sm font-semibold text-slate-950 underline-offset-4 hover:underline"
|
||||
>
|
||||
View standard
|
||||
</Link>
|
||||
<a
|
||||
href={standard.specification.href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center text-sm font-medium text-slate-600 underline-offset-4 hover:text-slate-950 hover:underline"
|
||||
>
|
||||
Open spec
|
||||
</a>
|
||||
{comparisonTarget ? (
|
||||
<Link
|
||||
href={`${comparisonTarget.href}#comparison`}
|
||||
className="inline-flex items-center text-sm font-medium text-slate-600 underline-offset-4 hover:text-slate-950 hover:underline"
|
||||
>
|
||||
Compare with {comparisonTarget.name}
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import type { MarketingLandingPageProps } from '../components/marketing/landing-page';
|
||||
|
||||
export const erc20LandingPage: MarketingLandingPageProps = {
|
||||
hero: {
|
||||
eyebrow: 'Foundational Token Standard',
|
||||
headline: 'The Baseline Standard for Fungible Tokens',
|
||||
subheadline:
|
||||
'ERC-20 defines the core interface for fungible tokens on Ethereum and remains the foundation for interoperable token issuance across wallets, exchanges, and applications.',
|
||||
supportingCopy:
|
||||
'For teams that need broad ecosystem support and straightforward fungible token behavior, ERC-20 provides the common baseline. It is best suited to general-purpose token issuance and often serves as the interoperability layer beneath more specialized token frameworks.',
|
||||
primaryCta: {
|
||||
label: 'Explore ERC-20',
|
||||
href: '/docs',
|
||||
},
|
||||
secondaryCta: {
|
||||
label: 'Compare Standards',
|
||||
href: '#comparison',
|
||||
},
|
||||
tertiaryLink: {
|
||||
label: 'View Documentation',
|
||||
href: '/docs',
|
||||
},
|
||||
},
|
||||
trustStrip: {
|
||||
label: 'Standards overview',
|
||||
items: [
|
||||
{
|
||||
label: 'Standard',
|
||||
value: 'ERC-20',
|
||||
},
|
||||
{
|
||||
label: 'Role',
|
||||
value: 'Baseline fungible token interface',
|
||||
},
|
||||
{
|
||||
label: 'Category',
|
||||
value: 'General-purpose tokenization',
|
||||
},
|
||||
{
|
||||
label: 'Fit',
|
||||
value: 'Interoperable token issuance and ecosystem compatibility',
|
||||
},
|
||||
{
|
||||
label: 'Primary strength',
|
||||
value: 'Simplicity and universal support',
|
||||
},
|
||||
],
|
||||
},
|
||||
valueProps: {
|
||||
headline: 'Optimized for interoperability and simplicity',
|
||||
intro:
|
||||
'ERC-20 established the common language for fungible tokens across the Ethereum ecosystem. Its strength is broad adoption, predictable behavior, and straightforward integration.',
|
||||
cards: [
|
||||
{
|
||||
title: 'Universal Compatibility',
|
||||
copy:
|
||||
'Supported across wallets, exchanges, custodians, and dApps as the baseline fungible token format.',
|
||||
},
|
||||
{
|
||||
title: 'Simple Token Logic',
|
||||
copy:
|
||||
'Provides a minimal and predictable interface for balances, transfers, approvals, and allowances.',
|
||||
},
|
||||
{
|
||||
title: 'Builder Friendly',
|
||||
copy:
|
||||
'Well understood by developers, infrastructure providers, and integrators across the Ethereum ecosystem.',
|
||||
},
|
||||
{
|
||||
title: 'Strong Liquidity Fit',
|
||||
copy:
|
||||
'Works naturally with trading venues, pools, bridges, and DeFi protocols designed around fungible tokens.',
|
||||
},
|
||||
{
|
||||
title: 'Composable Foundation',
|
||||
copy:
|
||||
'Frequently used as the compatibility layer for more specialized token models and compliance overlays.',
|
||||
},
|
||||
{
|
||||
title: 'Fast to Adopt',
|
||||
copy:
|
||||
'A practical choice for teams that need broad support without embedding complex policy logic directly into the token.',
|
||||
},
|
||||
],
|
||||
},
|
||||
useCases: {
|
||||
headline: 'Best suited to general-purpose token issuance',
|
||||
intro:
|
||||
'ERC-20 is often the right choice when the goal is broad market compatibility rather than native compliance enforcement.',
|
||||
items: [
|
||||
'Utility tokens',
|
||||
'Stablecoins',
|
||||
'Governance tokens',
|
||||
'Rewards tokens',
|
||||
'Exchange-listed assets',
|
||||
'Protocol assets',
|
||||
'Treasury instruments',
|
||||
'Liquidity and settlement rails',
|
||||
'Wrapped assets',
|
||||
'Ecosystem incentives',
|
||||
],
|
||||
supportingLine:
|
||||
'ERC-20 can still be used in regulated contexts, but compliance controls generally need to be added through external systems, wrappers, or custom extensions.',
|
||||
},
|
||||
proofPoints: {
|
||||
headline: 'Why teams still start with ERC-20',
|
||||
intro:
|
||||
'ERC-20 remains the default standard for fungible token interoperability because it balances simplicity, widespread support, and implementation maturity.',
|
||||
items: [
|
||||
'Widely supported across the Ethereum ecosystem and token infrastructure stack.',
|
||||
'Simple enough to implement, audit, and integrate without specialized compliance architecture.',
|
||||
'Well suited to liquid, broadly distributed token models and exchange-facing assets.',
|
||||
'Acts as the reference point for comparing more specialized token standards such as ERC-3643.',
|
||||
],
|
||||
},
|
||||
comparison: {
|
||||
headline: 'When is ERC-20 enough, and when is it not?',
|
||||
intro:
|
||||
'ERC-20 is ideal when broad compatibility and simple fungible token behavior matter most. It becomes less suitable when regulated issuance requires native controls over identity, eligibility, and transfer permissions.',
|
||||
columns: ['Category', 'ERC-20', 'ERC-3643'],
|
||||
rows: [
|
||||
{
|
||||
category: 'Primary purpose',
|
||||
left: 'General-purpose fungible tokens',
|
||||
right: 'Compliant tokenization for regulated assets',
|
||||
},
|
||||
{
|
||||
category: 'Compliance model',
|
||||
left: 'Usually external, manual, or custom',
|
||||
right: 'Identity-based and enforced onchain',
|
||||
},
|
||||
{
|
||||
category: 'Transfer restrictions',
|
||||
left: 'Not native',
|
||||
right: 'Native',
|
||||
},
|
||||
{
|
||||
category: 'Eligibility controls',
|
||||
left: 'Not native',
|
||||
right: 'Native',
|
||||
},
|
||||
{
|
||||
category: 'Best fit',
|
||||
left: 'Broad ecosystem issuance and liquidity',
|
||||
right: 'Regulated assets and RWAs',
|
||||
},
|
||||
{
|
||||
category: 'Operational complexity',
|
||||
left: 'Lower baseline complexity',
|
||||
right: 'Higher capability with more built-in controls',
|
||||
},
|
||||
],
|
||||
secondaryNote:
|
||||
'ERC-20 is the baseline standard. For regulated programs, it is often the starting point for comparison rather than the final architecture.',
|
||||
cta: {
|
||||
label: 'View Standards Library',
|
||||
href: '/standards',
|
||||
},
|
||||
},
|
||||
faq: {
|
||||
headline: 'Frequently asked questions',
|
||||
items: [
|
||||
{
|
||||
question: 'What is ERC-20?',
|
||||
answer:
|
||||
'ERC-20 is the standard interface for fungible tokens on Ethereum. It defines how balances, transfers, approvals, and allowances should behave so tokens can interoperate across the ecosystem.',
|
||||
},
|
||||
{
|
||||
question: 'Why is ERC-20 so widely used?',
|
||||
answer:
|
||||
'Its broad adoption comes from its simplicity and ecosystem support. Most wallets, exchanges, and DeFi applications are built to work with ERC-20 tokens by default.',
|
||||
},
|
||||
{
|
||||
question: 'Does ERC-20 include compliance controls?',
|
||||
answer:
|
||||
'Not natively. Teams that need regulated issuance or controlled transfers usually add compliance through external systems, custom extensions, or more specialized standards.',
|
||||
},
|
||||
{
|
||||
question: 'When should a team choose ERC-20?',
|
||||
answer:
|
||||
'ERC-20 is a strong choice when broad compatibility, liquidity integration, and straightforward token behavior matter more than embedding policy logic directly into the asset.',
|
||||
},
|
||||
{
|
||||
question: 'How does ERC-20 relate to ERC-3643?',
|
||||
answer:
|
||||
'ERC-3643 builds on ERC-20 compatibility while adding identity-based compliance and transfer controls. ERC-20 is the simpler baseline; ERC-3643 is the more compliance-oriented framework.',
|
||||
},
|
||||
],
|
||||
},
|
||||
footerCta: {
|
||||
headline: 'Start with the right token standard for the job',
|
||||
copy:
|
||||
'Use ERC-20 when you need broad compatibility and simple fungible token behavior, or compare it against specialized standards when compliance and lifecycle controls are part of the requirement.',
|
||||
primaryCta: {
|
||||
label: 'Explore Standards',
|
||||
href: '/standards',
|
||||
},
|
||||
secondaryCta: {
|
||||
label: 'Talk to Sales',
|
||||
href: '/contact',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,203 @@
|
||||
import type { MarketingLandingPageProps } from '../components/marketing/landing-page';
|
||||
|
||||
export const erc3643LandingPage: MarketingLandingPageProps = {
|
||||
hero: {
|
||||
eyebrow: 'Compliant Token Standard',
|
||||
headline: 'Compliant Tokenization for Real-World Assets',
|
||||
subheadline:
|
||||
'ERC-3643 is the token standard for issuing, managing, and distributing regulated digital assets with identity-based compliance built into every transfer.',
|
||||
supportingCopy:
|
||||
'Designed for regulated markets, ERC-3643 combines token issuance, transfer control, lifecycle automation, and ecosystem compatibility in a single standard. It is built for teams bringing securities, funds, private debt, real estate, and other real-world assets onchain.',
|
||||
primaryCta: {
|
||||
label: 'Explore ERC-3643',
|
||||
href: '/docs',
|
||||
},
|
||||
secondaryCta: {
|
||||
label: 'Compare Standards',
|
||||
href: '#comparison',
|
||||
},
|
||||
tertiaryLink: {
|
||||
label: 'View Documentation',
|
||||
href: '/docs',
|
||||
},
|
||||
},
|
||||
trustStrip: {
|
||||
label: 'Standards and assurance',
|
||||
items: [
|
||||
{
|
||||
label: 'Standard',
|
||||
value: 'ERC-3643, ERC-20 compatible',
|
||||
},
|
||||
{
|
||||
label: 'Publisher',
|
||||
value: 'Tokeny',
|
||||
},
|
||||
{
|
||||
label: 'Auditor',
|
||||
value: 'Hacken',
|
||||
},
|
||||
{
|
||||
label: 'Category',
|
||||
value: 'Compliant tokenization',
|
||||
},
|
||||
{
|
||||
label: 'Fit',
|
||||
value: 'Regulated digital assets and RWAs',
|
||||
},
|
||||
],
|
||||
},
|
||||
valueProps: {
|
||||
headline: 'Built for compliant digital asset issuance',
|
||||
intro:
|
||||
'ERC-3643 embeds identity, eligibility, and transfer controls directly into the token so compliance is enforced as part of the asset itself, not added as a separate layer.',
|
||||
cards: [
|
||||
{
|
||||
title: 'Built-In Compliance',
|
||||
copy:
|
||||
'Ensure only eligible participants can hold and transfer assets through identity-based controls enforced onchain.',
|
||||
},
|
||||
{
|
||||
title: 'Lifecycle Automation',
|
||||
copy:
|
||||
'Support issuance, restrictions, corporate actions, and ongoing administration with extensive built-in functionality.',
|
||||
},
|
||||
{
|
||||
title: 'Regulated Asset Ready',
|
||||
copy:
|
||||
'Well suited for securities, funds, private debt, commodities, real estate, and other tokenized real-world assets.',
|
||||
},
|
||||
{
|
||||
title: 'ERC-20 Compatibility',
|
||||
copy:
|
||||
'Integrates with wallets, marketplaces, exchanges, and dApps while maintaining compliance enforcement.',
|
||||
},
|
||||
{
|
||||
title: 'Configurable Rules',
|
||||
copy:
|
||||
'Adapt compliance policies over time to meet changing operational, legal, or market requirements.',
|
||||
},
|
||||
{
|
||||
title: 'Proven Model',
|
||||
copy: 'Validated across more than 120 use cases for compliant issuance and asset management.',
|
||||
},
|
||||
],
|
||||
},
|
||||
useCases: {
|
||||
headline: 'Designed for regulated and real-world asset use cases',
|
||||
intro:
|
||||
'ERC-3643 can be applied across a broad range of tokenized asset classes and identity-driven applications.',
|
||||
items: [
|
||||
'Equities',
|
||||
'Loans',
|
||||
'Currencies',
|
||||
'Funds and securities',
|
||||
'Commodities',
|
||||
'Private debt',
|
||||
'Bonds',
|
||||
'Real estate',
|
||||
'Real-world assets',
|
||||
'Identity',
|
||||
'Loyalty',
|
||||
],
|
||||
supportingLine:
|
||||
'From institutional issuance to programmable asset distribution, ERC-3643 provides a consistent compliance framework across asset classes.',
|
||||
},
|
||||
proofPoints: {
|
||||
headline: 'Trusted for compliant tokenization',
|
||||
intro:
|
||||
'ERC-3643 is designed for teams that need more than simple token issuance. It provides the operational and compliance foundation required for regulated digital asset programs.',
|
||||
items: [
|
||||
'More than 120 validated use cases across compliant digital asset issuance and management.',
|
||||
'Over 100 built-in functions for lifecycle operations, automation, and corporate actions.',
|
||||
'Identity-based compliance enforced directly at the token level.',
|
||||
'Broad compatibility with exchanges, marketplaces, wallets, and dApps.',
|
||||
],
|
||||
},
|
||||
comparison: {
|
||||
headline: 'Why ERC-3643 instead of a basic token standard?',
|
||||
intro:
|
||||
'ERC-20 is foundational for fungible tokens, but it does not natively provide the compliance controls needed for regulated issuance. ERC-3643 extends ERC-20 compatibility with identity-aware transfer rules and operational controls built into the token.',
|
||||
columns: ['Category', 'ERC-3643', 'ERC-20'],
|
||||
rows: [
|
||||
{
|
||||
category: 'Primary purpose',
|
||||
left: 'Compliant tokenization for regulated assets',
|
||||
right: 'General-purpose fungible tokens',
|
||||
},
|
||||
{
|
||||
category: 'Compliance',
|
||||
left: 'Enforced onchain through identity and rules',
|
||||
right: 'Typically external or manual',
|
||||
},
|
||||
{
|
||||
category: 'Transfer restrictions',
|
||||
left: 'Native',
|
||||
right: 'Not native',
|
||||
},
|
||||
{
|
||||
category: 'Eligibility controls',
|
||||
left: 'Native',
|
||||
right: 'Not native',
|
||||
},
|
||||
{
|
||||
category: 'Lifecycle management',
|
||||
left: 'Extensive built-in functions',
|
||||
right: 'Minimal core functions',
|
||||
},
|
||||
{
|
||||
category: 'Best fit',
|
||||
left: 'Regulated assets and RWAs',
|
||||
right: 'Simple token issuance',
|
||||
},
|
||||
],
|
||||
secondaryNote:
|
||||
'Compared with ERC-1400-style positioning, ERC-3643 is often easier to present as a practical, issuer-friendly standard for compliant tokenization with strong interoperability and lifecycle flexibility.',
|
||||
cta: {
|
||||
label: 'View Full Comparison',
|
||||
href: '#comparison',
|
||||
},
|
||||
},
|
||||
faq: {
|
||||
headline: 'Frequently asked questions',
|
||||
items: [
|
||||
{
|
||||
question: 'What is ERC-3643?',
|
||||
answer:
|
||||
'ERC-3643 is an Ethereum token standard for compliant tokenization. It combines ERC-20 compatibility with identity-based compliance controls that govern who can hold, transfer, and interact with tokenized assets.',
|
||||
},
|
||||
{
|
||||
question: 'How is ERC-3643 different from ERC-20?',
|
||||
answer:
|
||||
'ERC-20 defines the baseline behavior for fungible tokens, but it does not natively enforce compliance. ERC-3643 adds identity, eligibility, and transfer restrictions directly into token logic, making it better suited for regulated assets.',
|
||||
},
|
||||
{
|
||||
question: 'What types of assets can use ERC-3643?',
|
||||
answer:
|
||||
'ERC-3643 is well suited for equities, funds, securities, private debt, bonds, commodities, real estate, currencies, and other tokenized real-world assets.',
|
||||
},
|
||||
{
|
||||
question: 'Does ERC-3643 still work with the broader ecosystem?',
|
||||
answer:
|
||||
'Yes. ERC-3643 is designed to remain compatible with much of the broader Ethereum ecosystem, including wallets, dApps, marketplaces, and exchanges, while preserving compliance enforcement.',
|
||||
},
|
||||
{
|
||||
question: 'Who is ERC-3643 for?',
|
||||
answer:
|
||||
'It is particularly relevant for financial institutions, asset issuers, tokenization platforms, fund operators, and enterprises that need compliant digital asset issuance and lifecycle management.',
|
||||
},
|
||||
],
|
||||
},
|
||||
footerCta: {
|
||||
headline: 'Bring regulated assets onchain with greater control',
|
||||
copy:
|
||||
'Use ERC-3643 to issue compliant digital assets with identity-based transfer enforcement, lifecycle automation, and compatibility across the broader digital asset ecosystem.',
|
||||
primaryCta: {
|
||||
label: 'Get Started',
|
||||
href: '/contact',
|
||||
},
|
||||
secondaryCta: {
|
||||
label: 'Talk to Sales',
|
||||
href: '/contact',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
# Standards Registry Authoring Guide
|
||||
|
||||
This registry is the canonical content system for standards pages in the public portal.
|
||||
|
||||
## What a complete entry must include
|
||||
|
||||
Each standard entry should be complete enough to power:
|
||||
|
||||
- the homepage featured/preview cards
|
||||
- the `/standards` browser and filters
|
||||
- the detail page
|
||||
- the API export routes
|
||||
- future search, integrations, or content syndication
|
||||
|
||||
Minimum required content:
|
||||
|
||||
- canonical specification link
|
||||
- at least one repository reference
|
||||
- at least one submission point
|
||||
- compliance posture
|
||||
- governance and maintainer information
|
||||
- status history
|
||||
- implementation example
|
||||
- submission checklist
|
||||
- ecosystem support notes
|
||||
|
||||
## How to add a new standard
|
||||
|
||||
1. Copy the shape from [template.ts](./template.ts).
|
||||
2. Create or import the marketing landing page content file.
|
||||
3. Fill in all registry metadata fields.
|
||||
4. Add the entry to [index.ts](./index.ts).
|
||||
5. Confirm the `href`, `slug`, and `comparisonTarget` values line up.
|
||||
6. Run `pnpm --dir the-order/apps/portal-public type-check`.
|
||||
|
||||
## Native compliance policy
|
||||
|
||||
Use `compliance.native = true` only when the standard itself natively enforces compliance or transfer eligibility at the token or protocol layer.
|
||||
|
||||
Use `compliance.native = false` when compliance depends on:
|
||||
|
||||
- wrappers
|
||||
- external policy engines
|
||||
- offchain operating controls
|
||||
- custom overlays beyond the standard itself
|
||||
|
||||
## Submission points
|
||||
|
||||
Every entry should identify where updates or official proposals are submitted.
|
||||
|
||||
Examples:
|
||||
|
||||
- Ethereum EIPs repository
|
||||
- official pull request queue
|
||||
- canonical standards repo
|
||||
- maintainer-owned implementation repo used for amendments or reference updates
|
||||
|
||||
## Icons
|
||||
|
||||
Use one of the normalized icon names defined in `schema.ts`:
|
||||
|
||||
- `shield`
|
||||
- `coins`
|
||||
- `scale`
|
||||
- `network`
|
||||
- `file-stack`
|
||||
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { getSortedStandards } from './index';
|
||||
import { filterStandards } from './filter';
|
||||
|
||||
describe('filterStandards', () => {
|
||||
const standards = getSortedStandards();
|
||||
|
||||
it('filters by query', () => {
|
||||
const result = filterStandards(standards, { q: 'compliant' });
|
||||
expect(result.map((standard) => standard.slug)).toEqual(['erc-3643']);
|
||||
});
|
||||
|
||||
it('filters by compliance mode', () => {
|
||||
const result = filterStandards(standards, { compliance: 'external' });
|
||||
expect(result.map((standard) => standard.slug)).toEqual(['erc-20']);
|
||||
});
|
||||
|
||||
it('filters featured entries', () => {
|
||||
const result = filterStandards(standards, { featured: 'true' });
|
||||
expect(result.map((standard) => standard.slug)).toEqual(['erc-3643']);
|
||||
});
|
||||
|
||||
it('applies a limit', () => {
|
||||
const result = filterStandards(standards, { limit: 1 });
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { StandardRegistryEntry } from './index';
|
||||
|
||||
export interface StandardsFilterInput {
|
||||
q?: string | null;
|
||||
category?: string | null;
|
||||
status?: string | null;
|
||||
featured?: string | null;
|
||||
compliance?: string | null;
|
||||
limit?: number | null;
|
||||
}
|
||||
|
||||
export function filterStandards(
|
||||
standards: StandardRegistryEntry[],
|
||||
filters: StandardsFilterInput
|
||||
): StandardRegistryEntry[] {
|
||||
const query = filters.q?.trim().toLowerCase() ?? '';
|
||||
const category = filters.category?.trim().toLowerCase() ?? '';
|
||||
const status = filters.status?.trim().toLowerCase() ?? '';
|
||||
const featured = filters.featured ?? null;
|
||||
const compliance = filters.compliance?.trim().toLowerCase() ?? '';
|
||||
const limit = filters.limit ?? null;
|
||||
|
||||
let filtered = standards.filter((standard) => {
|
||||
if (query) {
|
||||
const matchesQuery =
|
||||
standard.name.toLowerCase().includes(query) ||
|
||||
standard.summary.toLowerCase().includes(query) ||
|
||||
standard.category.toLowerCase().includes(query) ||
|
||||
(standard.shortName?.toLowerCase().includes(query) ?? false) ||
|
||||
standard.standards.some((item) => item.toLowerCase().includes(query));
|
||||
|
||||
if (!matchesQuery) return false;
|
||||
}
|
||||
|
||||
if (category && standard.category.toLowerCase() !== category) return false;
|
||||
if (status && standard.status.toLowerCase() !== status) return false;
|
||||
if (featured === 'true' && !standard.featured) return false;
|
||||
if (featured === 'false' && standard.featured) return false;
|
||||
if (compliance === 'native' && !standard.compliance.native) return false;
|
||||
if (compliance === 'external' && standard.compliance.native) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (typeof limit === 'number' && Number.isFinite(limit) && limit > 0) {
|
||||
filtered = filtered.slice(0, limit);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { getSortedStandards } from './index';
|
||||
import { standardsRegistryJsonSchema } from './schema-json';
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = path.resolve(currentDir, '..', '..', '..');
|
||||
const outputDir = path.join(projectRoot, 'public', 'standards');
|
||||
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
const standards = getSortedStandards();
|
||||
|
||||
await writeFile(
|
||||
path.join(outputDir, 'registry.json'),
|
||||
`${JSON.stringify({ standards, count: standards.length }, null, 2)}\n`,
|
||||
'utf8'
|
||||
);
|
||||
|
||||
await writeFile(
|
||||
path.join(outputDir, 'schema.json'),
|
||||
`${JSON.stringify(standardsRegistryJsonSchema, null, 2)}\n`,
|
||||
'utf8'
|
||||
);
|
||||
|
||||
process.stdout.write(`Generated ${path.join(outputDir, 'registry.json')}\n`);
|
||||
process.stdout.write(`Generated ${path.join(outputDir, 'schema.json')}\n`);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { getComparisonTarget, getFeaturedStandards, getRelatedStandards, getSortedStandards } from './index';
|
||||
|
||||
describe('standards registry helpers', () => {
|
||||
it('returns standards sorted by order', () => {
|
||||
const standards = getSortedStandards();
|
||||
|
||||
expect(standards.map((standard) => standard.slug)).toEqual(['erc-3643', 'erc-20']);
|
||||
});
|
||||
|
||||
it('returns featured standards', () => {
|
||||
const featured = getFeaturedStandards();
|
||||
|
||||
expect(featured).toHaveLength(1);
|
||||
expect(featured[0]?.slug).toBe('erc-3643');
|
||||
});
|
||||
|
||||
it('resolves explicit comparison targets', () => {
|
||||
const erc3643 = getSortedStandards()[0]!;
|
||||
const comparisonTarget = getComparisonTarget(erc3643);
|
||||
|
||||
expect(comparisonTarget?.slug).toBe('erc-20');
|
||||
});
|
||||
|
||||
it('returns related standards ordered by score', () => {
|
||||
const erc3643 = getSortedStandards()[0]!;
|
||||
const related = getRelatedStandards(erc3643);
|
||||
|
||||
expect(related.map((standard) => standard.slug)).toContain('erc-20');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,399 @@
|
||||
import type { MarketingLandingPageProps } from '../../components/marketing/landing-page';
|
||||
import { erc20LandingPage } from '../erc20';
|
||||
import { erc3643LandingPage } from '../erc3643';
|
||||
import { standardsRegistrySchema, type StandardRegistryEntrySchema } from './schema';
|
||||
|
||||
export type StandardRegistryEntry = StandardRegistryEntrySchema & {
|
||||
landingPage: MarketingLandingPageProps;
|
||||
};
|
||||
|
||||
const standardsRegistrySource: StandardRegistryEntry[] = [
|
||||
{
|
||||
slug: 'erc-3643',
|
||||
name: 'ERC-3643',
|
||||
shortName: 'Compliant Tokenization',
|
||||
eyebrow: 'Compliant Token Standard',
|
||||
icon: 'shield',
|
||||
summary:
|
||||
'Issue, manage, and distribute regulated digital assets with identity-based compliance enforced directly at the token level.',
|
||||
category: 'Compliance and tokenization',
|
||||
status: 'Live',
|
||||
audience: 'Financial institutions, issuers, tokenization platforms, and regulated RWA teams.',
|
||||
standards: ['ERC-3643', 'ERC-20'],
|
||||
publisher: 'Tokeny',
|
||||
auditor: 'Hacken',
|
||||
seo: {
|
||||
title: 'ERC-3643 | The Order',
|
||||
description:
|
||||
'Compliant tokenization for real-world assets with identity-based compliance, lifecycle automation, and ERC-20 compatibility.',
|
||||
keywords: ['ERC-3643', 'compliant tokenization', 'real-world assets', 'regulated digital assets'],
|
||||
},
|
||||
specification: {
|
||||
label: 'EIP-3643 Specification',
|
||||
href: 'https://eips.ethereum.org/EIPS/eip-3643',
|
||||
kind: 'spec',
|
||||
},
|
||||
docs: [
|
||||
{
|
||||
label: 'Tokeny ERC-3643 Overview',
|
||||
href: 'https://www.erc3643.org/',
|
||||
kind: 'docs',
|
||||
},
|
||||
],
|
||||
repos: [
|
||||
{
|
||||
label: 'ethereum/EIPs',
|
||||
href: 'https://github.com/ethereum/EIPs',
|
||||
kind: 'repo',
|
||||
},
|
||||
{
|
||||
label: 'TokenySolutions/T-REX',
|
||||
href: 'https://github.com/TokenySolutions/T-REX',
|
||||
kind: 'repo',
|
||||
},
|
||||
],
|
||||
submissionPoints: [
|
||||
{
|
||||
label: 'Ethereum EIPs Repository',
|
||||
href: 'https://github.com/ethereum/EIPs',
|
||||
kind: 'submission',
|
||||
},
|
||||
{
|
||||
label: 'EIP-3643 Pull Requests',
|
||||
href: 'https://github.com/ethereum/EIPs/pulls',
|
||||
kind: 'submission',
|
||||
},
|
||||
],
|
||||
compliance: {
|
||||
native: true,
|
||||
label: 'Native compliance',
|
||||
summary:
|
||||
'Identity, eligibility, and transfer rules are enforced directly in token behavior rather than being bolted on externally.',
|
||||
enforcement: [
|
||||
'Identity-aware transfer restrictions',
|
||||
'Eligibility controls for holders and counterparties',
|
||||
'Lifecycle rule updates during issuance and administration',
|
||||
],
|
||||
},
|
||||
governanceBody: 'Ethereum Improvement Proposal process with ecosystem implementation led by Tokeny and related adopters.',
|
||||
maintainers: [
|
||||
{
|
||||
name: 'Ethereum EIPs editors',
|
||||
role: 'Canonical specification governance',
|
||||
href: 'https://github.com/ethereum/EIPs',
|
||||
},
|
||||
{
|
||||
name: 'Tokeny',
|
||||
role: 'Reference implementation and ecosystem steward',
|
||||
href: 'https://github.com/TokenySolutions/T-REX',
|
||||
},
|
||||
],
|
||||
statusHistory: [
|
||||
{
|
||||
date: '2021-11-09',
|
||||
title: 'EIP-3643 created',
|
||||
summary: 'The compliant tokenization standard was introduced through the Ethereum EIP process.',
|
||||
},
|
||||
{
|
||||
date: '2022-01-26',
|
||||
title: 'T-REX reference implementation publicized',
|
||||
summary: 'Production-oriented implementation guidance and contracts became widely available through Tokeny.',
|
||||
},
|
||||
{
|
||||
date: '2024-01-01',
|
||||
title: 'Ecosystem adoption broadened',
|
||||
summary: 'ERC-3643 continued to mature as a practical framework for regulated asset issuance and lifecycle management.',
|
||||
},
|
||||
],
|
||||
implementationExamples: [
|
||||
{
|
||||
label: 'Tokeny T-REX reference contracts',
|
||||
summary: 'Production-oriented smart contract suite implementing identity-aware compliance controls for ERC-3643 assets.',
|
||||
href: 'https://github.com/TokenySolutions/T-REX',
|
||||
},
|
||||
{
|
||||
label: 'EIP-3643 canonical specification',
|
||||
summary: 'The normative source for interface behavior, goals, and compliance-oriented design expectations.',
|
||||
href: 'https://eips.ethereum.org/EIPS/eip-3643',
|
||||
},
|
||||
],
|
||||
submissionChecklist: [
|
||||
{
|
||||
label: 'Canonical EIP specification reference',
|
||||
required: true,
|
||||
summary: 'Point to the accepted EIP text and normative standard behavior.',
|
||||
},
|
||||
{
|
||||
label: 'Reference implementation repository',
|
||||
required: true,
|
||||
summary: 'Include a public implementation repo that demonstrates how the standard is realized in production.',
|
||||
},
|
||||
{
|
||||
label: 'Compliance posture declaration',
|
||||
required: true,
|
||||
summary: 'Document whether compliance is natively enforced or relies on external controls.',
|
||||
},
|
||||
{
|
||||
label: 'Submission points and governance path',
|
||||
required: true,
|
||||
summary: 'Identify the repo, pull request surface, and governance venue for amendments or follow-on proposals.',
|
||||
},
|
||||
],
|
||||
ecosystemSupport: [
|
||||
{
|
||||
area: 'Wallet compatibility',
|
||||
status: 'Moderate',
|
||||
summary: 'Leverages ERC-20 compatibility, though full compliance-aware workflows depend on supporting infrastructure.',
|
||||
},
|
||||
{
|
||||
area: 'Exchange and marketplace support',
|
||||
status: 'Specialized',
|
||||
summary: 'Best fit for venues prepared to handle regulated asset onboarding and rule-aware transfer flows.',
|
||||
},
|
||||
{
|
||||
area: 'Issuer administration tooling',
|
||||
status: 'Strong',
|
||||
summary: 'Well suited to issuer-facing workflows, lifecycle events, and identity-driven operating models.',
|
||||
},
|
||||
{
|
||||
area: 'Regulated asset operations',
|
||||
status: 'Strong',
|
||||
summary: 'Purpose-built for permissioned or regulated issuance where transfer and holder eligibility matter.',
|
||||
},
|
||||
],
|
||||
comparisonTarget: 'erc-20',
|
||||
featured: true,
|
||||
order: 10,
|
||||
href: '/standards/erc-3643',
|
||||
landingPage: erc3643LandingPage,
|
||||
},
|
||||
{
|
||||
slug: 'erc-20',
|
||||
name: 'ERC-20',
|
||||
shortName: 'Fungible Token Baseline',
|
||||
eyebrow: 'Foundational Token Standard',
|
||||
icon: 'coins',
|
||||
summary:
|
||||
'The core fungible token standard for broad wallet, exchange, and application compatibility across the Ethereum ecosystem.',
|
||||
category: 'General-purpose tokenization',
|
||||
status: 'Live',
|
||||
audience: 'Protocol teams, issuers, exchanges, treasury teams, and builders focused on interoperability.',
|
||||
standards: ['ERC-20'],
|
||||
seo: {
|
||||
title: 'ERC-20 | The Order',
|
||||
description:
|
||||
'The foundational fungible token standard for broad wallet, exchange, and application compatibility across Ethereum.',
|
||||
keywords: ['ERC-20', 'fungible token', 'Ethereum token standard', 'token interoperability'],
|
||||
},
|
||||
specification: {
|
||||
label: 'EIP-20 Specification',
|
||||
href: 'https://eips.ethereum.org/EIPS/eip-20',
|
||||
kind: 'spec',
|
||||
},
|
||||
docs: [
|
||||
{
|
||||
label: 'OpenZeppelin ERC-20 Guide',
|
||||
href: 'https://docs.openzeppelin.com/contracts/4.x/erc20',
|
||||
kind: 'docs',
|
||||
},
|
||||
],
|
||||
repos: [
|
||||
{
|
||||
label: 'ethereum/EIPs',
|
||||
href: 'https://github.com/ethereum/EIPs',
|
||||
kind: 'repo',
|
||||
},
|
||||
{
|
||||
label: 'OpenZeppelin/openzeppelin-contracts',
|
||||
href: 'https://github.com/OpenZeppelin/openzeppelin-contracts',
|
||||
kind: 'repo',
|
||||
},
|
||||
],
|
||||
submissionPoints: [
|
||||
{
|
||||
label: 'Ethereum EIPs Repository',
|
||||
href: 'https://github.com/ethereum/EIPs',
|
||||
kind: 'submission',
|
||||
},
|
||||
{
|
||||
label: 'EIP-20 Pull Requests',
|
||||
href: 'https://github.com/ethereum/EIPs/pulls',
|
||||
kind: 'submission',
|
||||
},
|
||||
],
|
||||
compliance: {
|
||||
native: false,
|
||||
label: 'External compliance required',
|
||||
summary:
|
||||
'ERC-20 does not natively enforce compliance. Regulated issuance typically depends on wrappers, policy layers, or additional contract systems.',
|
||||
enforcement: [
|
||||
'External policy engines or offchain controls',
|
||||
'Custom transfer restrictions when needed',
|
||||
'Operational overlays for regulated counterparties',
|
||||
],
|
||||
},
|
||||
governanceBody: 'Ethereum Improvement Proposal process with widespread ecosystem stewardship across libraries, wallets, and exchanges.',
|
||||
maintainers: [
|
||||
{
|
||||
name: 'Ethereum EIPs editors',
|
||||
role: 'Canonical specification governance',
|
||||
href: 'https://github.com/ethereum/EIPs',
|
||||
},
|
||||
{
|
||||
name: 'OpenZeppelin',
|
||||
role: 'Widely used implementation maintainer',
|
||||
href: 'https://github.com/OpenZeppelin/openzeppelin-contracts',
|
||||
},
|
||||
],
|
||||
statusHistory: [
|
||||
{
|
||||
date: '2015-11-19',
|
||||
title: 'EIP-20 proposed',
|
||||
summary: 'The fungible token interface was introduced as a standard for interoperable Ethereum assets.',
|
||||
},
|
||||
{
|
||||
date: '2017-09-20',
|
||||
title: 'EIP-20 finalized',
|
||||
summary: 'ERC-20 became the canonical baseline for fungible token behavior across the ecosystem.',
|
||||
},
|
||||
{
|
||||
date: '2024-01-01',
|
||||
title: 'Baseline ecosystem standard maintained',
|
||||
summary: 'ERC-20 remains the reference layer for fungible token interoperability and exchange integration.',
|
||||
},
|
||||
],
|
||||
implementationExamples: [
|
||||
{
|
||||
label: 'OpenZeppelin ERC-20 implementation',
|
||||
summary: 'Widely used and audited Solidity implementation for fungible token behavior.',
|
||||
href: 'https://github.com/OpenZeppelin/openzeppelin-contracts',
|
||||
},
|
||||
{
|
||||
label: 'EIP-20 canonical specification',
|
||||
summary: 'Normative interface definition for fungible token balances, transfers, and approvals.',
|
||||
href: 'https://eips.ethereum.org/EIPS/eip-20',
|
||||
},
|
||||
],
|
||||
submissionChecklist: [
|
||||
{
|
||||
label: 'Canonical EIP specification reference',
|
||||
required: true,
|
||||
summary: 'Link the accepted EIP text as the normative basis for the registry entry.',
|
||||
},
|
||||
{
|
||||
label: 'Reference implementation repository',
|
||||
required: true,
|
||||
summary: 'Provide at least one widely adopted implementation repo to anchor ecosystem usage.',
|
||||
},
|
||||
{
|
||||
label: 'Compliance dependency disclosure',
|
||||
required: true,
|
||||
summary: 'State clearly that compliance is external if the token standard does not enforce it natively.',
|
||||
},
|
||||
{
|
||||
label: 'Submission and amendment path',
|
||||
required: true,
|
||||
summary: 'Document the repository and PR path for future revisions to the standard.',
|
||||
},
|
||||
],
|
||||
ecosystemSupport: [
|
||||
{
|
||||
area: 'Wallet compatibility',
|
||||
status: 'Strong',
|
||||
summary: 'Near-universal support across Ethereum wallets and custody providers.',
|
||||
},
|
||||
{
|
||||
area: 'Exchange and marketplace support',
|
||||
status: 'Strong',
|
||||
summary: 'Deep support across centralized and decentralized trading venues.',
|
||||
},
|
||||
{
|
||||
area: 'Issuer administration tooling',
|
||||
status: 'Moderate',
|
||||
summary: 'Strong for basic issuance, though advanced compliance workflows usually require add-on systems.',
|
||||
},
|
||||
{
|
||||
area: 'Regulated asset operations',
|
||||
status: 'Specialized',
|
||||
summary: 'Can be used as a base layer, but regulated issuance typically needs additional policy and control infrastructure.',
|
||||
},
|
||||
],
|
||||
comparisonTarget: 'erc-3643',
|
||||
featured: false,
|
||||
order: 20,
|
||||
href: '/standards/erc-20',
|
||||
landingPage: erc20LandingPage,
|
||||
},
|
||||
];
|
||||
|
||||
export const standardsRegistry = standardsRegistrySchema.parse(standardsRegistrySource) as StandardRegistryEntry[];
|
||||
|
||||
export function getSortedStandards(): StandardRegistryEntry[] {
|
||||
return [...standardsRegistry].sort((left, right) => {
|
||||
if (left.order !== right.order) {
|
||||
return left.order - right.order;
|
||||
}
|
||||
|
||||
return left.name.localeCompare(right.name);
|
||||
});
|
||||
}
|
||||
|
||||
export function getFeaturedStandards(): StandardRegistryEntry[] {
|
||||
return getSortedStandards().filter((entry) => entry.featured);
|
||||
}
|
||||
|
||||
export function getStandardBySlug(slug: string): StandardRegistryEntry | undefined {
|
||||
return standardsRegistry.find((entry) => entry.slug === slug);
|
||||
}
|
||||
|
||||
export function getComparisonTarget(standard: StandardRegistryEntry): StandardRegistryEntry | undefined {
|
||||
if (!standard.comparisonTarget) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getStandardBySlug(standard.comparisonTarget);
|
||||
}
|
||||
|
||||
export function getRelatedStandards(
|
||||
standard: StandardRegistryEntry,
|
||||
limit = 2
|
||||
): StandardRegistryEntry[] {
|
||||
const others = getSortedStandards().filter((entry) => entry.slug !== standard.slug);
|
||||
|
||||
const scored = others.map((entry) => {
|
||||
let score = 0;
|
||||
|
||||
if (entry.slug === standard.comparisonTarget) {
|
||||
score += 100;
|
||||
}
|
||||
|
||||
if (entry.comparisonTarget === standard.slug) {
|
||||
score += 80;
|
||||
}
|
||||
|
||||
if (entry.category === standard.category) {
|
||||
score += 30;
|
||||
}
|
||||
|
||||
const sharedStandards = entry.standards.filter((item) => standard.standards.includes(item)).length;
|
||||
score += sharedStandards * 10;
|
||||
|
||||
if (entry.compliance.native === standard.compliance.native) {
|
||||
score += 5;
|
||||
}
|
||||
|
||||
return { entry, score };
|
||||
});
|
||||
|
||||
return scored
|
||||
.filter((item) => item.score > 0)
|
||||
.sort((left, right) => {
|
||||
if (left.score !== right.score) {
|
||||
return right.score - left.score;
|
||||
}
|
||||
|
||||
return left.entry.order - right.entry.order;
|
||||
})
|
||||
.slice(0, limit)
|
||||
.map((item) => item.entry);
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
export const standardsRegistryJsonSchema = {
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||
$id: 'https://the-order.local/standards/schema.json',
|
||||
title: 'The Order Standards Registry',
|
||||
description: 'Canonical schema for standards registry entries used by the public portal.',
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/$defs/standardRegistryEntry',
|
||||
},
|
||||
$defs: {
|
||||
standardIcon: {
|
||||
type: 'string',
|
||||
enum: ['shield', 'coins', 'scale', 'network', 'file-stack'],
|
||||
},
|
||||
standardLink: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['label', 'href', 'kind'],
|
||||
properties: {
|
||||
label: { type: 'string', minLength: 1 },
|
||||
href: { type: 'string', format: 'uri' },
|
||||
kind: { type: 'string', enum: ['spec', 'repo', 'docs', 'submission'] },
|
||||
},
|
||||
},
|
||||
seo: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['title', 'description'],
|
||||
properties: {
|
||||
title: { type: 'string', minLength: 1 },
|
||||
description: { type: 'string', minLength: 1 },
|
||||
keywords: {
|
||||
type: 'array',
|
||||
items: { type: 'string', minLength: 1 },
|
||||
},
|
||||
},
|
||||
},
|
||||
complianceProfile: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['native', 'label', 'summary', 'enforcement'],
|
||||
properties: {
|
||||
native: { type: 'boolean' },
|
||||
label: { type: 'string', minLength: 1 },
|
||||
summary: { type: 'string', minLength: 1 },
|
||||
enforcement: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
items: { type: 'string', minLength: 1 },
|
||||
},
|
||||
},
|
||||
},
|
||||
maintainer: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['name', 'role'],
|
||||
properties: {
|
||||
name: { type: 'string', minLength: 1 },
|
||||
role: { type: 'string', minLength: 1 },
|
||||
href: { type: 'string', format: 'uri' },
|
||||
},
|
||||
},
|
||||
statusEvent: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['date', 'title', 'summary'],
|
||||
properties: {
|
||||
date: { type: 'string', minLength: 1 },
|
||||
title: { type: 'string', minLength: 1 },
|
||||
summary: { type: 'string', minLength: 1 },
|
||||
},
|
||||
},
|
||||
implementationExample: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['label', 'summary', 'href'],
|
||||
properties: {
|
||||
label: { type: 'string', minLength: 1 },
|
||||
summary: { type: 'string', minLength: 1 },
|
||||
href: { type: 'string', format: 'uri' },
|
||||
},
|
||||
},
|
||||
checklistItem: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['label', 'required', 'summary'],
|
||||
properties: {
|
||||
label: { type: 'string', minLength: 1 },
|
||||
required: { type: 'boolean' },
|
||||
summary: { type: 'string', minLength: 1 },
|
||||
},
|
||||
},
|
||||
ecosystemSupport: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['area', 'status', 'summary'],
|
||||
properties: {
|
||||
area: { type: 'string', minLength: 1 },
|
||||
status: { type: 'string', enum: ['Strong', 'Moderate', 'Specialized'] },
|
||||
summary: { type: 'string', minLength: 1 },
|
||||
},
|
||||
},
|
||||
marketingLink: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['label', 'href'],
|
||||
properties: {
|
||||
label: { type: 'string', minLength: 1 },
|
||||
href: { type: 'string', minLength: 1 },
|
||||
},
|
||||
},
|
||||
standardRegistryEntry: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: [
|
||||
'slug',
|
||||
'name',
|
||||
'icon',
|
||||
'summary',
|
||||
'category',
|
||||
'status',
|
||||
'audience',
|
||||
'standards',
|
||||
'seo',
|
||||
'specification',
|
||||
'repos',
|
||||
'submissionPoints',
|
||||
'compliance',
|
||||
'governanceBody',
|
||||
'maintainers',
|
||||
'statusHistory',
|
||||
'implementationExamples',
|
||||
'submissionChecklist',
|
||||
'ecosystemSupport',
|
||||
'order',
|
||||
'href',
|
||||
'landingPage',
|
||||
],
|
||||
properties: {
|
||||
slug: { type: 'string', minLength: 1 },
|
||||
name: { type: 'string', minLength: 1 },
|
||||
shortName: { type: 'string', minLength: 1 },
|
||||
eyebrow: { type: 'string', minLength: 1 },
|
||||
icon: { $ref: '#/$defs/standardIcon' },
|
||||
summary: { type: 'string', minLength: 1 },
|
||||
category: { type: 'string', minLength: 1 },
|
||||
status: { type: 'string', enum: ['Live', 'Preview'] },
|
||||
audience: { type: 'string', minLength: 1 },
|
||||
standards: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
items: { type: 'string', minLength: 1 },
|
||||
},
|
||||
publisher: { type: 'string', minLength: 1 },
|
||||
auditor: { type: 'string', minLength: 1 },
|
||||
seo: { $ref: '#/$defs/seo' },
|
||||
specification: { $ref: '#/$defs/standardLink' },
|
||||
docs: {
|
||||
type: 'array',
|
||||
items: { $ref: '#/$defs/standardLink' },
|
||||
},
|
||||
repos: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
items: { $ref: '#/$defs/standardLink' },
|
||||
},
|
||||
submissionPoints: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
items: { $ref: '#/$defs/standardLink' },
|
||||
},
|
||||
compliance: { $ref: '#/$defs/complianceProfile' },
|
||||
governanceBody: { type: 'string', minLength: 1 },
|
||||
maintainers: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
items: { $ref: '#/$defs/maintainer' },
|
||||
},
|
||||
statusHistory: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
items: { $ref: '#/$defs/statusEvent' },
|
||||
},
|
||||
implementationExamples: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
items: { $ref: '#/$defs/implementationExample' },
|
||||
},
|
||||
submissionChecklist: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
items: { $ref: '#/$defs/checklistItem' },
|
||||
},
|
||||
ecosystemSupport: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
items: { $ref: '#/$defs/ecosystemSupport' },
|
||||
},
|
||||
comparisonTarget: { type: 'string', minLength: 1 },
|
||||
featured: { type: 'boolean' },
|
||||
order: { type: 'integer', minimum: 0 },
|
||||
href: { type: 'string', minLength: 1 },
|
||||
landingPage: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['hero'],
|
||||
properties: {
|
||||
hero: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['headline', 'subheadline'],
|
||||
properties: {
|
||||
eyebrow: { type: 'string' },
|
||||
headline: { type: 'string' },
|
||||
subheadline: { type: 'string' },
|
||||
supportingCopy: { type: 'string' },
|
||||
primaryCta: { $ref: '#/$defs/marketingLink' },
|
||||
secondaryCta: { $ref: '#/$defs/marketingLink' },
|
||||
tertiaryLink: { $ref: '#/$defs/marketingLink' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -0,0 +1,151 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const standardIconSchema = z.enum(['shield', 'coins', 'scale', 'network', 'file-stack']);
|
||||
|
||||
export const standardLinkSchema = z.object({
|
||||
label: z.string().min(1),
|
||||
href: z.string().url(),
|
||||
kind: z.enum(['spec', 'repo', 'docs', 'submission']),
|
||||
});
|
||||
|
||||
export const standardSeoSchema = z.object({
|
||||
title: z.string().min(1),
|
||||
description: z.string().min(1),
|
||||
keywords: z.array(z.string().min(1)).optional(),
|
||||
});
|
||||
|
||||
export const standardComplianceProfileSchema = z.object({
|
||||
native: z.boolean(),
|
||||
label: z.string().min(1),
|
||||
summary: z.string().min(1),
|
||||
enforcement: z.array(z.string().min(1)).min(1),
|
||||
});
|
||||
|
||||
export const standardMaintainerSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
role: z.string().min(1),
|
||||
href: z.string().url().optional(),
|
||||
});
|
||||
|
||||
export const standardStatusEventSchema = z.object({
|
||||
date: z.string().min(1),
|
||||
title: z.string().min(1),
|
||||
summary: z.string().min(1),
|
||||
});
|
||||
|
||||
export const standardImplementationExampleSchema = z.object({
|
||||
label: z.string().min(1),
|
||||
summary: z.string().min(1),
|
||||
href: z.string().url(),
|
||||
});
|
||||
|
||||
export const standardChecklistItemSchema = z.object({
|
||||
label: z.string().min(1),
|
||||
required: z.boolean(),
|
||||
summary: z.string().min(1),
|
||||
});
|
||||
|
||||
export const standardEcosystemSupportItemSchema = z.object({
|
||||
area: z.string().min(1),
|
||||
status: z.enum(['Strong', 'Moderate', 'Specialized']),
|
||||
summary: z.string().min(1),
|
||||
});
|
||||
|
||||
export const standardRegistryEntrySchema = z.object({
|
||||
slug: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
shortName: z.string().min(1).optional(),
|
||||
eyebrow: z.string().min(1).optional(),
|
||||
icon: standardIconSchema,
|
||||
summary: z.string().min(1),
|
||||
category: z.string().min(1),
|
||||
status: z.enum(['Live', 'Preview']),
|
||||
audience: z.string().min(1),
|
||||
standards: z.array(z.string().min(1)).min(1),
|
||||
publisher: z.string().min(1).optional(),
|
||||
auditor: z.string().min(1).optional(),
|
||||
seo: standardSeoSchema,
|
||||
specification: standardLinkSchema,
|
||||
docs: z.array(standardLinkSchema).optional(),
|
||||
repos: z.array(standardLinkSchema).min(1),
|
||||
submissionPoints: z.array(standardLinkSchema).min(1),
|
||||
compliance: standardComplianceProfileSchema,
|
||||
governanceBody: z.string().min(1),
|
||||
maintainers: z.array(standardMaintainerSchema).min(1),
|
||||
statusHistory: z.array(standardStatusEventSchema).min(1),
|
||||
implementationExamples: z.array(standardImplementationExampleSchema).min(1),
|
||||
submissionChecklist: z.array(standardChecklistItemSchema).min(1),
|
||||
ecosystemSupport: z.array(standardEcosystemSupportItemSchema).min(1),
|
||||
comparisonTarget: z.string().min(1).optional(),
|
||||
featured: z.boolean().optional(),
|
||||
order: z.number().int().nonnegative(),
|
||||
href: z.string().min(1),
|
||||
landingPage: z.object({
|
||||
hero: z.object({
|
||||
eyebrow: z.string().optional(),
|
||||
headline: z.string(),
|
||||
subheadline: z.string(),
|
||||
supportingCopy: z.string().optional(),
|
||||
primaryCta: z.object({ label: z.string(), href: z.string() }).optional(),
|
||||
secondaryCta: z.object({ label: z.string(), href: z.string() }).optional(),
|
||||
tertiaryLink: z.object({ label: z.string(), href: z.string() }).optional(),
|
||||
}),
|
||||
trustStrip: z
|
||||
.object({
|
||||
label: z.string().optional(),
|
||||
items: z.array(z.object({ label: z.string(), value: z.string() })),
|
||||
})
|
||||
.optional(),
|
||||
valueProps: z
|
||||
.object({
|
||||
headline: z.string(),
|
||||
intro: z.string().optional(),
|
||||
cards: z.array(z.object({ title: z.string(), copy: z.string() })),
|
||||
})
|
||||
.optional(),
|
||||
useCases: z
|
||||
.object({
|
||||
headline: z.string(),
|
||||
intro: z.string().optional(),
|
||||
items: z.array(z.string()),
|
||||
supportingLine: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
proofPoints: z
|
||||
.object({
|
||||
headline: z.string(),
|
||||
intro: z.string().optional(),
|
||||
items: z.array(z.string()),
|
||||
})
|
||||
.optional(),
|
||||
comparison: z
|
||||
.object({
|
||||
headline: z.string(),
|
||||
intro: z.string().optional(),
|
||||
columns: z.tuple([z.string(), z.string(), z.string()]),
|
||||
rows: z.array(z.object({ category: z.string(), left: z.string(), right: z.string() })),
|
||||
secondaryNote: z.string().optional(),
|
||||
cta: z.object({ label: z.string(), href: z.string() }).optional(),
|
||||
})
|
||||
.optional(),
|
||||
faq: z
|
||||
.object({
|
||||
headline: z.string(),
|
||||
items: z.array(z.object({ question: z.string(), answer: z.string() })),
|
||||
})
|
||||
.optional(),
|
||||
footerCta: z
|
||||
.object({
|
||||
headline: z.string(),
|
||||
copy: z.string(),
|
||||
primaryCta: z.object({ label: z.string(), href: z.string() }).optional(),
|
||||
secondaryCta: z.object({ label: z.string(), href: z.string() }).optional(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const standardsRegistrySchema = z.array(standardRegistryEntrySchema);
|
||||
|
||||
export type StandardIconName = z.infer<typeof standardIconSchema>;
|
||||
export type StandardRegistryEntrySchema = z.infer<typeof standardRegistryEntrySchema>;
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { StandardRegistryEntry } from './index';
|
||||
|
||||
export const standardsRegistryTemplate: StandardRegistryEntry = {
|
||||
slug: 'new-standard-slug',
|
||||
name: 'New Standard',
|
||||
shortName: 'Short positioning label',
|
||||
eyebrow: 'Optional eyebrow',
|
||||
icon: 'file-stack',
|
||||
summary: 'One-sentence summary for cards, search, SEO previews, and registry exports.',
|
||||
category: 'Category label',
|
||||
status: 'Preview',
|
||||
audience: 'Who this standard is for.',
|
||||
standards: ['PRIMARY-STANDARD-ID'],
|
||||
publisher: 'Optional publisher',
|
||||
auditor: 'Optional auditor',
|
||||
seo: {
|
||||
title: 'New Standard | The Order',
|
||||
description: 'SEO description for the detail page.',
|
||||
keywords: ['keyword-one', 'keyword-two'],
|
||||
},
|
||||
specification: {
|
||||
label: 'Canonical specification',
|
||||
href: 'https://example.com/spec',
|
||||
kind: 'spec',
|
||||
},
|
||||
docs: [
|
||||
{
|
||||
label: 'Documentation link',
|
||||
href: 'https://example.com/docs',
|
||||
kind: 'docs',
|
||||
},
|
||||
],
|
||||
repos: [
|
||||
{
|
||||
label: 'Reference repo',
|
||||
href: 'https://github.com/example/repo',
|
||||
kind: 'repo',
|
||||
},
|
||||
],
|
||||
submissionPoints: [
|
||||
{
|
||||
label: 'Canonical submission point',
|
||||
href: 'https://github.com/example/repo/pulls',
|
||||
kind: 'submission',
|
||||
},
|
||||
],
|
||||
compliance: {
|
||||
native: false,
|
||||
label: 'External compliance required',
|
||||
summary: 'State clearly whether compliance is native or external.',
|
||||
enforcement: ['Describe enforcement mechanism'],
|
||||
},
|
||||
governanceBody: 'Who governs the standard and how changes are made.',
|
||||
maintainers: [
|
||||
{
|
||||
name: 'Maintainer name',
|
||||
role: 'Maintainer role',
|
||||
href: 'https://example.com/maintainer',
|
||||
},
|
||||
],
|
||||
statusHistory: [
|
||||
{
|
||||
date: 'YYYY-MM-DD',
|
||||
title: 'Milestone title',
|
||||
summary: 'Why this milestone matters.',
|
||||
},
|
||||
],
|
||||
implementationExamples: [
|
||||
{
|
||||
label: 'Example implementation',
|
||||
summary: 'Why this implementation is relevant.',
|
||||
href: 'https://example.com/example',
|
||||
},
|
||||
],
|
||||
submissionChecklist: [
|
||||
{
|
||||
label: 'Checklist item',
|
||||
required: true,
|
||||
summary: 'What this item proves or covers.',
|
||||
},
|
||||
],
|
||||
ecosystemSupport: [
|
||||
{
|
||||
area: 'Wallet compatibility',
|
||||
status: 'Moderate',
|
||||
summary: 'How well the standard is supported here.',
|
||||
},
|
||||
],
|
||||
comparisonTarget: 'existing-standard-slug',
|
||||
featured: false,
|
||||
order: 999,
|
||||
href: '/standards/new-standard-slug',
|
||||
landingPage: {
|
||||
hero: {
|
||||
headline: 'Hero headline',
|
||||
subheadline: 'Hero subheadline',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -87,6 +87,16 @@ class AuthStore {
|
||||
|
||||
const authStore = typeof window !== 'undefined' ? new AuthStore() : null;
|
||||
|
||||
function writeAuthCookie(accessToken?: string): void {
|
||||
if (typeof document === 'undefined') return;
|
||||
if (!accessToken) {
|
||||
document.cookie = 'auth_token=; Path=/; Max-Age=0; SameSite=Lax';
|
||||
return;
|
||||
}
|
||||
|
||||
document.cookie = `auth_token=${encodeURIComponent(accessToken)}; Path=/; Max-Age=2592000; SameSite=Lax`;
|
||||
}
|
||||
|
||||
// React hook to use auth state
|
||||
export function useAuth(): AuthState {
|
||||
const [state, setState] = React.useState<AuthState>(
|
||||
@@ -114,13 +124,16 @@ export function useAuth(): AuthState {
|
||||
if (user.accessToken) {
|
||||
localStorage.setItem('auth_token', user.accessToken);
|
||||
}
|
||||
writeAuthCookie(user.accessToken);
|
||||
},
|
||||
logout: () => {
|
||||
authStore?.setState({ user: null, isAuthenticated: false });
|
||||
localStorage.removeItem('auth_token');
|
||||
writeAuthCookie();
|
||||
},
|
||||
setUser: (user: AuthUser | null) => {
|
||||
authStore?.setState({ user, isAuthenticated: !!user });
|
||||
writeAuthCookie(user?.accessToken);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from '../../../../packages/ui/src/server';
|
||||
@@ -4,7 +4,7 @@ import type { NextRequest } from 'next/server';
|
||||
export function middleware(request: NextRequest): NextResponse {
|
||||
// Public portal routes are generally open
|
||||
// Only protect specific routes if needed
|
||||
const protectedRoutes: string[] = []; // Add protected routes here if needed
|
||||
const protectedRoutes: string[] = ['/standards/admin'];
|
||||
|
||||
const isProtectedRoute = protectedRoutes.some((route) => request.nextUrl.pathname.startsWith(route));
|
||||
|
||||
@@ -22,6 +22,5 @@ export function middleware(request: NextRequest): NextResponse {
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [], // Add protected routes here if needed
|
||||
matcher: ['/standards/admin/:path*'],
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"rootDir": "../../",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -22,10 +23,17 @@
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@the-order/ui": ["../../packages/ui/src"],
|
||||
"@the-order/ui/server": ["../../packages/ui/src/server"],
|
||||
"@the-order/schemas": ["../../packages/schemas/src"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"../../packages/ui/src/**/*.ts",
|
||||
"../../packages/ui/src/**/*.tsx"
|
||||
],
|
||||
"exclude": ["node_modules", "**/*.test.ts", "vitest.config.mts"]
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'node',
|
||||
include: ['src/**/*.test.ts'],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Server-safe UI exports.
|
||||
*
|
||||
* Keep this entrypoint limited to components and utilities that do not depend
|
||||
* on client-only hooks or browser APIs so Next.js server components can import
|
||||
* from it safely.
|
||||
*/
|
||||
|
||||
export { Button } from './components/Button';
|
||||
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './components/Card';
|
||||
export { Input } from './components/Input';
|
||||
export { Label } from './components/Label';
|
||||
export { Select } from './components/Select';
|
||||
export { Textarea } from './components/Textarea';
|
||||
export { Alert, AlertTitle, AlertDescription } from './components/Alert';
|
||||
export { Badge } from './components/Badge';
|
||||
export { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from './components/Table';
|
||||
export { Skeleton } from './components/Skeleton';
|
||||
export { Breadcrumbs } from './components/Breadcrumbs';
|
||||
export { Checkbox } from './components/Checkbox';
|
||||
export { Radio } from './components/Radio';
|
||||
export { Switch } from './components/Switch';
|
||||
export { cn } from './lib/utils';
|
||||
Generated
+20
-9
@@ -175,7 +175,7 @@ importers:
|
||||
version: 8.5.6
|
||||
tailwindcss:
|
||||
specifier: ^3.4.1
|
||||
version: 3.4.18
|
||||
version: 3.4.18([email protected])
|
||||
tailwindcss-animate:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7([email protected])
|
||||
@@ -249,6 +249,9 @@ importers:
|
||||
autoprefixer:
|
||||
specifier: ^10.4.16
|
||||
version: 10.4.22([email protected])
|
||||
baseline-browser-mapping:
|
||||
specifier: ^2.10.19
|
||||
version: 2.10.19
|
||||
eslint:
|
||||
specifier: ^8.57.1
|
||||
version: 8.57.1
|
||||
@@ -260,13 +263,19 @@ importers:
|
||||
version: 8.5.6
|
||||
tailwindcss:
|
||||
specifier: ^3.4.1
|
||||
version: 3.4.18
|
||||
version: 3.4.18([email protected])
|
||||
tailwindcss-animate:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7([email protected])
|
||||
tsx:
|
||||
specifier: ^4.20.5
|
||||
version: 4.20.6
|
||||
typescript:
|
||||
specifier: ^5.3.3
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^1.6.1
|
||||
version: 1.6.1(@types/[email protected])(@vitest/[email protected])
|
||||
|
||||
packages/api-client:
|
||||
dependencies:
|
||||
@@ -6495,8 +6504,9 @@ packages:
|
||||
/[email protected]:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
/baseline-browser-mapping@2.8.27:
|
||||
resolution: {integrity: sha512-2CXFpkjVnY2FT+B6GrSYxzYf65BJWEqz5tIRHCvNsZZ2F3CmsCB37h8SpYgKG7y9C4YAeTipIPWG7EmFmhAeXA==}
|
||||
/baseline-browser-mapping@2.10.19:
|
||||
resolution: {integrity: sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
@@ -6563,7 +6573,7 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
baseline-browser-mapping: 2.8.27
|
||||
baseline-browser-mapping: 2.10.19
|
||||
caniuse-lite: 1.0.30001754
|
||||
electron-to-chromium: 1.5.250
|
||||
node-releases: 2.0.27
|
||||
@@ -10324,7 +10334,7 @@ packages:
|
||||
postcss: 8.5.6
|
||||
dev: true
|
||||
|
||||
/[email protected]([email protected])([email protected]):
|
||||
/[email protected]([email protected])([email protected])([email protected]):
|
||||
resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
|
||||
engines: {node: '>= 18'}
|
||||
peerDependencies:
|
||||
@@ -10345,6 +10355,7 @@ packages:
|
||||
jiti: 1.21.7
|
||||
lilconfig: 3.1.3
|
||||
postcss: 8.5.6
|
||||
tsx: 4.20.6
|
||||
dev: true
|
||||
|
||||
/[email protected]([email protected]):
|
||||
@@ -11471,10 +11482,10 @@ packages:
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders'
|
||||
dependencies:
|
||||
tailwindcss: 3.4.18
|
||||
tailwindcss: 3.4.18([email protected])
|
||||
dev: true
|
||||
|
||||
/[email protected]:
|
||||
/[email protected]([email protected]):
|
||||
resolution: {integrity: sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
@@ -11496,7 +11507,7 @@ packages:
|
||||
postcss: 8.5.6
|
||||
postcss-import: 15.1.0([email protected])
|
||||
postcss-js: 4.1.0([email protected])
|
||||
postcss-load-config: 6.0.1([email protected])([email protected])
|
||||
postcss-load-config: 6.0.1([email protected])([email protected])([email protected])
|
||||
postcss-nested: 6.2.0([email protected])
|
||||
postcss-selector-parser: 6.1.2
|
||||
resolve: 1.22.11
|
||||
|
||||
+5
-6
@@ -30,6 +30,7 @@
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@the-order/ui": ["./packages/ui/src"],
|
||||
"@the-order/ui/server": ["./packages/ui/src/server"],
|
||||
"@the-order/schemas": ["./packages/schemas/src"],
|
||||
"@the-order/auth": ["./packages/auth/src"],
|
||||
"@the-order/storage": ["./packages/storage/src"],
|
||||
@@ -46,12 +47,10 @@
|
||||
"@the-order/notifications": ["./packages/notifications/src"],
|
||||
"@the-order/eu-lp": ["./packages/eu-lp/src"],
|
||||
"@the-order/cache": ["./packages/cache/src"],
|
||||
"@the-order/secrets": ["./packages/secrets/src"],
|
||||
"@the-order/workflows": ["./packages/workflows/src"],
|
||||
"@the-order/verifier-sdk": ["./packages/verifier-sdk/src"],
|
||||
"@the-order/api-client": ["./packages/api-client/src"]
|
||||
}
|
||||
"@the-order/secrets": ["./packages/secrets/src"],
|
||||
"@the-order/verifier-sdk": ["./packages/verifier-sdk/src"],
|
||||
"@the-order/api-client": ["./packages/api-client/src"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "build", ".next", "coverage"]
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user