From 96e6c8c52026c13c2b1b200601e16ddd459238c2 Mon Sep 17 00:00:00 2001 From: Vaibhav Sharma Date: Wed, 25 Jun 2025 14:03:05 +0530 Subject: [PATCH 1/4] Initial commit from Create Next App --- .env.example | 20 + .eslintrc.json | 3 + .gitignore | 47 + .gitkeep | 0 .prettierignore | 2 + .storybook/main.ts | 29 + .storybook/preview.ts | 28 + .vscode/settings.json | 3 + README.md | 9 + app/[[...slug]]/page.tsx | 171 + app/favicon.ico | Bin 0 -> 25931 bytes app/globals.css | 135 + app/layout.tsx | 38 + app/preview.css | 7 + components.json | 21 + components/blocks/Article/Article.stories.tsx | 85 + components/blocks/Article/Article.tsx | 118 + components/blocks/CTA/CTA.stories.tsx | 50 + components/blocks/CTA/CTA.tsx | 75 + components/blocks/CardGroup/CardGroup.tsx | 118 + .../CardGroup/CardGroupSimpleCard.stories.tsx | 104 + .../CardGroup/CardGroupTeaserCard.stories.tsx | 216 + components/blocks/FAQ/FAQ.stories.tsx | 62 + components/blocks/FAQ/FAQ.tsx | 75 + components/blocks/Footer/Footer.stories.tsx | 102 + components/blocks/Footer/Footer.tsx | 94 + components/blocks/Header/Header.stories.tsx | 100 + components/blocks/Header/Header.tsx | 165 + components/blocks/Hero/Hero.stories.tsx | 75 + components/blocks/Hero/Hero.tsx | 77 + .../blocks/LogoGroup/LogoGroup.stories.tsx | 91 + components/blocks/LogoGroup/LogoGroup.tsx | 81 + .../Testimonial/Testimonial.stories.tsx | 52 + components/blocks/Testimonial/Testimonial.tsx | 64 + components/blocks/index.ts | 10 + components/form/Form.mdx | 56 + components/form/Form.stories.tsx | 148 + components/form/Input/Input.stories.tsx | 112 + components/form/Input/Input.tsx | 23 + components/form/Textarea/Textarea.stories.tsx | 120 + components/form/Textarea/Textarea.tsx | 12 + components/form/index.ts | 2 + components/pages/ArticleLayout.stories.tsx | 40 + components/pages/PageLayout.stories.tsx | 59 + .../Accordion/Accordion.stories.tsx | 31 + components/primitives/Accordion/Accordion.tsx | 29 + .../primitives/Avatar/Avatar.stories.tsx | 23 + components/primitives/Avatar/Avatar.tsx | 26 + components/primitives/Badge/Badge.stories.tsx | 50 + components/primitives/Badge/Badge.tsx | 10 + .../primitives/Button/Button.stories.tsx | 86 + components/primitives/Button/Button.tsx | 41 + components/primitives/Image/Image.tsx | 7 + components/primitives/Link/Link.tsx | 25 + .../primitives/MainLayout/MainLayout.tsx | 9 + .../NavigationMenu/NavigationMenu.stories.tsx | 48 + .../NavigationMenu/NavigationMenu.tsx | 60 + .../primitives/RichText/RichText.stories.tsx | 203 + components/primitives/RichText/RichText.tsx | 24 + .../SimpleCard/SimpleCard.stories.tsx | 35 + .../primitives/SimpleCard/SimpleCard.tsx | 38 + .../TeaserCard/TeaserCard.stories.tsx | 48 + .../primitives/TeaserCard/TeaserCard.tsx | 72 + components/primitives/index.ts | 11 + components/tokens/Colors.mdx | 17 + components/tokens/Typography.mdx | 77 + components/ui/accordion.tsx | 64 + components/ui/alert.tsx | 66 + components/ui/avatar.tsx | 53 + components/ui/badge.tsx | 46 + components/ui/button.tsx | 59 + components/ui/input.tsx | 21 + components/ui/label.tsx | 22 + components/ui/navigation-menu.tsx | 168 + components/ui/textarea.tsx | 18 + graphql/fragments/media.ts | 25 + graphql/fragments/menu.ts | 24 + graphql/fragments/metatag.ts | 28 + graphql/fragments/misc.ts | 19 + graphql/fragments/node.ts | 76 + graphql/fragments/paragraph.ts | 39 + graphql/fragments/terms.ts | 12 + graphql/fragments/user.ts | 18 + graphql/fragments/view.ts | 54 + graphql/fragments/webform.ts | 26 + graphql/generated/gql.tada.cache.ts | 66 + graphql/generated/gql.tada.instrospection.ts | 8801 +++++++++ graphql/generated/schema.graphql | 2204 +++ graphql/gql.tada.ts | 6 + graphql/types.ts | 8 + integration/forms/ContactForm/ContactForm.tsx | 104 + integration/forms/ContactForm/action.ts | 38 + integration/forms/ContactForm/function.ts | 40 + integration/forms/ContactForm/schema.ts | 9 + integration/helpers/NavigationEvents.tsx | 46 + integration/node/NodeArticle.tsx | 41 + integration/node/NodePage.tsx | 30 + .../resolvers/ParagraphCardGroupResolver.tsx | 77 + .../resolvers/ParagraphCtaResolver.tsx | 48 + .../resolvers/ParagraphFaqResolver.tsx | 63 + .../resolvers/ParagraphHeroResolver.tsx | 57 + .../resolvers/ParagraphLogoGroupResolver.tsx | 74 + .../ParagraphTestimonialResolver.tsx | 76 + .../ParagraphViewReferenceResolver.tsx | 160 + .../resolvers/ParagraphWebformResolver.tsx | 67 + integration/resolvers/components.tsx | 163 + integration/resolvers/helpers.tsx | 71 + integration/taxonomy/TermTags.tsx | 21 + lib/utils.ts | 6 + middleware.ts | 18 + next.config.mjs | 4 + package-lock.json | 16291 ++++++++++++++++ package.json | 73 + postcss.config.mjs | 8 + prettier.config.js | 16 + public/next.svg | 1 + public/vercel.svg | 1 + scripts/sync.ts | 42 + static/placeholders/doc-tahedroid/avatar.png | Bin 0 -> 1243 bytes .../doc-tahedroid/hero-landscape-large.png | Bin 0 -> 55192 bytes .../doc-tahedroid/landscape-large.png | Bin 0 -> 17386 bytes .../doc-tahedroid/landscape-small.png | Bin 0 -> 3657 bytes static/placeholders/doc-tahedroid/square.png | Bin 0 -> 15784 bytes .../drupal-decoupled/landscape-large.png | Bin 0 -> 8494 bytes .../drupal-decoupled/landscape-small.png | Bin 0 -> 2112 bytes .../placeholders/drupal-decoupled/square.png | Bin 0 -> 9207 bytes static/placeholders/icons/doc-tahedroid.png | Bin 0 -> 1836 bytes .../icons/drupal-decoupled-hexagon.png | Bin 0 -> 974 bytes .../placeholders/icons/drupal-decoupled.png | Bin 0 -> 726 bytes tsconfig.json | 35 + utils/auth.ts | 16 + utils/client.ts | 26 + utils/routes.ts | 18 + yarn.lock | 7325 +++++++ 134 files changed, 40857 insertions(+) create mode 100644 .env.example create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .gitkeep create mode 100644 .prettierignore create mode 100644 .storybook/main.ts create mode 100644 .storybook/preview.ts create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 app/[[...slug]]/page.tsx create mode 100644 app/favicon.ico create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/preview.css create mode 100644 components.json create mode 100644 components/blocks/Article/Article.stories.tsx create mode 100644 components/blocks/Article/Article.tsx create mode 100644 components/blocks/CTA/CTA.stories.tsx create mode 100644 components/blocks/CTA/CTA.tsx create mode 100644 components/blocks/CardGroup/CardGroup.tsx create mode 100644 components/blocks/CardGroup/CardGroupSimpleCard.stories.tsx create mode 100644 components/blocks/CardGroup/CardGroupTeaserCard.stories.tsx create mode 100644 components/blocks/FAQ/FAQ.stories.tsx create mode 100644 components/blocks/FAQ/FAQ.tsx create mode 100644 components/blocks/Footer/Footer.stories.tsx create mode 100644 components/blocks/Footer/Footer.tsx create mode 100644 components/blocks/Header/Header.stories.tsx create mode 100644 components/blocks/Header/Header.tsx create mode 100644 components/blocks/Hero/Hero.stories.tsx create mode 100644 components/blocks/Hero/Hero.tsx create mode 100644 components/blocks/LogoGroup/LogoGroup.stories.tsx create mode 100644 components/blocks/LogoGroup/LogoGroup.tsx create mode 100644 components/blocks/Testimonial/Testimonial.stories.tsx create mode 100644 components/blocks/Testimonial/Testimonial.tsx create mode 100644 components/blocks/index.ts create mode 100644 components/form/Form.mdx create mode 100644 components/form/Form.stories.tsx create mode 100644 components/form/Input/Input.stories.tsx create mode 100644 components/form/Input/Input.tsx create mode 100644 components/form/Textarea/Textarea.stories.tsx create mode 100644 components/form/Textarea/Textarea.tsx create mode 100644 components/form/index.ts create mode 100644 components/pages/ArticleLayout.stories.tsx create mode 100644 components/pages/PageLayout.stories.tsx create mode 100644 components/primitives/Accordion/Accordion.stories.tsx create mode 100644 components/primitives/Accordion/Accordion.tsx create mode 100644 components/primitives/Avatar/Avatar.stories.tsx create mode 100644 components/primitives/Avatar/Avatar.tsx create mode 100644 components/primitives/Badge/Badge.stories.tsx create mode 100644 components/primitives/Badge/Badge.tsx create mode 100644 components/primitives/Button/Button.stories.tsx create mode 100644 components/primitives/Button/Button.tsx create mode 100644 components/primitives/Image/Image.tsx create mode 100644 components/primitives/Link/Link.tsx create mode 100644 components/primitives/MainLayout/MainLayout.tsx create mode 100644 components/primitives/NavigationMenu/NavigationMenu.stories.tsx create mode 100644 components/primitives/NavigationMenu/NavigationMenu.tsx create mode 100644 components/primitives/RichText/RichText.stories.tsx create mode 100644 components/primitives/RichText/RichText.tsx create mode 100644 components/primitives/SimpleCard/SimpleCard.stories.tsx create mode 100644 components/primitives/SimpleCard/SimpleCard.tsx create mode 100644 components/primitives/TeaserCard/TeaserCard.stories.tsx create mode 100644 components/primitives/TeaserCard/TeaserCard.tsx create mode 100644 components/primitives/index.ts create mode 100644 components/tokens/Colors.mdx create mode 100644 components/tokens/Typography.mdx create mode 100644 components/ui/accordion.tsx create mode 100644 components/ui/alert.tsx create mode 100644 components/ui/avatar.tsx create mode 100644 components/ui/badge.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/navigation-menu.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 graphql/fragments/media.ts create mode 100644 graphql/fragments/menu.ts create mode 100644 graphql/fragments/metatag.ts create mode 100644 graphql/fragments/misc.ts create mode 100644 graphql/fragments/node.ts create mode 100644 graphql/fragments/paragraph.ts create mode 100644 graphql/fragments/terms.ts create mode 100644 graphql/fragments/user.ts create mode 100644 graphql/fragments/view.ts create mode 100644 graphql/fragments/webform.ts create mode 100644 graphql/generated/gql.tada.cache.ts create mode 100644 graphql/generated/gql.tada.instrospection.ts create mode 100644 graphql/generated/schema.graphql create mode 100644 graphql/gql.tada.ts create mode 100644 graphql/types.ts create mode 100644 integration/forms/ContactForm/ContactForm.tsx create mode 100644 integration/forms/ContactForm/action.ts create mode 100644 integration/forms/ContactForm/function.ts create mode 100644 integration/forms/ContactForm/schema.ts create mode 100644 integration/helpers/NavigationEvents.tsx create mode 100644 integration/node/NodeArticle.tsx create mode 100644 integration/node/NodePage.tsx create mode 100644 integration/resolvers/ParagraphCardGroupResolver.tsx create mode 100644 integration/resolvers/ParagraphCtaResolver.tsx create mode 100644 integration/resolvers/ParagraphFaqResolver.tsx create mode 100644 integration/resolvers/ParagraphHeroResolver.tsx create mode 100644 integration/resolvers/ParagraphLogoGroupResolver.tsx create mode 100644 integration/resolvers/ParagraphTestimonialResolver.tsx create mode 100644 integration/resolvers/ParagraphViewReferenceResolver.tsx create mode 100644 integration/resolvers/ParagraphWebformResolver.tsx create mode 100644 integration/resolvers/components.tsx create mode 100644 integration/resolvers/helpers.tsx create mode 100644 integration/taxonomy/TermTags.tsx create mode 100644 lib/utils.ts create mode 100644 middleware.ts create mode 100644 next.config.mjs create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.mjs create mode 100644 prettier.config.js create mode 100644 public/next.svg create mode 100644 public/vercel.svg create mode 100644 scripts/sync.ts create mode 100644 static/placeholders/doc-tahedroid/avatar.png create mode 100644 static/placeholders/doc-tahedroid/hero-landscape-large.png create mode 100644 static/placeholders/doc-tahedroid/landscape-large.png create mode 100644 static/placeholders/doc-tahedroid/landscape-small.png create mode 100644 static/placeholders/doc-tahedroid/square.png create mode 100644 static/placeholders/drupal-decoupled/landscape-large.png create mode 100644 static/placeholders/drupal-decoupled/landscape-small.png create mode 100644 static/placeholders/drupal-decoupled/square.png create mode 100644 static/placeholders/icons/doc-tahedroid.png create mode 100644 static/placeholders/icons/drupal-decoupled-hexagon.png create mode 100644 static/placeholders/icons/drupal-decoupled.png create mode 100644 tsconfig.json create mode 100644 utils/auth.ts create mode 100644 utils/client.ts create mode 100644 utils/routes.ts create mode 100644 yarn.lock diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..a3088c43 --- /dev/null +++ b/.env.example @@ -0,0 +1,20 @@ +# use your site url and comment adding # to the orther +# ddev +DRUPAL_AUTH_URI=http://drupal-decoupled.ddev.site +# lando +# DRUPAL_AUTH_URI=http://drupal-decoupled.lndo.site + +# You can use the previously generated `DRUPAL_CLIENT_ID` and `DRUPAL_CLIENT_SECRET` values shown on the CLI or as a warning message. +# You can reuse the previously generated Consumers at the site by visiting `admin/config/services/consumer` to edit the consumers labeled as `Viewer` & `Previewer` and assigning them a new secret value. +# For this demo use the one with the scope Previewer +DRUPAL_CLIENT_ID= +DRUPAL_CLIENT_SECRET= + +# use your site url and comment adding # to the orther +# ddev +DRUPAL_GRAPHQL_URI=http://drupal-decoupled.ddev.site/graphql +# lando +# DRUPAL_GRAPHQL_URI=http://drupal-decoupled.lndo.site/graphql + +# use preview or production +ENVIRONMENT=preview diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..4360d9b1 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["next/core-web-vitals", "next/typescript", "prettier"] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e9a812fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +.env + +# yalc +.yalc + +# gql:fragments (generated) - do not commit +graphql/drupal/fragments/generated + +*storybook.log +storybook-static diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..46d711ee --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +graphql/generated/* +components/ui/**/*.mdx diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 00000000..5d24f2d8 --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,29 @@ +import type { StorybookConfig } from '@storybook/nextjs' + +import { join, dirname } from 'path' + +/** + * This function is used to resolve the absolute path of a package. + * It is needed in projects that use Yarn PnP or are set up within a monorepo. + */ +function getAbsolutePath(value: string): any { + return dirname(require.resolve(join(value, 'package.json'))) +} +const config: StorybookConfig = { + stories: [ + '../components/**/*.mdx', + '../components/**/*.stories.@(js|jsx|mjs|ts|tsx)', + ], + addons: [ + getAbsolutePath('@storybook/addon-onboarding'), + getAbsolutePath('@storybook/addon-links'), + getAbsolutePath('@storybook/addon-essentials'), + getAbsolutePath('@storybook/addon-interactions'), + ], + framework: { + name: getAbsolutePath('@storybook/nextjs'), + options: {}, + }, + staticDirs: ['../static'], +} +export default config diff --git a/.storybook/preview.ts b/.storybook/preview.ts new file mode 100644 index 00000000..e23228e8 --- /dev/null +++ b/.storybook/preview.ts @@ -0,0 +1,28 @@ +import type { Preview } from '@storybook/react' + +import '../app/globals.css' + +const preview: Preview = { + parameters: { + options: { + storySort: { + order: [ + 'Tokens', + 'Primitives', + 'Blocks', + 'Pages', + 'Form', + ['*', 'Form - Demo'], + ], + }, + }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, +} + +export default preview diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..25fa6215 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/README.md b/README.md new file mode 100644 index 00000000..3287ce90 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Drupal Decoupled: Next.js + +## Getting Started + +Visit the docs to see how to use this [Next.js](https://drupal-decoupled.octahedroid.com/docs/getting-started/quickstart/next) starter. + +## Supporting organizations + +Development sponsored by [Octahedroid](https://octahedroid.com/) diff --git a/app/[[...slug]]/page.tsx b/app/[[...slug]]/page.tsx new file mode 100644 index 00000000..a5139089 --- /dev/null +++ b/app/[[...slug]]/page.tsx @@ -0,0 +1,171 @@ +import { FragmentOf, readFragment } from 'gql.tada' +import { headers } from 'next/headers' +import { redirect } from 'next/navigation' + +import { NodeArticleFragment, NodePageFragment } from '@/graphql/fragments/node' +import { TermTagsFragment } from '@/graphql/fragments/terms' +import { graphql } from '@/graphql/gql.tada' +import { EntityFragmentType } from '@/graphql/types' +import NodeArticleComponent from '@/integration/node/NodeArticle' +import NodePageComponent from '@/integration/node/NodePage' +import TermTagsComponent from '@/integration/taxonomy/TermTags' +import { getClient } from '@/utils/client' +import { calculatePath } from '@/utils/routes' + +import { PageProps } from '@/.next/types/app/layout' +import { Footer, Header } from '@/components/blocks' +import { MenuFragment, MenuItemFragment } from '@/graphql/fragments/menu' + +async function getDrupalData({ params }: { params: { slug: string[] } }) { + const pathFromParams = params.slug?.join('/') || '/home' + const requestUrl = (await headers()).get('x-url') + const path = calculatePath({ + path: pathFromParams, + url: requestUrl!, + }) + + const client = await getClient({ + url: process.env.DRUPAL_GRAPHQL_URI!, + auth: { + uri: process.env.DRUPAL_AUTH_URI!, + clientId: process.env.DRUPAL_CLIENT_ID!, + clientSecret: process.env.DRUPAL_CLIENT_SECRET!, + }, + }) + const nodeRouteQuery = graphql( + ` + query route($path: String!) { + route(path: $path) { + __typename + ... on RouteInternal { + entity { + __typename + ... on NodePage { + id + title + } + ...NodePageFragment + ...NodeArticleFragment + ...TermTagsFragment + } + } + } + + menuMain: menu(name: MAIN) { + ...MenuFragment + } + + menuFooter: menu(name: FOOTER) { + ...MenuFragment + } + } + `, + [NodePageFragment, NodeArticleFragment, TermTagsFragment, MenuFragment] + ) + + const { data, error } = await client.query(nodeRouteQuery, { + path, + }) + + if (error) { + throw error + } + + if ( + !data || + !data?.route || + data?.route.__typename !== 'RouteInternal' || + !data.route.entity + ) { + return redirect('/404') + } + + const menuMain = readFragment(MenuFragment, data.menuMain) + const navItems = menuMain + ? menuMain.items.map((item) => { + const menuItem = readFragment(MenuItemFragment, item) + + return { + label: menuItem.label, + href: menuItem.href || undefined, + expanded: menuItem.expanded, + } + }) + : [] + + return { + type: data.route.entity.__typename, + header: { + logo: { + // add DRUPAL URI as env variable + src: `${process.env.DRUPAL_AUTH_URI}/sites/default/files/2024-09/drupal-decoupled.png`, + alt: 'Company Logo', + }, + navItems, + sticky: true, + actions: [ + { + text: 'Docs', + href: 'https://drupal-decoupled.octahedroid.com/docs', + }, + { + text: 'Quickstart', + href: 'https://drupal-decoupled.octahedroid.com/docs/getting-started/quick-start/drupal', + }, + ], + }, + footer: { + logo: { + // add DRUPAL URI as env variable + src: `${process.env.DRUPAL_AUTH_URI}/sites/default/files/2024-09/drupal-decoupled.png`, + alt: 'Company Logo', + }, + copyrightText: `© ${new Date().getFullYear()} Drupal Decoupled`, + navItems: [], + }, + entity: data.route.entity as EntityFragmentType, + environment: process.env.ENVIRONMENT!, + } +} + +export default async function Page({ params }: PageProps) { + const { type, entity, environment, header, footer } = await getDrupalData({ + params: await params, + }) + if (!type || !entity) { + return null + } + + return ( + <> +
+ {type === 'NodePage' && ( + } + environment={environment} + /> + )} + {type === 'NodeArticle' && ( + } + environment={environment} + /> + )} + {type === 'TermTags' && ( + } + /> + )} +