diff --git a/apps/docs/docusaurus.config.ts b/apps/docs/docusaurus.config.ts index f335d59a..906900e7 100644 --- a/apps/docs/docusaurus.config.ts +++ b/apps/docs/docusaurus.config.ts @@ -253,7 +253,7 @@ const config: Config = { markdown: { mermaid: true, }, - plugins: [tailwindPlugin, '@docusaurus/theme-mermaid', 'docusaurus-plugin-image-zoom'], + plugins: [tailwindPlugin, '@docusaurus/theme-mermaid', 'docusaurus-plugin-image-zoom', 'docusaurus-plugin-sass'], presets: [ [ 'classic', @@ -323,23 +323,72 @@ const config: Config = { items: hideDocs ? undefined : [ + { + type: 'dropdown', + label: 'Developers', + position: 'left', + items: [ + { + label: 'Documentation', + to: '/docs', + }, + { + label: 'Community', + href: 'https://discord.gg/4R568nZgsT', + }, + { + label: 'Changelog', + to: '/blog/tags/releases', + }, + { + label: 'Github', + href: 'https://github.com/o2sdev/openselfservice', + }, + ], + }, { - type: 'search', - position: 'right', + type: 'dropdown', + label: 'Resources', + position: 'left', + items: [ + { + label: 'Blog', + to: '/blog', + }, + { + label: 'Roadmap', + href: 'https://github.com/orgs/o2sdev/projects/2', + }, + ], }, { - type: 'docSidebar', - sidebarId: 'tutorialSidebar', - position: 'right', - label: 'Docs', - className: 'navbar__item--docs', + type: 'dropdown', + label: 'Support', + position: 'left', + items: [ + { + label: 'For developers', + to: '/support/developers', + }, + { + label: 'Enterprise support', + to: '/support/enterprise', + }, + { + label: 'Contact us', + to: '/contact', + }, + ], + }, + { + label: 'Partners', + to: '/partners', + position: 'left', }, - { to: '/blog', label: 'Blog', position: 'right', className: 'navbar__item--guides' }, + { - to: 'https://discord.gg/4R568nZgsT', - label: 'Discord', + type: 'search', position: 'right', - className: 'navbar__item--discord', }, { to: 'https://github.com/o2sdev/openselfservice', @@ -348,18 +397,16 @@ const config: Config = { className: 'navbar__item--github', }, { - to: '/dxp', - // label: 'DXP Starter', + to: '/contact', + label: 'Contact us', position: 'right', className: 'button button-tertiary', - html: 'DXP Starter', }, - { - to: '/contact', - label: 'Contact us', + to: '/dxp', + label: 'DXP Starter', position: 'right', - className: 'navbar__item--contact button', + className: 'button button-primary', }, ], }, @@ -389,29 +436,37 @@ const config: Config = { }, ], }, + { + title: 'Developers', + items: [ + { + label: 'Documentation', + to: '/docs', + }, + { + label: 'Community', + href: 'https://discord.gg/4R568nZgsT', + }, + { + label: 'Changelog', + to: '/blog/tags/releases', + }, + { + label: 'Github', + href: 'https://github.com/o2sdev/openselfservice', + }, + ], + }, { - title: 'Community', + title: 'More', items: [ { - label: 'LinkedIn', - href: 'https://www.linkedin.com/company/open-self-service/', + label: 'Partners', + to: '/partners', }, - // { - // label: 'Twitter / X', - // href: 'https://x.com/openselfservice', - // }, - { - label: 'Discord', - href: 'https://discord.gg/4R568nZgsT', - }, - ], - }, - { - title: 'More', - items: [ { - label: 'GitHub', - href: 'https://github.com/o2sdev/openselfservice', + to: '/contact', + label: 'Contact us', }, ], }, diff --git a/apps/docs/package.json b/apps/docs/package.json index 81c98161..0c81fb79 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -26,10 +26,12 @@ "@vercel/speed-insights": "^1.2.0", "clsx": "^2.1.1", "docusaurus-plugin-image-zoom": "^3.0.1", + "docusaurus-plugin-sass": "^0.2.6", "framer-motion": "^12.12.1", "prism-react-renderer": "^2.4.1", "react": "^19.1.0", "react-dom": "^19.1.0", + "sass": "^1.93.0", "vanilla-cookieconsent": "^3.1.0" }, "devDependencies": { diff --git a/apps/docs/src/assets/icons/ArrowDown.svg b/apps/docs/src/assets/icons/ArrowDown.svg new file mode 100644 index 00000000..0e6c14e3 --- /dev/null +++ b/apps/docs/src/assets/icons/ArrowDown.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/docs/src/assets/icons/ChevronDown.svg b/apps/docs/src/assets/icons/ChevronDown.svg new file mode 100644 index 00000000..85759f32 --- /dev/null +++ b/apps/docs/src/assets/icons/ChevronDown.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/docs/src/assets/icons/Discord2.svg b/apps/docs/src/assets/icons/Discord2.svg new file mode 100644 index 00000000..80b05804 --- /dev/null +++ b/apps/docs/src/assets/icons/Discord2.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/docs/src/assets/icons/FolderOpenDot.svg b/apps/docs/src/assets/icons/FolderOpenDot.svg new file mode 100644 index 00000000..b7ac4a6f --- /dev/null +++ b/apps/docs/src/assets/icons/FolderOpenDot.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/docs/src/assets/icons/Github2.svg b/apps/docs/src/assets/icons/Github2.svg new file mode 100644 index 00000000..2c9e0311 --- /dev/null +++ b/apps/docs/src/assets/icons/Github2.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/docs/src/assets/icons/History.svg b/apps/docs/src/assets/icons/History.svg new file mode 100644 index 00000000..ca28c6ac --- /dev/null +++ b/apps/docs/src/assets/icons/History.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/docs/src/assets/icons/Mail.svg b/apps/docs/src/assets/icons/Mail.svg new file mode 100644 index 00000000..8cb990cd --- /dev/null +++ b/apps/docs/src/assets/icons/Mail.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/docs/src/assets/icons/Map.svg b/apps/docs/src/assets/icons/Map.svg new file mode 100644 index 00000000..47527d33 --- /dev/null +++ b/apps/docs/src/assets/icons/Map.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/docs/src/assets/icons/PencilLine.svg b/apps/docs/src/assets/icons/PencilLine.svg new file mode 100644 index 00000000..45b24944 --- /dev/null +++ b/apps/docs/src/assets/icons/PencilLine.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/docs/src/assets/icons/SquareArrowOutUpRight.svg b/apps/docs/src/assets/icons/SquareArrowOutUpRight.svg new file mode 100644 index 00000000..7cb5833d --- /dev/null +++ b/apps/docs/src/assets/icons/SquareArrowOutUpRight.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/docs/src/assets/icons/icon call.svg b/apps/docs/src/assets/icons/icon call.svg new file mode 100644 index 00000000..25b06765 --- /dev/null +++ b/apps/docs/src/assets/icons/icon call.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/docs/src/assets/icons/icon handshake.svg b/apps/docs/src/assets/icons/icon handshake.svg new file mode 100644 index 00000000..d98a838c --- /dev/null +++ b/apps/docs/src/assets/icons/icon handshake.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/docs/src/assets/icons/icon text.svg b/apps/docs/src/assets/icons/icon text.svg new file mode 100644 index 00000000..8cb10f2a --- /dev/null +++ b/apps/docs/src/assets/icons/icon text.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/docs/src/assets/icons/o2s-icon-add_logo.svg b/apps/docs/src/assets/icons/o2s-icon-add_logo.svg new file mode 100644 index 00000000..d926dd89 --- /dev/null +++ b/apps/docs/src/assets/icons/o2s-icon-add_logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/apps/docs/src/assets/icons/o2s-icon-badge.svg b/apps/docs/src/assets/icons/o2s-icon-badge.svg new file mode 100644 index 00000000..d3b0a83d --- /dev/null +++ b/apps/docs/src/assets/icons/o2s-icon-badge.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/apps/docs/src/assets/icons/o2s-icon-contact.svg b/apps/docs/src/assets/icons/o2s-icon-contact.svg new file mode 100644 index 00000000..0cffe9a7 --- /dev/null +++ b/apps/docs/src/assets/icons/o2s-icon-contact.svg @@ -0,0 +1,15 @@ + + + + + + + +
+ + + + + + +
diff --git a/apps/docs/src/assets/icons/o2s-icon-loop.svg b/apps/docs/src/assets/icons/o2s-icon-loop.svg new file mode 100644 index 00000000..eeb230f9 --- /dev/null +++ b/apps/docs/src/assets/icons/o2s-icon-loop.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/docs/src/assets/icons/o2s-icon-roadmap.svg b/apps/docs/src/assets/icons/o2s-icon-roadmap.svg new file mode 100644 index 00000000..0e825cb5 --- /dev/null +++ b/apps/docs/src/assets/icons/o2s-icon-roadmap.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/docs/src/assets/icons/o2s-icon-support.svg b/apps/docs/src/assets/icons/o2s-icon-support.svg new file mode 100644 index 00000000..e9c124f6 --- /dev/null +++ b/apps/docs/src/assets/icons/o2s-icon-support.svg @@ -0,0 +1,9 @@ + + + +
+ + + + +
diff --git a/apps/docs/src/assets/logos/Bosch.svg b/apps/docs/src/assets/logos/Bosch.svg new file mode 100644 index 00000000..39133192 --- /dev/null +++ b/apps/docs/src/assets/logos/Bosch.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/apps/docs/src/assets/logos/Cerrad.svg b/apps/docs/src/assets/logos/Cerrad.svg new file mode 100644 index 00000000..877a628d --- /dev/null +++ b/apps/docs/src/assets/logos/Cerrad.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/apps/docs/src/assets/logos/DeutscheTelekom.svg b/apps/docs/src/assets/logos/DeutscheTelekom.svg new file mode 100644 index 00000000..163b7dcb --- /dev/null +++ b/apps/docs/src/assets/logos/DeutscheTelekom.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/docs/src/assets/logos/DormerPramet.svg b/apps/docs/src/assets/logos/DormerPramet.svg new file mode 100644 index 00000000..3c7e2c38 --- /dev/null +++ b/apps/docs/src/assets/logos/DormerPramet.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/docs/src/assets/logos/Fortum.svg b/apps/docs/src/assets/logos/Fortum.svg new file mode 100644 index 00000000..4e1000e9 --- /dev/null +++ b/apps/docs/src/assets/logos/Fortum.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/docs/src/assets/logos/Orange.svg b/apps/docs/src/assets/logos/Orange.svg new file mode 100644 index 00000000..ef6864b2 --- /dev/null +++ b/apps/docs/src/assets/logos/Orange.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/docs/src/assets/logos/OrangeEnergia.svg b/apps/docs/src/assets/logos/OrangeEnergia.svg new file mode 100644 index 00000000..c2389ee8 --- /dev/null +++ b/apps/docs/src/assets/logos/OrangeEnergia.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/docs/src/assets/logos/Osadkowski.svg b/apps/docs/src/assets/logos/Osadkowski.svg new file mode 100644 index 00000000..ec755eef --- /dev/null +++ b/apps/docs/src/assets/logos/Osadkowski.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/docs/src/components/Badge/index.tsx b/apps/docs/src/components/Badge/index.tsx index 403e329b..f3d59b65 100644 --- a/apps/docs/src/components/Badge/index.tsx +++ b/apps/docs/src/components/Badge/index.tsx @@ -1,14 +1,21 @@ +import clsx from 'clsx'; import React from 'react'; interface BadgeProps { title: string; icon?: string | null; + variant?: 'primary' | 'secondary'; } -const Badge: React.FC = ({ title, icon }) => ( -
+const Badge: React.FC = ({ title, icon, variant = 'primary' }) => ( +
{icon && {title} - {title} + {title}
); diff --git a/apps/docs/src/components/BenefitsSection/index.tsx b/apps/docs/src/components/BenefitsSection/index.tsx new file mode 100644 index 00000000..098a0578 --- /dev/null +++ b/apps/docs/src/components/BenefitsSection/index.tsx @@ -0,0 +1,92 @@ +import clsx from 'clsx'; +import React, { type ReactNode } from 'react'; + +import Card from '../Card'; +import { Body, BodySmall, H2, H3 } from '../Typography'; + +export interface BenefitsSectionProps { + title?: React.ReactNode; + benefits: BenefitCardProps[]; +} + +export interface BenefitCardProps { + team?: string; + icon: React.ReactNode; + iconPosition?: 'left' | 'right'; + title: React.ReactNode; + description?: React.ReactNode; + link?: { + text: string; + url: string; + iconLeft?: ReactNode; + iconRight?: ReactNode; + target?: HTMLAnchorElement['target']; + }; + borderColor?: 'gradient' | 'blue' | 'green' | 'light'; +} + +export const BenefitCard: React.FC = ({ + team, + icon, + iconPosition = 'right', + title, + description, + link, + borderColor = 'blue', +}) => { + return ( + + {team && ( + <> +
+ {team} +
{icon}
+
+

{title}

+ + )} + + {!team && !description && ( + <> +
+
{icon}
+
+

{title}

+ + )} + + {!team && description && ( + <> +
+

{title}

+
{icon}
+
+ + )} + + {description && {description}} + + {link && ( + + {link.iconLeft} + {link.text} + {link.iconRight} + + )} +
+ ); +}; + +export const BenefitsSection: React.FC = ({ title, benefits }) => { + return ( +
+ {title &&

{title}

} + +
+ {benefits.map((benefit, index) => ( + + ))} +
+
+ ); +}; diff --git a/apps/docs/src/components/Card/index.tsx b/apps/docs/src/components/Card/index.tsx index 5a9b7faf..302c81a1 100644 --- a/apps/docs/src/components/Card/index.tsx +++ b/apps/docs/src/components/Card/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; interface CardProps { children: React.ReactNode; - borderColor?: 'gradient' | 'blue' | 'green'; + borderColor?: 'gradient' | 'blue' | 'green' | 'light'; className?: string; gap?: string; } @@ -12,6 +12,8 @@ const Card: React.FC = ({ children, borderColor = 'blue', className = if (borderColor === 'gradient') return 'card-gradient-border'; if (borderColor === 'blue') return 'card-base card-solid-border card-border-blue'; if (borderColor === 'green') return 'card-base card-solid-border card-border-green'; + if (borderColor === 'light') return 'card-base card-solid-border card-border-light'; + return 'card-base card-solid-border card-border-blue'; }; diff --git a/apps/docs/src/components/ClientsSection/index.tsx b/apps/docs/src/components/ClientsSection/index.tsx new file mode 100644 index 00000000..a4c827c1 --- /dev/null +++ b/apps/docs/src/components/ClientsSection/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +export interface Client { + name: string; + img: React.ReactNode; +} + +export interface ClientsSectionProps { + lead?: React.ReactNode; + clients: Client[]; +} + +export const ClientsSection: React.FC = ({ lead, clients }) => { + return ( +
+ {lead &&
{lead}
} + +
    + {clients.map((client, index) => ( +
  • + {client.name} + {client.img} +
  • + ))} +
+
+ ); +}; diff --git a/apps/docs/src/components/DXPBenefitsSection/index.tsx b/apps/docs/src/components/DXPBenefitsSection/index.tsx deleted file mode 100644 index 185c27d3..00000000 --- a/apps/docs/src/components/DXPBenefitsSection/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; - -import CodeIcon from '@site/src/assets/icons/code.svg'; -import NetworkIcon from '@site/src/assets/icons/network.svg'; -import PenToolIcon from '@site/src/assets/icons/pentool.svg'; - -import Card from '../Card'; -import { Body, H2, H3 } from '../Typography'; - -interface BenefitCardProps { - team: string; - icon: React.ReactNode; - title: string; - borderColor?: 'gradient' | 'blue' | 'green'; -} - -const BenefitCard: React.FC = ({ team, icon, title, borderColor = 'blue' }) => { - return ( - - {/* Header */} -
- {team} -
{icon}
-
- - {/* Title */} -

{title}

-
- ); -}; - -export function DXPBenefitsSection() { - const benefits: Array<{ - team: string; - icon: React.ReactNode; - title: string; - borderColor?: 'gradient' | 'blue' | 'green'; - }> = [ - { - team: 'Frontend Developers', - icon: , - title: 'Quick start with zero boilerplate', - borderColor: 'blue', - }, - { - team: 'Content Teams', - icon: , - title: 'Structured CMS and ready to use content models', - borderColor: 'gradient', - }, - { - team: 'Solution Architects', - icon: , - title: 'Flexible, composable stack as a base for future scaling', - borderColor: 'green', - }, - ]; - - return ( -
-

- Benefits for every team -

- -
- {benefits.map((benefit, index) => ( - - ))} -
-
- ); -} diff --git a/apps/docs/src/components/DXPFeaturesSection/index.tsx b/apps/docs/src/components/DXPFeaturesSection/index.tsx index 196b639b..506bd2b7 100644 --- a/apps/docs/src/components/DXPFeaturesSection/index.tsx +++ b/apps/docs/src/components/DXPFeaturesSection/index.tsx @@ -3,10 +3,9 @@ import React from 'react'; import CodeIcon from '@site/src/assets/icons/code.svg'; import CodepenIcon from '@site/src/assets/icons/codepen.svg'; import GitcompareIcon from '@site/src/assets/icons/gitcompare.svg'; -import LightbulbIcon from '@site/src/assets/icons/lightbulb.svg'; import Card from '../Card'; -import { Body, H2, H3 } from '../Typography'; +import { H2, H3 } from '../Typography'; interface FeatureCardProps { title: string; @@ -14,9 +13,48 @@ interface FeatureCardProps { features: string[]; buttonText: string; buttonUrl: string; - borderColor?: 'gradient' | 'blue' | 'green'; + borderColor?: 'gradient' | 'blue' | 'green' | 'light'; } +const features: Array = [ + { + title: 'Modern Frontend Foundation', + icon: ( +
+ +
+ ), + features: ['Next.js based frontend', 'UI components & content types', 'Optimized for SEO, a11y & web perf'], + buttonText: 'Learn more', + buttonUrl: '/docs/app-starters/dxp/overview', + borderColor: 'light', + }, + { + title: 'Composable Integration Layer', + icon: ( +
+ +
+ ), + features: ['Future-ready architecture', 'API Harmonization', 'Vendor independence'], + buttonText: 'Learn more', + buttonUrl: '/docs/main-components/harmonization-app/', + borderColor: 'light', + }, + { + title: 'Seamless CMS Experience', + icon: ( +
+ +
+ ), + features: ['Headless CMS integration', 'Powerful content management', 'Multilingual support'], + buttonText: 'Learn more', + buttonUrl: '/docs/integrations/cms/strapi/overview', + borderColor: 'light', + }, +]; + const FeatureCard: React.FC = ({ title, icon, @@ -59,51 +97,6 @@ const FeatureCard: React.FC = ({ }; export function DXPFeaturesSection() { - const features: Array<{ - title: string; - icon: React.ReactNode; - features: string[]; - buttonText: string; - buttonUrl: string; - borderColor?: 'gradient' | 'blue' | 'green'; - }> = [ - { - title: 'Modern Frontend Foundation', - icon: ( -
- -
- ), - features: ['Next.js based frontend', 'UI components & content types', 'Optimized for SEO, a11y & web perf'], - buttonText: 'Learn more', - buttonUrl: '/docs/app-starters/dxp/overview', - }, - { - title: 'Composable Integration Layer', - icon: ( -
- -
- ), - features: ['Future-ready architecture', 'API Harmonization', 'Vendor independence'], - buttonText: 'Learn more', - buttonUrl: '/docs/main-components/harmonization-app/', - borderColor: 'gradient', - }, - { - title: 'Seamless CMS Experience', - icon: ( -
- -
- ), - features: ['Headless CMS integration', 'Powerful content management', 'Multilingual support'], - buttonText: 'Learn more', - buttonUrl: '/docs/integrations/cms/strapi/overview', - borderColor: 'green', - }, - ]; - return (

diff --git a/apps/docs/src/components/DXPFooterSection/index.tsx b/apps/docs/src/components/DXPFooterSection/index.tsx deleted file mode 100644 index b872e96e..00000000 --- a/apps/docs/src/components/DXPFooterSection/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; - -import GithubActiveIcon from '@site/src/assets/icons/github-active.svg'; -import GithubIcon from '@site/src/assets/icons/github.svg'; - -import { Body, H2 } from '../Typography'; - -export function DXPFooterSection() { - return ( -
- {/* Text Content */} -
-

- Ready to - get started? -

- - Build your next digital experience platform with our composable frontend starter kit. - -
- - {/* Buttons */} - -
- ); -} diff --git a/apps/docs/src/components/FooterSection/index.tsx b/apps/docs/src/components/FooterSection/index.tsx new file mode 100644 index 00000000..e63d3594 --- /dev/null +++ b/apps/docs/src/components/FooterSection/index.tsx @@ -0,0 +1,61 @@ +import React from 'react'; + +import { Body, H2 } from '../Typography'; + +export interface FooterSectionProps { + title: React.ReactNode; + description: React.ReactNode; + primaryButton?: { + text: string; + url: string; + iconLeft?: React.ReactNode; + iconRight?: React.ReactNode; + target?: HTMLAnchorElement['target']; + }; + secondaryButton?: { + text: string; + url: string; + iconLeft?: React.ReactNode; + iconRight?: React.ReactNode; + target?: HTMLAnchorElement['target']; + }; +} + +export const FooterSection: React.FC = ({ title, description, primaryButton, secondaryButton }) => { + return ( +
+ {/* Text Content */} +
+

{title}

+ {description} +
+ + {/* Buttons */} + {(primaryButton || secondaryButton) && ( + + )} +
+ ); +}; diff --git a/apps/docs/src/components/GuidesSection/index.tsx b/apps/docs/src/components/GuidesSection/index.tsx new file mode 100644 index 00000000..8bd84bcb --- /dev/null +++ b/apps/docs/src/components/GuidesSection/index.tsx @@ -0,0 +1,95 @@ +import clsx from 'clsx'; +import React, { type ReactNode } from 'react'; + +import HistoryIcon from '../../assets/icons/History.svg'; +import Badge from '../Badge'; +import { Body, BodyBold, BodySmall, H2, H3 } from '../Typography'; + +export interface Guide { + title: string; + description?: string; + icon?: React.ReactNode; + badge?: string; +} + +export interface GuideInfo { + title: string; + description?: React.ReactNode; + link?: { + text: string; + url: string; + iconLeft?: ReactNode; + iconRight?: ReactNode; + target?: HTMLAnchorElement['target']; + }; + subtitle?: string; +} + +export interface GuidesSectionProps { + title?: React.ReactNode; + guides: Guide[]; + info?: GuideInfo; +} + +export const GuidesSection: React.FC = ({ title, guides, info }) => { + return ( +
+ {title &&

{title}

} + +
+
    + {guides.map((guide, index) => ( +
  • +
    +
    +
    {guide.icon}
    + +
    + {guide.title} + {guide.description} +
    + +
    + +
    +
    +
    +
  • + ))} +
+ + {info && ( +
+
+
+
+

{info.title}

+ {info.description && {info.description}} + + {info.link && ( + + {info.link.iconLeft} + {info.link.text} + {info.link.iconRight} + + )} +
+
+
+ + {info.subtitle && ( +
+ + {info.subtitle} +
+ )} +
+ )} +
+
+ ); +}; diff --git a/apps/docs/src/components/HeroBannerSection/index.tsx b/apps/docs/src/components/HeroBannerSection/index.tsx index 12779911..649790a2 100644 --- a/apps/docs/src/components/HeroBannerSection/index.tsx +++ b/apps/docs/src/components/HeroBannerSection/index.tsx @@ -1,26 +1,38 @@ +import clsx from 'clsx'; import React, { type ReactNode, useState } from 'react'; import CircleCheckIcon from '@site/src/assets/icons/circle-check.svg'; import CopyIcon from '@site/src/assets/icons/copy.svg'; -import GithubActiveIcon from '@site/src/assets/icons/github-active.svg'; -import GithubIcon from '@site/src/assets/icons/github.svg'; import TerminalIcon from '@site/src/assets/icons/terminal.svg'; -import { Body, H1 } from '../Typography'; +import { H1 } from '../Typography'; interface HeroBannerSectionProps { - heading: ReactNode; - description: ReactNode | string[]; - cliCommand: string; - mainLink: { + heading?: ReactNode; + description: ReactNode | ReactNode[]; + cliCommand?: string; + mainLink?: { text: string; url: string; + iconLeft?: ReactNode; + iconRight?: ReactNode; + target?: HTMLAnchorElement['target']; }; - secondaryLink: { + secondaryLink?: { text: string; url: string; + iconLeft?: ReactNode; + iconRight?: ReactNode; + target?: HTMLAnchorElement['target']; }; - heroImage: { + tertiaryLink?: { + text: string; + url: string; + iconLeft?: ReactNode; + iconRight?: ReactNode; + target?: HTMLAnchorElement['target']; + }; + heroImage?: { url: string; alt: string; }; @@ -33,6 +45,7 @@ export function HeroBannerSection({ cliCommand, mainLink, secondaryLink, + tertiaryLink, heroImage, isDXPage = false, }: HeroBannerSectionProps) { @@ -46,81 +59,107 @@ export function HeroBannerSection({ return (
-
-
-

{heading}

- - {Array.isArray(description) ? ( -
    - {description.map((item, index) => ( -
  • - - {item} -
  • - ))} -
- ) : ( - description - )} - -
-
- -
- ); diff --git a/apps/docs/src/pages/contact.module.css b/apps/docs/src/components/HubspotForm/HubspotForm.module.scss similarity index 52% rename from apps/docs/src/pages/contact.module.css rename to apps/docs/src/components/HubspotForm/HubspotForm.module.scss index 23e26e7c..75bdda1b 100644 --- a/apps/docs/src/pages/contact.module.css +++ b/apps/docs/src/components/HubspotForm/HubspotForm.module.scss @@ -1,16 +1,13 @@ /* -------------------------- - Contact Page + HubspotForm Styles -------------------------- */ -.contactContainer { - overflow: hidden; - min-height: 400px; - max-width: 100%; - flex: 1 0 auto; -} .contactFormText { color: var(--inverted-foreground-color); } +.contactFormSelect { +} + .contactFormText a { font-weight: bold; } @@ -18,16 +15,8 @@ .contactFormH2 { color: var(--inverted-foreground-color); font-size: 1.5rem; - margin-bottom: 0; - font-weight: bold; -} - -.text-highlighted { - color: var(--color-highlighted, #21d99a); -} - -.font-extrabold { - font-weight: 800; + margin-bottom: 0.25rem; + font-weight: 600; } .msg { @@ -45,14 +34,11 @@ color: #d61830; } .error:before { - background: url('../../src/assets/icons/circle-alert.svg') no-repeat; + background: url('../../assets/icons/circle-alert.svg') no-repeat; } .success { color: var(--inverted-foreground-color); } .success:before { - background: url('../../src/assets/icons/circle-check.svg') no-repeat; -} -body:not(.navigation-with-keyboard) *:not(input):focus { - outline-width: 2px; + background: url('../../assets/icons/circle-check.svg') no-repeat; } diff --git a/apps/docs/src/components/HubspotForm/index.tsx b/apps/docs/src/components/HubspotForm/index.tsx new file mode 100644 index 00000000..508e3ab1 --- /dev/null +++ b/apps/docs/src/components/HubspotForm/index.tsx @@ -0,0 +1,473 @@ +import clsx from 'clsx'; +import React, { useState } from 'react'; + +import { Body } from '@site/src/components/Typography'; + +import styles from './HubspotForm.module.scss'; + +type HubspotField = { + __typename: 'text' | 'email' | 'textarea' | 'select' | 'checkboxGroup' | 'radioGroup'; + label: string; + required?: boolean; + name: string; + rows?: number; + objectTypeId?: '0-1' | '0-2'; +}; + +type HubspotFieldText = HubspotField & { + __typename: 'text'; + type: 'text' | 'email'; +}; +type HubspotFieldTextarea = HubspotField & { + __typename: 'textarea'; +}; +type HubspotFieldSelect = HubspotField & { + __typename: 'select'; + options: { + label: string; + value?: string; + other?: boolean; + }[]; + other?: HubspotFieldText; +}; +type HubspotFieldCheckboxGroup = HubspotField & { + __typename: 'checkboxGroup'; + options: { + label: string; + value?: string; + other?: boolean; + }[]; + other?: HubspotFieldText; +}; +type HubspotFieldRadioGroup = HubspotField & { + __typename: 'radioGroup'; + options: { + label: string; + value?: string; + other?: boolean; + }[]; +}; + +type HubspotConsent = { + label: React.ReactNode; + required?: boolean; + name?: string; + defaultChecked?: boolean; + objectTypeId?: '0-1' | '0-2'; +}; + +type HubspotFormProps = { + portalId: string; + formId: string; + title: string; + description: React.ReactNode; + pageName: string; + fields: ( + | HubspotFieldText + | HubspotFieldTextarea + | HubspotFieldSelect + | HubspotFieldCheckboxGroup + | HubspotFieldRadioGroup + )[]; + consents?: HubspotConsent[]; +}; + +const HubspotForm: React.FC = ({ + portalId, + formId, + title, + description, + pageName, + fields, + consents = [], +}) => { + const [values, setValues] = useState>(() => { + const initial: Record = {}; + fields.forEach((f) => (initial[f.name] = '')); + return initial; + }); + const [consentValues, setConsentValues] = useState>(() => { + const initial: Record = {}; + consents.forEach((c) => (initial[c.name] = !!c.defaultChecked)); + return initial; + }); + const [status, setStatus] = useState<{ type: 'success' | 'error' | null; message: string }>({ + type: null, + message: '', + }); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleChange = (id: string, v: string | string[]) => { + setValues((prev) => ({ ...prev, [id]: v })); + }; + + const handleConsentToggle = (id: string, checked: boolean) => { + setConsentValues((prev) => ({ ...prev, [id]: checked })); + }; + + const getPageUri = () => { + if (typeof window !== 'undefined' && window.location) { + return window.location.href; + } + return ''; + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const missingRequiredConsent = consents.some((c) => c.required && !consentValues[c.name]); + if (missingRequiredConsent) { + setStatus({ type: 'error', message: 'Please agree to required consents.' }); + return; + } + setIsSubmitting(true); + + const payload = { + fields: [ + ...fields.reduce((prev, f) => { + switch (f.__typename) { + case 'checkboxGroup': { + let other = undefined; + if (f.other && values[f.other.name]) { + other = { + name: f.other.name, + objectTypeId: f.other.objectTypeId, + value: values[f.other.name], + }; + } + + return [ + ...prev, + { + name: f.name, + objectTypeId: f.objectTypeId, + value: (values[f.name] as string[]).join(';'), + }, + ...(other ? [other] : []), + ]; + } + case 'select': { + let other = undefined; + if (f.other && values[f.other.name]) { + other = { + name: f.other.name, + objectTypeId: f.other.objectTypeId, + value: values[f.other.name], + }; + } + + return [ + ...prev, + { name: f.name, objectTypeId: f.objectTypeId, value: values[f.name] }, + ...(other ? [other] : []), + ]; + } + default: + return [...prev, { name: f.name, objectTypeId: f.objectTypeId, value: values[f.name] }]; + } + }, []), + ...consents.map((c) => ({ name: c.name, objectTypeId: c.objectTypeId, value: consentValues[c.name] })), + ], + context: { + pageUri: getPageUri(), + pageName, + }, + legalConsentOptions: { + consent: { + consentToProcess: true, + text: 'I consent to the processing of my personal data by Hycom SA as described in the openselfservice_EN_Information_obligation.pdf information clause to respond to inquiries and provide information about products and services.', + communications: [ + { + value: true, + subscriptionTypeId: '296606641', + text: 'I agree to receive communications that respond to inquiries and provide information about products and services.', + }, + ], + }, + }, + }; + + try { + const response = await fetch( + `https://api.hsforms.com/submissions/v3/integration/submit/${portalId}/${formId}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }, + ); + + if (response.ok) { + const data = await response.json(); + setStatus({ type: 'success', message: data.inlineMessage }); + setValues((prev) => { + const cleared: Record = {}; + Object.keys(prev).forEach((k) => (cleared[k] = '')); + return cleared; + }); + setConsentValues((prev) => { + const cleared: Record = {}; + Object.keys(prev).forEach((k) => (cleared[k] = false)); + return cleared; + }); + } else { + const data = await response.json(); + setStatus({ type: 'error', message: data.message }); + setIsSubmitting(false); + } + } catch (error) { + setStatus({ type: 'error', message: 'An unexpected error occurred. Please try again later.' }); + setIsSubmitting(false); + } + }; + + return ( +
+ {status.type !== 'success' && ( +
+

{title}

+

{description}

+ + {fields.map((f) => { + switch (f.__typename) { + case 'text': + return ( +
+ + handleChange(f.name, e.target.value)} + /> +
+ ); + case 'textarea': + return ( +
+ +