From 0721b7dc8fa8ac6d0420210beb526bb0eb511644 Mon Sep 17 00:00:00 2001 From: vespo92 Date: Wed, 9 Aug 2023 21:34:23 +0000 Subject: [PATCH] Initial commit Created from https://vercel.com/new --- .env.example | 7 + .gitignore | 41 ++ CHANGELOG.md | 531 ++++++++++++++++++++++++ README.md | 11 + components/footer.tsx | 31 ++ components/form-item.tsx | 16 + components/formatted-text.tsx | 64 +++ components/layout.tsx | 23 + components/link.tsx | 38 ++ components/links.tsx | 33 ++ components/locale-switcher.tsx | 25 ++ components/media--image.tsx | 34 ++ components/media.tsx | 25 ++ components/meta.tsx | 53 +++ components/navbar.tsx | 64 +++ components/node--article.tsx | 108 +++++ components/node--landing-page.tsx | 20 + components/node--page.tsx | 17 + components/node--property.tsx | 127 ++++++ components/node.tsx | 32 ++ components/pager.tsx | 80 ++++ components/paragraph--cards.tsx | 38 ++ components/paragraph--faq.tsx | 41 ++ components/paragraph--feature.tsx | 60 +++ components/paragraph--hero.tsx | 38 ++ components/paragraph--view.tsx | 9 + components/paragraph.tsx | 33 ++ components/section-header.tsx | 35 ++ components/section.tsx | 17 + components/view--properties_listing.tsx | 290 +++++++++++++ cypress.json | 3 + cypress/fixtures/example.json | 5 + cypress/integration/about.spec.tsx | 14 + cypress/integration/blog.spec.tsx | 66 +++ cypress/integration/home.spec.tsx | 63 +++ cypress/integration/locale.spec.tsx | 19 + cypress/integration/properties.spec.tsx | 32 ++ cypress/plugins/index.js | 22 + cypress/support/commands.js | 25 ++ cypress/support/index.js | 20 + hooks/use-pagination.tsx | 83 ++++ lib/drupal.ts | 13 + lib/get-menus.ts | 22 + lib/get-params.ts | 39 ++ lib/utils/absolute-url.ts | 3 + lib/utils/format-date.ts | 8 + lib/utils/is-relative.ts | 3 + next-env.d.ts | 5 + next.config.js | 28 ++ package.json | 40 ++ pages/[[...slug]].tsx | 112 +++++ pages/_app.tsx | 31 ++ pages/_document.tsx | 17 + pages/api/exit-preview.ts | 8 + pages/api/preview.ts | 5 + pages/api/revalidate.ts | 34 ++ pages/blog/page/[page].tsx | 126 ++++++ postcss.config.js | 6 + public/favicon.ico | Bin 0 -> 15086 bytes public/meta.jpg | Bin 0 -> 66619 bytes public/robots.txt | 2 + styles/globals.css | 3 + tailwind.config.js | 14 + tsconfig.json | 29 ++ types/.gitkeep | 0 types/drupal.d.ts | 17 + 66 files changed, 2858 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 components/footer.tsx create mode 100644 components/form-item.tsx create mode 100644 components/formatted-text.tsx create mode 100644 components/layout.tsx create mode 100644 components/link.tsx create mode 100644 components/links.tsx create mode 100644 components/locale-switcher.tsx create mode 100644 components/media--image.tsx create mode 100644 components/media.tsx create mode 100644 components/meta.tsx create mode 100644 components/navbar.tsx create mode 100644 components/node--article.tsx create mode 100644 components/node--landing-page.tsx create mode 100644 components/node--page.tsx create mode 100644 components/node--property.tsx create mode 100644 components/node.tsx create mode 100644 components/pager.tsx create mode 100644 components/paragraph--cards.tsx create mode 100644 components/paragraph--faq.tsx create mode 100644 components/paragraph--feature.tsx create mode 100644 components/paragraph--hero.tsx create mode 100644 components/paragraph--view.tsx create mode 100644 components/paragraph.tsx create mode 100644 components/section-header.tsx create mode 100644 components/section.tsx create mode 100644 components/view--properties_listing.tsx create mode 100644 cypress.json create mode 100644 cypress/fixtures/example.json create mode 100644 cypress/integration/about.spec.tsx create mode 100644 cypress/integration/blog.spec.tsx create mode 100644 cypress/integration/home.spec.tsx create mode 100644 cypress/integration/locale.spec.tsx create mode 100644 cypress/integration/properties.spec.tsx create mode 100644 cypress/plugins/index.js create mode 100644 cypress/support/commands.js create mode 100644 cypress/support/index.js create mode 100644 hooks/use-pagination.tsx create mode 100644 lib/drupal.ts create mode 100644 lib/get-menus.ts create mode 100644 lib/get-params.ts create mode 100644 lib/utils/absolute-url.ts create mode 100644 lib/utils/format-date.ts create mode 100644 lib/utils/is-relative.ts create mode 100644 next-env.d.ts create mode 100644 next.config.js create mode 100644 package.json create mode 100644 pages/[[...slug]].tsx create mode 100644 pages/_app.tsx create mode 100644 pages/_document.tsx create mode 100644 pages/api/exit-preview.ts create mode 100644 pages/api/preview.ts create mode 100644 pages/api/revalidate.ts create mode 100644 pages/blog/page/[page].tsx create mode 100644 postcss.config.js create mode 100644 public/favicon.ico create mode 100644 public/meta.jpg create mode 100644 public/robots.txt create mode 100644 styles/globals.css create mode 100644 tailwind.config.js create mode 100644 tsconfig.json create mode 100644 types/.gitkeep create mode 100644 types/drupal.d.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1ab48b3 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +NEXT_PUBLIC_DRUPAL_BASE_URL=http://localhost:8080 +NEXT_IMAGE_DOMAIN=localhost +DRUPAL_CLIENT_ID=52ce1a10-bf5c-4c81-8edf-eea3af95da84 +DRUPAL_CLIENT_SECRET=SA9AGbHnx6pOamaAus2f9LG9XudHFjKs +DRUPAL_SITE_ID=example_marketing +DRUPAL_PREVIEW_SECRET=secret +DRUPAL_FRONT_PAGE=/home \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3aa970b --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ +.next + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +/certificates/* + +cypress/screenshots +cypress/videos \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..455bd9c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,531 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.5.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.4.4...example-marketing@1.5.0) (2022-12-06) + +**Note:** Version bump only for package example-marketing + + + + + +## [1.4.4](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.4.3...example-marketing@1.4.4) (2022-12-06) + +**Note:** Version bump only for package example-marketing + + + + + +## [1.4.3](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.4.2...example-marketing@1.4.3) (2022-09-07) + +**Note:** Version bump only for package example-marketing + + + + + +## [1.4.2](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.4.1...example-marketing@1.4.2) (2022-08-09) + + +### Bug Fixes + +* Warning title element received an array with more than 1 element ([910ca41](https://github.com/chapter-three/next-drupal/commit/910ca41ca230e5948fbe9d880bed04232d004306)) + + + + + +## [1.4.1](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.4.0...example-marketing@1.4.1) (2022-07-29) + +**Note:** Version bump only for package example-marketing + + + + + +# [1.4.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.4.0-rc.0...example-marketing@1.4.0) (2022-06-14) + +**Note:** Version bump only for package example-marketing + + + + + +# [1.4.0-rc.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.3.3-rc.1...example-marketing@1.4.0-rc.0) (2022-06-14) + + +### Features + +* rename Experimental_DrupalClient to DrupalClient ([fc549ec](https://github.com/chapter-three/next-drupal/commit/fc549ecab94a5a1e67f38b4e951351365adbb1f5)) + + + + + +## [1.3.3-rc.1](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.3.3-rc.0...example-marketing@1.3.3-rc.1) (2022-06-10) + +**Note:** Version bump only for package example-marketing + + + + + +## [1.3.3-rc.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.3.3-alpha.0...example-marketing@1.3.3-rc.0) (2022-06-06) + +**Note:** Version bump only for package example-marketing + + + + + +## [1.3.3-alpha.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.3.2...example-marketing@1.3.3-alpha.0) (2022-06-02) + +**Note:** Version bump only for package example-marketing + + + + + +## [1.3.2](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.3.1...example-marketing@1.3.2) (2022-05-02) + +**Note:** Version bump only for package example-marketing + + + + + +## [1.3.1](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.3.0...example-marketing@1.3.1) (2022-04-25) + +**Note:** Version bump only for package example-marketing + + + + + +# [1.3.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.3.0-rc.0...example-marketing@1.3.0) (2022-04-19) + +**Note:** Version bump only for package example-marketing + + + + + +# [1.3.0-rc.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.3.0-alpha.0...example-marketing@1.3.0-rc.0) (2022-04-19) + +**Note:** Version bump only for package example-marketing + + + + + +# [1.3.0-alpha.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.2.2...example-marketing@1.3.0-alpha.0) (2022-04-18) + + +### Bug Fixes + +* **example-marketing:** update slug ([cd6f9cc](https://github.com/chapter-three/next-drupal/commit/cd6f9ccdb783f95bc95d5dc7a551edcd5b2601b2)) +* update examples ([c00cafb](https://github.com/chapter-three/next-drupal/commit/c00cafbf3c667265fd6f0478164808664f778433)) + + +### Features + +* add example-cache ([2b29d66](https://github.com/chapter-three/next-drupal/commit/2b29d66c8cddb66a331e3b588da8140a4e4ba61e)) +* **next-drupal:** rename DrupalClient to Experimental_DrupalClient ([0d5cf2f](https://github.com/chapter-three/next-drupal/commit/0d5cf2f44b503a2d8e61eee19146fd5b797356ab)) + + +### Reverts + +* Revert "chore: revert example-marketing" ([88a9505](https://github.com/chapter-three/next-drupal/commit/88a950508617e3e94a2b6504bb0ea95d7574c3b9)) + + + + + +## [1.2.2](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.2.1...example-marketing@1.2.2) (2022-04-11) + +**Note:** Version bump only for package example-marketing + + + + + +## [1.2.1](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.2.0...example-marketing@1.2.1) (2022-03-28) + + +### Bug Fixes + +* **example-blog:** switch to manual revalidation ([933ca86](https://github.com/chapter-three/next-drupal/commit/933ca86d47a35dc3bf2a120466f1fec197ca8b61)) +* **example-marketing:** turn off revalidate for pages ([22e8bf1](https://github.com/chapter-three/next-drupal/commit/22e8bf1f333e08aa6f13b6038e362bf826133acb)) + + + + + +# [1.2.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.1.1...example-marketing@1.2.0) (2022-02-24) + + +### Bug Fixes + +* **example-marketing:** add next-transpile-modules ([b574e30](https://github.com/chapter-three/next-drupal/commit/b574e3077ee90d742b9ec49713597bb4b8d8005e)) +* **example-marketing:** add tailwindcss forms ([e9b583f](https://github.com/chapter-three/next-drupal/commit/e9b583f645e2a8aa9a5bf70cc132fc31b2ebb6b3)) +* **example-marketing:** debug code for translatePath in getStaticProps ([c14b690](https://github.com/chapter-three/next-drupal/commit/c14b690fdc3bd2b1125ba080f0343293426f9e6b)) +* **example-marketing:** remove next-transpile-modules ([5b248c2](https://github.com/chapter-three/next-drupal/commit/5b248c2e4d44c9e2acf363743904e4078c5ec610)) +* **example-marketing:** remove tailwind forms ([630fc24](https://github.com/chapter-three/next-drupal/commit/630fc24e126560f1dccc6f495079e895fa4f70ca)) +* **example-marketing:** switch to next/link ([b39da07](https://github.com/chapter-three/next-drupal/commit/b39da077b2bb4c6cbbbd51a533aa7d061842c532)) +* **example-marketing:** update meta component ([4d4e99b](https://github.com/chapter-three/next-drupal/commit/4d4e99b7c9206bc05117ac10868c12e696f53dae)) +* **example-marketing:** update tailwindcss ([943d7f1](https://github.com/chapter-three/next-drupal/commit/943d7f1c9a37418125053e897b67b070065479c4)) +* **example-marketing:** use local version for next-drupal ([8903fc9](https://github.com/chapter-three/next-drupal/commit/8903fc9e79d29c0e4a87acd7357229c5fe1bc61b)) +* update gitignore ([0e05896](https://github.com/chapter-three/next-drupal/commit/0e05896f06a6a48bf82db4830c085e6f9c5e7b84)) + + +### Features + +* **example-marketing:** add revalidate api ([bf402b1](https://github.com/chapter-three/next-drupal/commit/bf402b1a8ae4937cb91cd636c1256161da7d612e)) +* bump all examples to next 12.1.0 ([00b15f2](https://github.com/chapter-three/next-drupal/commit/00b15f2b308a0a9fcb298789a9ca712f4efa7eff)) +* **example-marketing:** add meta og image ([116e29f](https://github.com/chapter-three/next-drupal/commit/116e29f3171b40b274435f1ac064567770ed8f32)) + + + + + +## [1.1.1](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.1.0...example-marketing@1.1.1) (2022-01-17) + +**Note:** Version bump only for package example-marketing + + + + + +# [1.1.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.0.1...example-marketing@1.1.0) (2022-01-12) + + +### Features + +* **example-marketing:** handle redirects ([ba12711](https://github.com/chapter-three/next-drupal/commit/ba1271155ebb6dda80dac77e29683b07021c8e73)) + + + + + +## [1.0.1](https://github.com/chapter-three/next-drupal/compare/example-marketing@1.0.0...example-marketing@1.0.1) (2021-12-21) + +**Note:** Version bump only for package example-marketing + + + + + +# [1.0.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.17.0...example-marketing@1.0.0) (2021-12-03) + +**Note:** Version bump only for package example-marketing + + + + + +# [0.17.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.16.0...example-marketing@0.17.0) (2021-11-24) + + +### Bug Fixes + +* update .env files ([8c3207b](https://github.com/chapter-three/next-drupal/commit/8c3207b79bc641b605c11cec3fd556d0f71f1e72)) +* update page for marketing ([e527b36](https://github.com/chapter-three/next-drupal/commit/e527b360da861d3c69ca9a01954e851231b3ac51)) + + +### Features + +* **drupal:** update example modules ([3f2b578](https://github.com/chapter-three/next-drupal/commit/3f2b57822226e587e590fdcc5f760cae0b11d97f)) + + + + + +# [0.16.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.16.0-alpha.0...example-marketing@0.16.0) (2021-11-01) + +**Note:** Version bump only for package example-marketing + + + + + +# [0.16.0-alpha.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.15.2...example-marketing@0.16.0-alpha.0) (2021-11-01) + +**Note:** Version bump only for package example-marketing + + + + + +## [0.15.2](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.15.1...example-marketing@0.15.2) (2021-10-14) + +**Note:** Version bump only for package example-marketing + + + + + +## [0.15.1](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.15.0...example-marketing@0.15.1) (2021-10-14) + +**Note:** Version bump only for package example-marketing + + + + + +# [0.15.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.14.0...example-marketing@0.15.0) (2021-10-13) + + +### Bug Fixes + +* rename repo links ([48d52dd](https://github.com/chapter-three/next-drupal/commit/48d52dde79f69396ef706d152c03670117b6a480)) + + +### Features + +* **example-marketing:** add drupal jsonapi params example ([e663466](https://github.com/chapter-three/next-drupal/commit/e6634662139c614b36fb76370020298f9f89352a)) + + + + + +# [0.14.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.13.2...example-marketing@0.14.0) (2021-08-11) + + +### Features + +* **example-marketing:** implement route sync ([b49e531](https://github.com/chapter-three/next-drupal/commit/b49e5314692a037b6e420167609dfec216551b77)) + + + + + +## [0.13.2](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.13.1...example-marketing@0.13.2) (2021-08-07) + +**Note:** Version bump only for package example-marketing + + + + + +## [0.13.1](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.13.0...example-marketing@0.13.1) (2021-06-22) + + +### Bug Fixes + +* **example-marketing:** add missing dependency ([7a182f6](https://github.com/chapter-three/next-drupal/commit/7a182f68f0cf86b03977009c07660e886c9635eb)) +* **example-marketing:** use fallback blocking ([be5a2d3](https://github.com/chapter-three/next-drupal/commit/be5a2d32764d1c510ffb335cb5076cc927c65e0e)) + + + + + +# [0.13.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.12.0...example-marketing@0.13.0) (2021-06-16) + + +### Features + +* update to nextjs 11 ([1e46e44](https://github.com/chapter-three/next-drupal/commit/1e46e44ab5eb9d961e95dcc87d51282178f02bb2)) + + + + + +# [0.12.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.11.0...example-marketing@0.12.0) (2021-06-16) + + +### Features + +* **example-marketing:** add pagination to view ([feb6543](https://github.com/chapter-three/next-drupal/commit/feb6543be9a201f02b7afee38a3fed5c170de85b)) + + + + + +# [0.11.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.10.0...example-marketing@0.11.0) (2021-06-15) + + +### Bug Fixes + +* **example-marketing:** remove unused locale ([412e39d](https://github.com/chapter-three/next-drupal/commit/412e39d7ce9103f845fafaff8172c1e07bddb442)) + + +### Features + +* **example-marketing:** add properties view ([2b2804f](https://github.com/chapter-three/next-drupal/commit/2b2804f835a375bdc5076cbd4148604c964dbd06)) + + + + + +# [0.10.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.9.2...example-marketing@0.10.0) (2021-06-14) + + +### Features + +* **example-marketing:** update site to work with latest next-drupal ([9f27e7a](https://github.com/chapter-three/next-drupal/commit/9f27e7ae2b9c775ba4fe7d99fdfa0b8240a3dd90)) + + + + + +## [0.9.2](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.9.1...example-marketing@0.9.2) (2021-06-13) + +**Note:** Version bump only for package example-marketing + + + + + +## [0.9.1](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.9.0...example-marketing@0.9.1) (2021-06-13) + + +### Bug Fixes + +* update sites to handle unpublished entities ([93dd678](https://github.com/chapter-three/next-drupal/commit/93dd6786caff73398dd291c84b41d45c5bc50645)) + + + + + +# [0.9.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.8.0...example-marketing@0.9.0) (2021-06-13) + + +### Bug Fixes + +* update marketing site ([08863e6](https://github.com/chapter-three/next-drupal/commit/08863e6e8ab273799f1b1fcab793fdf51ad1d04b)) +* update preview ([f01ca6b](https://github.com/chapter-three/next-drupal/commit/f01ca6b9f68ac92c587b11c6e05f1145a57e8995)) + + +### Features + +* add support for basic page to example-marketing ([f9c5d1b](https://github.com/chapter-three/next-drupal/commit/f9c5d1b81697c59bc15b0372700fa2e979de9a00)) + + + + + +# [0.8.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.7.0...example-marketing@0.8.0) (2021-06-11) + + +### Features + +* update all sites ([ca9b2e9](https://github.com/chapter-three/next-drupal/commit/ca9b2e964c5a7fe591602465f2c2516eb4a54a1b)) + + + + + +# [0.7.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.6.0...example-marketing@0.7.0) (2021-06-10) + + +### Features + +* **examples:** update examples to use the new menu hook ([f9169e3](https://github.com/chapter-three/next-drupal/commit/f9169e34ab76584db855f1a69df027024156afff)) + + + + + +# [0.6.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.5.0...example-marketing@0.6.0) (2021-05-17) + + +### Features + +* add getEntityByPath ([072ead7](https://github.com/chapter-three/next-drupal/commit/072ead7ecc3b7f158e4b81e03d17f0bf1a5b511c)) + + + + + +# [0.5.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.4.1...example-marketing@0.5.0) (2021-05-17) + + +### Features + +* deserialize entities by default ([8b53ae2](https://github.com/chapter-three/next-drupal/commit/8b53ae222717b8983568194373be04903944a032)) + + + + + +## [0.4.1](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.4.0...example-marketing@0.4.1) (2021-05-07) + +**Note:** Version bump only for package example-marketing + + + + + +# [0.4.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.3.0...example-marketing@0.4.0) (2021-05-07) + + +### Features + +* add a no-preview example ([db48c6e](https://github.com/chapter-three/next-drupal/commit/db48c6e90ae5100eafb25d3b5688b5ef8131c477)) + + + + + +# [0.3.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.2.1...example-marketing@0.3.0) (2021-04-29) + + +### Bug Fixes + +* update .env.example ([467047b](https://github.com/chapter-three/next-drupal/commit/467047b010f54394c52760b9db960b06ee48db61)) + + +### Features + +* **www:** update to reflexjs 2.0.0-alpha-5 ([3b28c84](https://github.com/chapter-three/next-drupal/commit/3b28c84e9b7eefd4892aaf22dea0dd2512091b93)) +* update examples to reflexjs 2.0.0-alpha-4 ([49816ee](https://github.com/chapter-three/next-drupal/commit/49816ee6ba0f669d45cee6930b449547200ce1c7)) + + + + + +## [0.2.1](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.2.0...example-marketing@0.2.1) (2021-02-02) + +**Note:** Version bump only for package example-marketing + + + + + +# [0.2.0](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.1.1...example-marketing@0.2.0) (2021-02-01) + + +### Bug Fixes + +* make image domain configurable via env ([9b66159](https://github.com/chapter-three/next-drupal/commit/9b66159561a5e0bf17b4e73c4cde318e06fe938d)) +* update examples ([e70ed45](https://github.com/chapter-three/next-drupal/commit/e70ed459294cb8945f42d04cf7bd20a54ab9fe77)) + + +### Features + +* implement filter by entity reference ([eeade94](https://github.com/chapter-three/next-drupal/commit/eeade9485caaff587735d5d8211a86a88ca8847f)) + + + + + +## [0.1.1](https://github.com/chapter-three/next-drupal/compare/example-marketing@0.1.0...example-marketing@0.1.1) (2021-02-01) + + +### Bug Fixes + +* update example sites ([33143d0](https://github.com/chapter-three/next-drupal/commit/33143d0d5229be6424c41ace2ad846c0d85447d9)) + + + + + +# 0.1.0 (2021-01-31) + + +### Features + +* add marketing site ([8462d7c](https://github.com/chapter-three/next-drupal/commit/8462d7cfcf623a9e8ca03456ebed0bb6ab838e11)) diff --git a/README.md b/README.md new file mode 100644 index 0000000..b63cb32 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# example-marketing + +An example marketing site built using Drupal + JSON:API. + +Pages are built from the Landing page node type and paragraphs sourced from `/drupal`. + +See https://demo.next-drupal.org + +## License + +Licensed under the [MIT license](https://github.com/chapter-three/next-drupal/blob/master/LICENSE). diff --git a/components/footer.tsx b/components/footer.tsx new file mode 100644 index 0000000..567ed21 --- /dev/null +++ b/components/footer.tsx @@ -0,0 +1,31 @@ +import Link from "next/link" +import { DrupalMenuLinkContent } from "next-drupal" + +interface FooterProps { + links: DrupalMenuLinkContent[] +} + +export function Footer({ links }: FooterProps) { + return ( + + ) +} diff --git a/components/form-item.tsx b/components/form-item.tsx new file mode 100644 index 0000000..06fdd98 --- /dev/null +++ b/components/form-item.tsx @@ -0,0 +1,16 @@ +interface FormItemProps { + label: string + name: string + children?: React.ReactNode +} + +export function FormItem({ label, name, children, ...props }: FormItemProps) { + return ( +
+ + {children} +
+ ) +} diff --git a/components/formatted-text.tsx b/components/formatted-text.tsx new file mode 100644 index 0000000..cdb224e --- /dev/null +++ b/components/formatted-text.tsx @@ -0,0 +1,64 @@ +import Image from "next/image" +import { HTMLReactParserOptions, domToReact } from "html-react-parser" +import { Element } from "domhandler/lib/node" +import parse from "html-react-parser" + +import { isRelative } from "lib/utils/is-relative" +import Link from "next/link" + +const options: HTMLReactParserOptions = { + replace: (domNode) => { + if (domNode instanceof Element) { + if (domNode.name === "img") { + const { src, alt, width = "100px", height = "100px" } = domNode.attribs + + if (isRelative(src)) { + return ( + {alt} + ) + } + } + + if (domNode.name === "a") { + const { href, class: className } = domNode.attribs + + if (href && isRelative(href)) { + return ( + + {domToReact(domNode.children)} + + ) + } + } + + if (domNode.name === "input") { + if (domNode.attribs.value === "") { + delete domNode.attribs.value + } + + return domNode + } + } + }, +} + +interface FormattedTextProps extends React.HTMLAttributes { + format?: string + processed: string + value?: string +} + +export function FormattedText({ processed, ...props }: FormattedTextProps) { + return ( +
+ {parse(processed, options)} +
+ ) +} diff --git a/components/layout.tsx b/components/layout.tsx new file mode 100644 index 0000000..35dc96c --- /dev/null +++ b/components/layout.tsx @@ -0,0 +1,23 @@ +import * as React from "react" +import { DrupalMenuLinkContent } from "next-drupal" + +import { Navbar } from "components/navbar" +import { Footer } from "components/footer" + +export interface LayoutProps { + menus: { + main: DrupalMenuLinkContent[] + footer: DrupalMenuLinkContent[] + } + children?: React.ReactNode +} + +export function Layout({ menus, children }: LayoutProps) { + return ( +
+ +
{children}
+
+
+ ) +} diff --git a/components/link.tsx b/components/link.tsx new file mode 100644 index 0000000..0ae7fea --- /dev/null +++ b/components/link.tsx @@ -0,0 +1,38 @@ +import * as React from "react" +import NextLink from "next/link" +import type { LinkProps as NextLinkProps } from "next/link" +import { useRouter } from "next/router" + +import { isRelative } from "lib/utils/is-relative" + +interface LinkProps extends NextLinkProps { + href: string + children?: React.ReactElement +} + +export function Link({ href, passHref, as, children, ...props }: LinkProps) { + const router = useRouter() + + if (!href) { + return null + } + + // Use Next Link for internal links, and for others. + if (isRelative(href)) { + // Disable prefetching in preview mode. + // We do this here inside of inline `prefetch={!router.isPreview}` + // because `prefetch={true}` is not allowed. + // See https://nextjs.org/docs/messages/prefetch-true-deprecated + const linkProps = router.isPreview ? { prefetch: false, ...props } : props + + return ( + + {children} + + ) + } + + return React.cloneElement(children, { + href, + }) +} diff --git a/components/links.tsx b/components/links.tsx new file mode 100644 index 0000000..eba8cfb --- /dev/null +++ b/components/links.tsx @@ -0,0 +1,33 @@ +import classNames from "classnames" +import Link from "next/link" + +export interface LinksProps { + links: { + title: string + uri: string + options?: [] + }[] +} + +export function Links({ links }: LinksProps) { + if (!links.length) return null + + return ( +
+ {links.map((link, index) => ( + + + {link.title} + + + ))} +
+ ) +} diff --git a/components/locale-switcher.tsx b/components/locale-switcher.tsx new file mode 100644 index 0000000..6eebbfc --- /dev/null +++ b/components/locale-switcher.tsx @@ -0,0 +1,25 @@ +import classNames from "classnames" +import Link from "next/link" +import { useRouter } from "next/router" + +export function LocaleSwitcher() { + const { locales, asPath, locale: currentLocale } = useRouter() + + return ( +
+ {locales.map((locale) => ( + + + {locale} + + + ))} +
+ ) +} diff --git a/components/media--image.tsx b/components/media--image.tsx new file mode 100644 index 0000000..671442e --- /dev/null +++ b/components/media--image.tsx @@ -0,0 +1,34 @@ +import Image, { ImageProps } from "next/image" + +import { absoluteURL } from "lib/utils/absolute-url" +import { MediaProps } from "components/media" + +interface MediaImageProps extends MediaProps, Partial {} + +export function MediaImage({ + media, + layout = "responsive", + objectFit, + width, + height, + ...props +}: MediaImageProps) { + const image = media?.field_media_image + + if (!image) { + return null + } + + return ( + {image.resourceIdObjMeta.alt + ) +} diff --git a/components/media.tsx b/components/media.tsx new file mode 100644 index 0000000..71c324d --- /dev/null +++ b/components/media.tsx @@ -0,0 +1,25 @@ +import { DrupalMedia } from "next-drupal" + +import { MediaImage } from "components/media--image" + +const mediaTypes = { + "media--image": MediaImage, +} + +export interface MediaProps { + media: DrupalMedia +} + +export function Media({ media, ...props }: MediaProps) { + if (!media) { + return null + } + + const Component = mediaTypes[media.type] + + if (!Component) { + return null + } + + return +} diff --git a/components/meta.tsx b/components/meta.tsx new file mode 100644 index 0000000..71ca3ff --- /dev/null +++ b/components/meta.tsx @@ -0,0 +1,53 @@ +import Head from "next/head" +import { useRouter } from "next/router" +import { DrupalMetatag } from "types/drupal" + +interface MetaProps { + title?: string + path?: string + tags?: DrupalMetatag[] +} + +export function Meta({ title, tags }: MetaProps) { + const router = useRouter() + + return ( + + + {tags?.length ? ( + tags.map((tag, index) => { + if (tag.attributes.rel === "canonical") { + return null + } + + if (tag.attributes.name === "title") { + return ( + {tag.attributes.content} + ) + } + const Tag = tag.tag as keyof JSX.IntrinsicElements + return + }) + ) : ( + <> + {`${title} | Next.js for Drupal`} + + + + + + )} + + ) +} diff --git a/components/navbar.tsx b/components/navbar.tsx new file mode 100644 index 0000000..038569e --- /dev/null +++ b/components/navbar.tsx @@ -0,0 +1,64 @@ +import React from "react" +import { useRouter } from "next/router" +import { DrupalMenuLinkContent } from "next-drupal" +import classNames from "classnames" + +import { LocaleSwitcher } from "components/locale-switcher" +import Link from "next/link" + +interface NavbarProps { + links: DrupalMenuLinkContent[] +} + +export function Navbar({ links, ...props }: NavbarProps) { + const { locale } = useRouter() + + return ( +
+
+ + Marketing + + {links ? : null} +
+ +
+
+
+ ) +} + +function Menu({ items }: { items: DrupalMenuLinkContent[] }) { + return ( +
    + {items.map((item) => ( + + ))} +
+ ) +} + +function MenuLink({ link }: { link: DrupalMenuLinkContent }) { + const { asPath } = useRouter() + + return ( +
  • + + + {link.title} + + +
  • + ) +} diff --git a/components/node--article.tsx b/components/node--article.tsx new file mode 100644 index 0000000..6019b14 --- /dev/null +++ b/components/node--article.tsx @@ -0,0 +1,108 @@ +import Link from "next/link" +import Image from "next/image" + +import { formatDate } from "lib/utils/format-date" +import { absoluteURL } from "lib/utils/absolute-url" +import { FormattedText } from "components/formatted-text" +import { NodeProps } from "components/node" + +export function NodeArticle({ node, viewMode, ...props }: NodeProps) { + if (viewMode === "teaser") { + return + } + + if (viewMode === "full") { + return + } + + return null +} + +export function NodeArticleFull({ node, ...props }) { + return ( +
    +
    +

    + {node.title} +

    +
    +
    + {node.uid?.field_name ? ( + + Posted by {node.uid?.field_name} + + ) : null} + - {formatDate(node.created)} +
    + {node.body?.summary ?

    {node.body.summary}

    : null} + {node.field_image?.uri && ( + + )} + {node.body?.processed && ( + + )} +
    +
    +
    + ) +} + +export function NodeArticleTeaser({ node, ...props }) { + return ( +
    + {node.field_image?.uri && ( +
    + +
    + )} +

    + + {node.title} + +

    +
    + {node.uid?.field_name ? ( + + Posted by {node.uid?.field_name} + + ) : null} + - {formatDate(node.created)} +
    + {node.body?.summary ? ( +

    + {node.body.summary} +

    + ) : null} + + + Read more + + + + + +
    + ) +} diff --git a/components/node--landing-page.tsx b/components/node--landing-page.tsx new file mode 100644 index 0000000..ed368d0 --- /dev/null +++ b/components/node--landing-page.tsx @@ -0,0 +1,20 @@ +import { DrupalNode } from "next-drupal" + +import { Paragraph } from "components/paragraph" + +interface NodeLandingPage { + node: DrupalNode + viewMode?: string +} + +export function NodeLandingPage({ node }: NodeLandingPage) { + if (!node.field_sections?.length) return null + + return node.field_sections.map((paragraph) => { + if (paragraph.type === "paragraph--from_library") { + paragraph = paragraph.field_reusable_paragraph.paragraphs + } + + return + }) +} diff --git a/components/node--page.tsx b/components/node--page.tsx new file mode 100644 index 0000000..a928ffc --- /dev/null +++ b/components/node--page.tsx @@ -0,0 +1,17 @@ +import { FormattedText } from "components/formatted-text" +import { NodeProps } from "components/node" + +export function NodePage({ node, ...props }: NodeProps) { + delete props.viewMode + + return ( +
    +

    + {node.title} +

    +
    + {node.body && } +
    +
    + ) +} diff --git a/components/node--property.tsx b/components/node--property.tsx new file mode 100644 index 0000000..764d750 --- /dev/null +++ b/components/node--property.tsx @@ -0,0 +1,127 @@ +import Image from "next/image" + +import { NodeProps } from "components/node" + +export function NodeProperty({ node, viewMode, ...props }: NodeProps) { + if (viewMode === "list") { + return + } + + return +} + +export function NodePropertyGrid({ node }) { + const thumbnail = node.field_images?.[0].field_media_image + + return ( +
    + {node.field_status ? ( +

    + {node.field_status === "rent" ? "For Rent" : "For Sale"} +

    + ) : null} + {thumbnail && ( + + )} +
    +
    +

    {node.field_location.name}

    + {node.field_size && ( +

    + {node.field_size} + sqft +

    + )} +
    +

    {node.title}

    + {node.field_teaser && ( +

    {node.field_teaser}

    + )} +
    +
    +
    + {node.field_beds && ( +

    + {node.field_beds} + beds +

    + )} + {node.field_baths && ( +

    + {node.field_baths} + baths +

    + )} +
    +
    +
    +
    + ) +} + +export function NodePropertyList({ node }) { + const thumbnail = node.field_images?.[0].field_media_image + + return ( +
    + {node.field_status ? ( +

    + {node.field_status === "rent" ? "For Rent" : "For Sale"} +

    + ) : null} +
    + {thumbnail && ( + + )} +
    +
    +

    {node.field_location.name}

    + {node.field_size && ( +

    + {node.field_size} + sqft +

    + )} +
    +

    {node.title}

    + {node.field_teaser && ( +

    {node.field_teaser}

    + )} +
    +
    +
    + {node.field_beds && ( +

    + {node.field_beds} + beds +

    + )} + {node.field_baths && ( +

    + {node.field_baths} + baths +

    + )} +
    +
    +
    +
    +
    + ) +} diff --git a/components/node.tsx b/components/node.tsx new file mode 100644 index 0000000..d7b2664 --- /dev/null +++ b/components/node.tsx @@ -0,0 +1,32 @@ +import { DrupalNode } from "next-drupal" + +import { NodePage } from "components/node--page" +import { NodeArticle } from "components/node--article" +import { NodeLandingPage } from "components/node--landing-page" +import { NodeProperty } from "components/node--property" + +const nodeTypes = { + "node--page": NodePage, + "node--article": NodeArticle, + "node--landing_page": NodeLandingPage, + "node--property_listing": NodeProperty, +} + +export interface NodeProps { + node: DrupalNode + viewMode?: string +} + +export function Node({ node, viewMode = "full", ...props }: NodeProps) { + if (!node) { + return null + } + + const Component = nodeTypes[node.type] + + if (!Component) { + return null + } + + return +} diff --git a/components/pager.tsx b/components/pager.tsx new file mode 100644 index 0000000..4ae1a9a --- /dev/null +++ b/components/pager.tsx @@ -0,0 +1,80 @@ +import classNames from "classnames" + +import { usePagination, usePaginationProps } from "hooks/use-pagination" +import Link from "next/link" + +export interface PagerProps extends React.HTMLAttributes { + current: number + total: number + href: usePaginationProps["href"] +} + +export function Pager({ current, total, href, ...props }: PagerProps) { + const items = usePagination({ + current, + total, + href, + }) + + return ( + + ) +} diff --git a/components/paragraph--cards.tsx b/components/paragraph--cards.tsx new file mode 100644 index 0000000..4e9e712 --- /dev/null +++ b/components/paragraph--cards.tsx @@ -0,0 +1,38 @@ +import { ParagraphProps } from "components/paragraph" +import { SectionHeader } from "components/section-header" +import { FormattedText } from "components/formatted-text" +import { Section } from "components/section" + +export function ParagraphCards({ paragraph, ...props }: ParagraphProps) { + return ( +
    + +
    + {paragraph.field_items?.length && ( +
    + {paragraph.field_items.map((card) => ( +
    +

    {card.field_heading}

    + {card.field_text?.processed && ( + + )} +
    + ))} +
    + )} +
    +
    + ) +} diff --git a/components/paragraph--faq.tsx b/components/paragraph--faq.tsx new file mode 100644 index 0000000..b9b1714 --- /dev/null +++ b/components/paragraph--faq.tsx @@ -0,0 +1,41 @@ +import { ParagraphProps } from "components/paragraph" +import { Section } from "components/section" +import { SectionHeader } from "components/section-header" +import { FormattedText } from "./formatted-text" + +export function ParagraphFAQ({ paragraph, ...props }: ParagraphProps) { + return ( +
    + +
    + {paragraph.field_items && ( +
    + {paragraph.field_items.map((card) => ( +
    +

    + {card.field_heading} +

    + {card.field_text?.processed && ( + + )} +
    + ))} +
    + )} +
    +
    + ) +} diff --git a/components/paragraph--feature.tsx b/components/paragraph--feature.tsx new file mode 100644 index 0000000..5682a7c --- /dev/null +++ b/components/paragraph--feature.tsx @@ -0,0 +1,60 @@ +import Image from "next/image" +import classNames from "classnames" + +import { absoluteURL } from "lib/utils/absolute-url" +import { Links } from "components/links" +import { FormattedText } from "components/formatted-text" +import { ParagraphProps } from "components/paragraph" +import { Section } from "components/section" + +export function ParagraphFeature({ paragraph, ...props }: ParagraphProps) { + return ( +
    +
    +
    + {paragraph.field_media?.field_media_image && ( +
    + { +
    + )} +
    + {paragraph.field_heading && ( +

    + {paragraph.field_heading} +

    + )} + + {paragraph.field_link && } +
    +
    +
    +
    + ) +} diff --git a/components/paragraph--hero.tsx b/components/paragraph--hero.tsx new file mode 100644 index 0000000..442e933 --- /dev/null +++ b/components/paragraph--hero.tsx @@ -0,0 +1,38 @@ +import classNames from "classnames" + +import { ParagraphProps } from "components/paragraph" +import { MediaImage } from "components/media--image" +import { SectionHeader } from "components/section-header" + +export function ParagraphHero({ paragraph, ...props }: ParagraphProps) { + return ( +
    + +
    + {paragraph.field_media && ( +
    + +
    + )} +
    +
    + ) +} diff --git a/components/paragraph--view.tsx b/components/paragraph--view.tsx new file mode 100644 index 0000000..df03229 --- /dev/null +++ b/components/paragraph--view.tsx @@ -0,0 +1,9 @@ +import { ViewPropertiesListing } from "components/view--properties_listing" + +export function ParagraphView({ paragraph, ...props }) { + if (paragraph.field_view.name === "properties--listing") { + return + } + + return null +} diff --git a/components/paragraph.tsx b/components/paragraph.tsx new file mode 100644 index 0000000..92dd785 --- /dev/null +++ b/components/paragraph.tsx @@ -0,0 +1,33 @@ +import { DrupalParagraph } from "next-drupal" + +import { ParagraphCards } from "components/paragraph--cards" +import { ParagraphFAQ } from "components/paragraph--faq" +import { ParagraphFeature } from "components/paragraph--feature" +import { ParagraphHero } from "components/paragraph--hero" +import { ParagraphView } from "components/paragraph--view" + +const paragraphTypes = { + "paragraph--cards": ParagraphCards, + "paragraph--faq": ParagraphFAQ, + "paragraph--feature": ParagraphFeature, + "paragraph--hero": ParagraphHero, + "paragraph--view": ParagraphView, +} + +export interface ParagraphProps { + paragraph: DrupalParagraph +} + +export function Paragraph({ paragraph, ...props }: ParagraphProps) { + if (!paragraph) { + return null + } + + const Component = paragraphTypes[paragraph.type] + + if (!Component) { + return null + } + + return +} diff --git a/components/section-header.tsx b/components/section-header.tsx new file mode 100644 index 0000000..3595dd3 --- /dev/null +++ b/components/section-header.tsx @@ -0,0 +1,35 @@ +import { LinksProps, Links } from "components/links" +import { FormattedText } from "components/formatted-text" + +interface SectionHeaderProps { + level?: number + heading: string + text: string + links?: LinksProps["links"] +} + +export function SectionHeader({ + level = 2, + heading, + text, + links, + ...props +}: SectionHeaderProps) { + const HeadingLevel = `h${level}` as keyof JSX.IntrinsicElements + return ( +
    + {heading && ( + + {heading} + + )} + {text && ( + + )} + {links?.length ? : null} +
    + ) +} diff --git a/components/section.tsx b/components/section.tsx new file mode 100644 index 0000000..17dc6c3 --- /dev/null +++ b/components/section.tsx @@ -0,0 +1,17 @@ +import classNames from "classnames" + +interface SectionProps extends React.HTMLAttributes { + backgroundColor?: string + children?: React.ReactNode +} + +export function Section({ backgroundColor, children, ...props }: SectionProps) { + return ( +
    + {children} +
    + ) +} diff --git a/components/view--properties_listing.tsx b/components/view--properties_listing.tsx new file mode 100644 index 0000000..9b8ce8d --- /dev/null +++ b/components/view--properties_listing.tsx @@ -0,0 +1,290 @@ +import React from "react" +import { useQuery, useQueryClient } from "@tanstack/react-query" +import { useForm } from "react-hook-form" +import { deserialize } from "next-drupal" +import classNames from "classnames" + +import { FormItem } from "components/form-item" +import { Section } from "components/section" +import { Node } from "components/node" + +const filters = { location: "All", status: "All", beds: "1", baths: "1" } + +async function fetchView(url, params) { + const _url = new URL(url) + _url.search = new URLSearchParams(params).toString() + + const result = await fetch(_url.toString()) + + if (!result.ok) { + throw new Error(result.statusText) + } + + const data = await result.json() + + return { + results: deserialize(data), + meta: data.meta, + links: data.links, + } +} + +export function ViewPropertiesListing({ view: initialView, ...props }) { + const [page, setPage] = React.useState(0) + const queryClient = useQueryClient() + const [display, setDisplay] = React.useState<"grid" | "list">("grid") + const { register, handleSubmit, getValues, formState, reset } = useForm({ + defaultValues: filters, + }) + const [locations, setLocations] = React.useState([]) + const { + data: view, + isLoading, + isPreviousData, + } = useQuery( + [initialView.name, page], + async () => { + // Build params from form values. + const values = getValues() + const params = { + page: page + "", + include: "field_location,field_images.field_media_image", + } + for (const filter of Object.keys(filters)) { + if (values[filter]) { + params[`views-filter[${filter}]`] = values[filter] + } + } + + return fetchView(initialView.links.self.href.split("?")[0], params) + }, + { + initialData: initialView, + keepPreviousData: true, + } + ) + + // Build locations dropdown from view results. + React.useEffect(() => { + const allLocations: { + name: string + id: string + }[] = view.results.map((result) => ({ + name: result.field_location.name, + id: result.field_location.drupal_internal__tid, + })) + + setLocations( + Array.from(new Map(allLocations.map((item) => [item.id, item])).values()) + ) + }, []) + + async function submitForm() { + setPage(0) + await queryClient.invalidateQueries(view.name) + } + + async function resetForm() { + reset() + submitForm() + } + + return ( +
    +
    +
    +
    +

    Find your place

    +
    +
    +
    +
    + + + + + + +
    + + + + + + +
    +
    +
    +
    + + +
    +
    +
    +
    + {view.results.length ? ( + <> +
    +

    + Found {view.meta?.count} properties. +

    +
    + + +
    +
    +
    + {view.results.map((node) => ( + + ))} +
    +
    +
    + + +
    + + ) : ( +

    No properties found.

    + )} +
    +
    +
    + ) +} diff --git a/cypress.json b/cypress.json new file mode 100644 index 0000000..399eea2 --- /dev/null +++ b/cypress.json @@ -0,0 +1,3 @@ +{ + "baseUrl": "http://localhost:3001" +} diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/cypress/integration/about.spec.tsx b/cypress/integration/about.spec.tsx new file mode 100644 index 0000000..3e869d0 --- /dev/null +++ b/cypress/integration/about.spec.tsx @@ -0,0 +1,14 @@ +/// + +context("About", () => { + beforeEach(() => { + cy.visit("/about") + }) + + it("should convert inline images to Next.js images", () => { + cy.get("img[data-nimg='intrinsic']") + cy.get("figcaption").contains("This is the caption") + }) +}) + +export {} diff --git a/cypress/integration/blog.spec.tsx b/cypress/integration/blog.spec.tsx new file mode 100644 index 0000000..dbd43a5 --- /dev/null +++ b/cypress/integration/blog.spec.tsx @@ -0,0 +1,66 @@ +/// + +context("Blog", () => { + beforeEach(() => { + cy.visit("/blog") + }) + + it("should render articles", () => { + cy.get("h1").contains("Latest Articles.") + cy.get("[data-cy=node--article]").should("have.length.gt", 1) + + cy.get("[data-cy=node--article] h2").contains( + "Dynamic Routing and Static Generation" + ) + cy.get("[data-cy=node--article] [data-cy=node--meta]").contains( + "Posted by Arshad" + ) + cy.get("[data-cy=node--article] [data-cy=node--meta]").contains( + "June 13, 2021" + ) + }) +}) + +context("Translation", () => { + beforeEach(() => { + cy.visit("/es/blog") + }) + + it("should render articles", () => { + cy.get("h1").contains("Últimas Publicaciones.") + cy.get("[data-cy=node--article]").should("have.length.gt", 1) + + cy.get("[data-cy=node--article] h2").contains( + "Enrutamiento dinámico y Generación Estática" + ) + cy.get("[data-cy=node--article] [data-cy=node--meta]").contains( + "Posted by Arshad" + ) + cy.get("[data-cy=node--article] [data-cy=node--meta]").contains( + "June 14, 2021" + ) + }) +}) + +context("Blog node", () => { + beforeEach(() => { + cy.visit("/blog/learn-how-pre-render-pages-using-static-generation-nextjs") + }) + + it("should render article node", () => { + cy.get("h1").contains( + "Learn How to Pre-render Pages Using Static Generation with Next.js" + ) + cy.get("[data-cy=node--article] [data-cy=node--body]").should( + "not.be.empty" + ) + cy.get("[data-cy=node--article] [data-cy=node--meta]").contains( + "Posted by Arshad" + ) + cy.get("[data-cy=node--article] [data-cy=node--meta]").contains( + "June 13, 2021" + ) + }) +}) + +export {} diff --git a/cypress/integration/home.spec.tsx b/cypress/integration/home.spec.tsx new file mode 100644 index 0000000..6506bea --- /dev/null +++ b/cypress/integration/home.spec.tsx @@ -0,0 +1,63 @@ +/// + +context("Home", () => { + beforeEach(() => { + cy.visit("") + }) + + it("should render paragraphs", () => { + cy.get("[data-cy=paragraph-hero] h1").contains("Build Something Amazing") + cy.get("[data-cy=paragraph-hero] p").contains( + "Must today firm from bag. Investment try cold a when capital. Everything wait person service." + ) + cy.get("[data-cy=paragraph-hero] [alt=Hero]").should("be.visible") + + cy.get("[data-cy=paragraph-feature] h2").contains("Marketing Strategy") + cy.get("[data-cy=paragraph-feature] img[alt=Feature]").should("be.visible") + + cy.get("[data-cy=paragraph-faq] h2").contains("Frequently Asked Questions") + cy.get("[data-cy=paragraph-faq] h3").contains( + "Move weight here just either attorney?" + ) + }) + + it("should render menu items", () => { + cy.get("[data-cy=navbar-menu] a").contains("Home") + cy.get("[data-cy=navbar-menu] a").contains("Blog") + cy.get("[data-cy=navbar-menu] a").contains("Properties") + cy.get("[data-cy=navbar-menu] a").contains("About") + cy.get("[data-cy=navbar-menu] a").contains("GitHub") + }) +}) + +context("Translation", () => { + beforeEach(() => { + cy.visit("/es") + }) + + it("should render paragraphs", () => { + cy.get("[data-cy=paragraph-hero] h1").contains("Construye Algo Asombroso") + cy.get("[data-cy=paragraph-hero] p").contains( + "Dicta laboriosam magnam possimus ad. Ratione rem nihil nostrum dolore reiciendis enim." + ) + cy.get("[data-cy=paragraph-hero] img[alt=Hero]").should("be.visible") + + cy.get("[data-cy=paragraph-feature] h2").contains("Marketing Strategy") + cy.get("[data-cy=paragraph-feature] img[alt=Feature]").should("be.visible") + + cy.get("[data-cy=paragraph-faq] h2").contains("Frequently Asked Questions") + cy.get("[data-cy=paragraph-faq] h3").contains( + "Move weight here just either attorney?" + ) + }) + + it("should render menu items localized", () => { + cy.get("[data-cy=navbar-menu] a").contains("Inicio") + cy.get("[data-cy=navbar-menu] a").contains("Blog") + cy.get("[data-cy=navbar-menu] a").contains("Propiedades") + cy.get("[data-cy=navbar-menu] a").contains("Acerca") + cy.get("[data-cy=navbar-menu] a").contains("GitHub") + }) +}) + +export {} diff --git a/cypress/integration/locale.spec.tsx b/cypress/integration/locale.spec.tsx new file mode 100644 index 0000000..2bba1b6 --- /dev/null +++ b/cypress/integration/locale.spec.tsx @@ -0,0 +1,19 @@ +/// + +context("Home", () => { + beforeEach(() => { + cy.visit("") + }) + + it("should switch locale when clicking on the locale switcher", () => { + cy.get("[data-cy=local-switcher-en]").contains("en") + cy.get("[data-cy=local-switcher-es]").contains("es") + + cy.get("[data-cy=paragraph-hero] h1").contains("Build Something Amazing") + cy.get("[data-cy=local-switcher-es]").click() + cy.url().should("include", "/es") + cy.get("[data-cy=paragraph-hero] h1").contains("Construye Algo Asombroso") + }) +}) + +export {} diff --git a/cypress/integration/properties.spec.tsx b/cypress/integration/properties.spec.tsx new file mode 100644 index 0000000..6acae2f --- /dev/null +++ b/cypress/integration/properties.spec.tsx @@ -0,0 +1,32 @@ +/// + +context("Properties", () => { + beforeEach(() => { + cy.visit("/properties") + }) + + it("should render view with meta and pager", () => { + cy.get("[data-cy=view--results]").contains("Found 12 properties.") + cy.get("[data-cy=node--property]").should("have.length", 4) + cy.get("[data-cy=node--property]") + .find("h4") + .contains("484 Robert Crest Apt. 875") + cy.get("[data-cy=pager-next]").should("not.be.disabled") + cy.get("[data-cy=pager-previous]").should("be.disabled") + + cy.get("[data-cy=pager-next]").click() + cy.get("[data-cy=node--property]").should("have.length", 4) + cy.get("[data-cy=pager-next]").should("not.be.disabled") + cy.get("[data-cy=pager-previous]").should("not.be.disabled") + }) + + it("should allow view to be filtered", () => { + cy.get("select[name='location']").select("San Francisco, CA") + cy.get("select[name='status']").select("For Sale") + cy.get("select[name='beds']").select("4") + cy.get("[data-cy=submit]").contains("Search").click() + cy.get("[data-cy=node--property]").should("have.length", 1) + }) +}) + +export {} diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js new file mode 100644 index 0000000..59b2bab --- /dev/null +++ b/cypress/plugins/index.js @@ -0,0 +1,22 @@ +/// +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +/** + * @type {Cypress.PluginConfig} + */ +// eslint-disable-next-line no-unused-vars +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +} diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 0000000..119ab03 --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) diff --git a/cypress/support/index.js b/cypress/support/index.js new file mode 100644 index 0000000..d68db96 --- /dev/null +++ b/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/hooks/use-pagination.tsx b/hooks/use-pagination.tsx new file mode 100644 index 0000000..984dfc0 --- /dev/null +++ b/hooks/use-pagination.tsx @@ -0,0 +1,83 @@ +import * as React from "react" +import { LinkProps } from "next/link" + +type PagerItemType = "page" | "previous" | "next" + +export interface PagerItem { + type: PagerItemType + page: number + display: string + href: LinkProps["href"] + isCurrent?: boolean +} + +export interface usePaginationProps { + total: number + current: number + href: (page: PagerItem["page"]) => LinkProps["href"] + show?: number +} + +export const usePagination = ({ + total, + current, + href, + show = 9, +}: usePaginationProps): PagerItem[] => { + return React.useMemo((): PagerItem[] => { + const pagerMiddle = Math.ceil(show / 2) + const pagerCurrent = current + 1 + let pagerFirst = pagerCurrent - pagerMiddle + 1 + let pagerLast = pagerCurrent + show - pagerMiddle + + show = total < show ? total : show + + // Adjust start end end based on position. + if (pagerLast > total) { + pagerFirst = pagerFirst + (total - pagerLast) + pagerLast = total + } + + if (pagerFirst <= 0) { + pagerFirst = 1 + pagerLast = pagerLast + (1 - pagerFirst) + } + + const items: PagerItem[] = [] + + if (current !== 0) { + items.push({ + type: "previous", + display: "Previous", + page: current - 1, + href: href(current - 1), + }) + } + + items.push( + ...Array.from(Array(show).keys()).map( + (pageNumber: number): PagerItem | null => { + const page = pageNumber + pagerFirst - 1 + return { + type: "page", + page, + display: `${page + 1}`, + isCurrent: page === current, + href: href(page), + } + } + ) + ) + + if (current !== total - 1) { + items.push({ + type: "next", + display: "Next", + page: current + 1, + href: href(current + 1), + }) + } + + return items + }, [total, current]) +} diff --git a/lib/drupal.ts b/lib/drupal.ts new file mode 100644 index 0000000..0afd727 --- /dev/null +++ b/lib/drupal.ts @@ -0,0 +1,13 @@ +import { DrupalClient } from "next-drupal" + +export const drupal = new DrupalClient( + process.env.NEXT_PUBLIC_DRUPAL_BASE_URL, + { + frontPage: "/home", + previewSecret: "secret", + auth: { + clientId: process.env.DRUPAL_CLIENT_ID, + clientSecret: process.env.DRUPAL_CLIENT_SECRET, + }, + } +) diff --git a/lib/get-menus.ts b/lib/get-menus.ts new file mode 100644 index 0000000..3fa58d1 --- /dev/null +++ b/lib/get-menus.ts @@ -0,0 +1,22 @@ +import { GetStaticPropsContext } from "next" +import { DrupalMenuLinkContent } from "next-drupal" +import { drupal } from "lib/drupal" + +export async function getMenus(context: GetStaticPropsContext): Promise<{ + main: DrupalMenuLinkContent[] + footer: DrupalMenuLinkContent[] +}> { + const { tree: main } = await drupal.getMenu("main", { + locale: context.locale, + defaultLocale: context.defaultLocale, + }) + const { tree: footer } = await drupal.getMenu("footer", { + locale: context.locale, + defaultLocale: context.defaultLocale, + }) + + return { + main, + footer, + } +} diff --git a/lib/get-params.ts b/lib/get-params.ts new file mode 100644 index 0000000..a40d5ea --- /dev/null +++ b/lib/get-params.ts @@ -0,0 +1,39 @@ +import { DrupalJsonApiParams } from "drupal-jsonapi-params" + +export function getParams(resourceType: string) { + const apiParams = new DrupalJsonApiParams().addFilter( + "field_site.meta.drupal_internal__target_id", + process.env.DRUPAL_SITE_ID + ) + + if (resourceType === "node--landing_page") { + apiParams + .addInclude([ + "field_sections", + "field_sections.field_media.field_media_image", + "field_sections.field_items", + "field_sections.field_reusable_paragraph.paragraphs.field_items", + ]) + .addFields("node--landing_page", [ + "title", + "field_sections", + "path", + "status", + ]) + } + + if (resourceType === "node--article") { + apiParams.addInclude(["field_image", "uid"]) + apiParams.addFields(resourceType, [ + "title", + "body", + "uid", + "created", + "field_image", + "status", + "metatag", + ]) + } + + return apiParams.getQueryObject() +} diff --git a/lib/utils/absolute-url.ts b/lib/utils/absolute-url.ts new file mode 100644 index 0000000..8974f14 --- /dev/null +++ b/lib/utils/absolute-url.ts @@ -0,0 +1,3 @@ +export function absoluteURL(uri: string) { + return `${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}${uri}` +} diff --git a/lib/utils/format-date.ts b/lib/utils/format-date.ts new file mode 100644 index 0000000..fc6bdfa --- /dev/null +++ b/lib/utils/format-date.ts @@ -0,0 +1,8 @@ +export function formatDate(input: string): string { + const date = new Date(input) + return date.toLocaleDateString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + }) +} diff --git a/lib/utils/is-relative.ts b/lib/utils/is-relative.ts new file mode 100644 index 0000000..8ec6e5d --- /dev/null +++ b/lib/utils/is-relative.ts @@ -0,0 +1,3 @@ +export function isRelative(url: string) { + return !new RegExp("^(?:[a-z]+:)?//", "i").test(url) +} diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..600c711 --- /dev/null +++ b/next.config.js @@ -0,0 +1,28 @@ +module.exports = { + swcMinify: true, + i18n: { + locales: ["en", "es"], + defaultLocale: "en", + }, + images: { + domains: [process.env.NEXT_IMAGE_DOMAIN], + }, + async rewrites() { + return [ + { + source: "/blog", + destination: "/blog/page/0", + }, + { + source: "/es", + destination: "/es/home", + locale: false, + }, + { + source: "/en/principal", + destination: "/", + locale: false, + }, + ] + }, +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1d771b6 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "example-marketing", + "version": "1.5.0", + "private": true, + "license": "MIT", + "scripts": { + "dev": "next dev -p 3001", + "build": "next build", + "preview": "next build && next start -p 3001", + "lint": "next lint", + "cy:open": "cypress open", + "cy:run": "cypress run", + "test:e2e": "start-server-and-test 'yarn preview' http://localhost:3001 cy:open", + "test:e2e:ci": "start-server-and-test 'yarn preview' http://localhost:3001 cy:run" + }, + "dependencies": { + "@tailwindcss/forms": "^0.4.0", + "@tailwindcss/typography": "^0.5.1", + "@tanstack/react-query": "^4.0.10", + "classnames": "^2.3.1", + "drupal-jsonapi-params": "^1.2.2", + "html-react-parser": "^1.2.7", + "next": "^12.2.3", + "next-drupal": "^1.6.0", + "nprogress": "^0.2.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-hook-form": "^7.8.6" + }, + "devDependencies": { + "@babel/core": "^7.12.9", + "@types/node": "^17.0.21", + "@types/react": "^17.0.0", + "autoprefixer": "^10.4.2", + "eslint-config-next": "^12.0.10", + "postcss": "^8.4.5", + "tailwindcss": "^3.0.15", + "typescript": "^4.5.5" + } +} diff --git a/pages/[[...slug]].tsx b/pages/[[...slug]].tsx new file mode 100644 index 0000000..38e7d37 --- /dev/null +++ b/pages/[[...slug]].tsx @@ -0,0 +1,112 @@ +import * as React from "react" +import { + GetStaticPathsContext, + GetStaticPathsResult, + GetStaticPropsContext, + GetStaticPropsResult, +} from "next" +import Head from "next/head" +import { useRouter } from "next/router" +import { DrupalNode } from "next-drupal" + +import { drupal } from "lib/drupal" +import { getMenus } from "lib/get-menus" +import { absoluteURL } from "lib/utils/absolute-url" +import { getParams } from "lib/get-params" +import { Node } from "components/node" +import { Layout, LayoutProps } from "components/layout" +import { Meta } from "components/meta" + +const RESOURCE_TYPES = ["node--page", "node--landing_page", "node--article"] + +interface NodePageProps extends LayoutProps { + node: DrupalNode +} + +export default function NodePage({ node, menus }: NodePageProps) { + const router = useRouter() + + return ( + + + + {node.content_translations?.map((translation, index) => + translation.langcode !== router.locale ? ( + + ) : null + )} + + + + ) +} + +export async function getStaticPaths( + context: GetStaticPathsContext +): Promise { + return { + paths: await drupal.getStaticPathsFromContext(RESOURCE_TYPES, context, { + params: { + filter: { + "field_site.meta.drupal_internal__target_id": + process.env.DRUPAL_SITE_ID, + }, + }, + }), + fallback: "blocking", + } +} + +export async function getStaticProps( + context: GetStaticPropsContext +): Promise> { + const path = await drupal.translatePathFromContext(context) + + if (!path || !RESOURCE_TYPES.includes(path.jsonapi.resourceName)) { + return { + notFound: true, + } + } + + const type = path.jsonapi.resourceName + + const node = await drupal.getResourceFromContext(path, context, { + params: getParams(type), + }) + + if (!node || (!context.preview && node?.status === false)) { + return { + notFound: true, + } + } + + // Load initial view data. + if (type === "node--landing_page") { + for (const section of node.field_sections) { + if (section.type === "paragraph--view" && section.field_view) { + const view = await drupal.getView(section.field_view, { + params: { + include: "field_location,field_images.field_media_image", + }, + }) + + section.field_view = { + name: section.field_view, + ...view, + } + } + } + } + + return { + props: { + node, + menus: await getMenus(context), + }, + } +} diff --git a/pages/_app.tsx b/pages/_app.tsx new file mode 100644 index 0000000..c4b28f3 --- /dev/null +++ b/pages/_app.tsx @@ -0,0 +1,31 @@ +import * as React from "react" +import Router from "next/router" +import { QueryClient, QueryClientProvider, Hydrate } from "@tanstack/react-query" +import NProgress from "nprogress" +import { syncDrupalPreviewRoutes } from "next-drupal" +import "nprogress/nprogress.css" + +import "styles/globals.css" + +NProgress.configure({ showSpinner: false }) + +Router.events.on("routeChangeStart", function (path) { + syncDrupalPreviewRoutes(path) + NProgress.start() +}) +Router.events.on("routeChangeComplete", () => NProgress.done()) +Router.events.on("routeChangeError", () => NProgress.done()) + +export default function App({ Component, pageProps }) { + const queryClientRef = React.useRef() + if (!queryClientRef.current) { + queryClientRef.current = new QueryClient() + } + return ( + + + + + + ) +} diff --git a/pages/_document.tsx b/pages/_document.tsx new file mode 100644 index 0000000..1b6ab3e --- /dev/null +++ b/pages/_document.tsx @@ -0,0 +1,17 @@ +import NextDocument, { Html, Main, NextScript, Head } from "next/document" + +export default class Document extends NextDocument { + render() { + return ( + + + + + +
    + + + + ) + } +} diff --git a/pages/api/exit-preview.ts b/pages/api/exit-preview.ts new file mode 100644 index 0000000..e08051e --- /dev/null +++ b/pages/api/exit-preview.ts @@ -0,0 +1,8 @@ +import { NextApiResponse } from "next" + +export default async function exit(_, response: NextApiResponse) { + response.clearPreviewData() + + response.writeHead(307, { Location: "/" }) + response.end() +} diff --git a/pages/api/preview.ts b/pages/api/preview.ts new file mode 100644 index 0000000..6100e38 --- /dev/null +++ b/pages/api/preview.ts @@ -0,0 +1,5 @@ +import { drupal } from "lib/drupal" + +export default async function (request, response) { + return drupal.preview(request, response) +} diff --git a/pages/api/revalidate.ts b/pages/api/revalidate.ts new file mode 100644 index 0000000..3e7d8da --- /dev/null +++ b/pages/api/revalidate.ts @@ -0,0 +1,34 @@ +import { NextApiRequest, NextApiResponse } from "next" + +export default async function handler( + request: NextApiRequest, + response: NextApiResponse +) { + let slug = request.query.slug as string + const secret = request.query.secret as string + + // Validate secret. + if (secret !== process.env.DRUPAL_PREVIEW_SECRET) { + return response.status(401).json({ message: "Invalid secret." }) + } + + // Validate slug. + if (!slug) { + return response.status(400).json({ message: "Invalid slug." }) + } + + // Fix for home slug. + if (slug === process.env.DRUPAL_FRONT_PAGE) { + slug = "/" + } + + try { + await response.revalidate(slug) + + return response.json({}) + } catch (error) { + return response.status(404).json({ + message: error.message, + }) + } +} diff --git a/pages/blog/page/[page].tsx b/pages/blog/page/[page].tsx new file mode 100644 index 0000000..292e571 --- /dev/null +++ b/pages/blog/page/[page].tsx @@ -0,0 +1,126 @@ +import * as React from "react" +import { GetStaticPathsResult, GetStaticPropsResult } from "next" +import { useRouter } from "next/router" +import { DrupalNode, JsonApiResponse } from "next-drupal" +import { DrupalJsonApiParams } from "drupal-jsonapi-params" + +import { drupal } from "lib/drupal" +import { getMenus } from "lib/get-menus" +import { Layout, LayoutProps } from "components/layout" +import { Pager, PagerProps } from "components/pager" +import { Node } from "components/node" +import { Meta } from "components/meta" + +export const NUMBER_OF_POSTS_PER_PAGE = 2 + +export interface BlogPageProps extends LayoutProps { + page: Pick + nodes: DrupalNode[] +} + +export default function BlogPage({ nodes, menus, page }: BlogPageProps) { + const { locale } = useRouter() + const title = locale === "en" ? "Latest Articles." : "Últimas Publicaciones." + + return ( + + +
    +

    + {title} +

    + {nodes.length ? ( +
    + {nodes.map((article) => ( + + ))} +
    + ) : ( +

    No posts found

    + )} + {page ? ( + (page === 0 ? `/blog` : `/blog/page/${page}`)} + className="py-8 mt-8" + /> + ) : null} +
    +
    + ) +} + +export async function getStaticPaths(): Promise { + // Use SSG for the first pages, then fallback to SSR for other pages. + const paths = Array(5) + .fill(0) + .map((_, page) => ({ + params: { + page: `${page + 1}`, + }, + })) + + return { + paths, + fallback: "blocking", + } +} + +export async function getStaticProps( + context +): Promise> { + const current = parseInt(context.params.page) + + const params = new DrupalJsonApiParams() + .addFilter( + "field_site.meta.drupal_internal__target_id", + process.env.DRUPAL_SITE_ID + ) + .addInclude(["uid", "field_image"]) + .addFields("node--article", [ + "title", + "path", + "body", + "uid", + "created", + "field_image", + ]) + .addFields("user--user", ["field_name"]) + .addFilter("status", "1") + .addSort("created", "DESC") + + const result = await drupal.getResourceCollectionFromContext( + "node--article", + context, + { + deserialize: false, + params: { + ...params.getQueryObject(), + page: { + limit: NUMBER_OF_POSTS_PER_PAGE, + offset: context.params.page ? NUMBER_OF_POSTS_PER_PAGE * current : 0, + }, + }, + } + ) + + if (!result.data?.length) { + return { + notFound: true, + } + } + + const nodes = drupal.deserialize(result) as DrupalNode[] + + return { + props: { + nodes, + page: { + current, + total: Math.ceil(result.meta.count / NUMBER_OF_POSTS_PER_PAGE), + }, + menus: await getMenus(context), + }, + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ea2f437d9db6552726693be6cc2943a32dc5a964 GIT binary patch literal 15086 zcmdU$X^c%<7ROI1lv$Z)$~+GtJY$|F!80Yq2f; >NSTKFL46_E}iL5mO)AEZS| zhz??kp>#q_Pa+X9S9!)q#7v>f>h;_1zJ2P{J?EY~6zP?$I_K=Y*ZTkWKIiUX?Ol$O z;bc0!dO3Q{aq`JLhPRsE;xZ>o<~Kd63*DEFk1M;XdugYEZ_Oa-Nh{-%0D^{MKAR3)TR78`8wfiHZP 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 literal 0 HcmV?d00001 diff --git a/public/meta.jpg b/public/meta.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ab6299eeec96e604cdbce3494a5bd54069c1f7e4 GIT binary patch literal 66619 zcmeFZ2UJwc(+7BGhMcoxhCCoS&k!7P#sNkNg5(^OC@APi&XO}YWRM_9B^XGOb4E}R zNhKU<>lst!}*0}MfnA#5OBEI z32`ZTq>_>npQxIaDoRUMQ3-i82!xD`jEaJajhdPbDF7Eh{_FC!3!o(-NF@Pa5Ly6A z3xUx>zV-m@M{E)vvHPe3e7XrT!_=UhP1b!j#3xQt<{6gUWTL=)B2zd3P?@09CHpl&UoDrA2 z@hbN$N|1d{g8A?I{e`AXZ;|$`!Hvjq46xTR$NwY~Pv`3}$uiP$ZAyKmR(&P(1MteyRle^3Rw z0)(;9Xt9+2qc+$CH`jS`lM{$PI{Z_?52vKz;t=LIFmXp8E&98l*TKHG%$tn$)0 zjkORDcXK)TsFc@O0~B`7WI29sAnGJeCtHZqNHD{Bs_yQZRcHvthD|6{g@~f#x&-D^ z!EG!|52^8o=wJZ@fpwu~l{jPV!JeJc) zL(oJAp{jv6*R$@`hbQ+%!qbQ_c%V`a8o;Nv42=S!Nbm;*Py<3e9EJgzpNJEL`T<45 z5$CZi>J^kdl>~6r=+kVKL?mktm4@lGy22TRRDL{o?8~M=;EuxGm*MAudWFsFq?nJ; zo=6?rZL&NT#Z=NgkY#@E)bURo{~$B~6q42ez7~}LS;1-VW!BE3G}29IB+2}GYUILH z`3_lFr0VKKlAS18xgg&W2YeT|Xw=aJGUL1r+2IW{0JU_e!WbZFSzp2B4|IT{x6z*` zPGuFaFC*dGHtHJ(AnL5XZ!6OQM)~ftp%pxUDPm)PbTXm~02q+je@F>2bwX|Kk+HV+ z_npoIrJN=esZtZ>wje{)w5C31c59KNZqtP+9qNaX{6K^BaiTbP}~= zQs^K~&Cx2L^F1NSK5mG|NkDyEGzl$%+tn$5WVj4fy)3tYfZBG!(Qw|^?oAc1HzrgWb@?yq`FNa?!PMCBO``dAW*STaUII%i=@inUbSM~)~LKy*|^qGQvVNrFyu?2*xbzHn|sG55~U%vDIK;#f32@%!i0&t@fQ%*fkNPQJUU9`o( z{rozPXfFT_dJqb_&IwxviaWMlQK1!RR=~4y z^RpReN(Wg`&221M7_kEmR*v>bjZyUz|}l zyKhM6*4A@Ah?E=?} z#Hj%HyMfIIQfLO*cl|0A4lFowhieI{>iB`6N`&7F8je(Fv?MWWAsWah9fn3r({^qS z&>1Tpi$o(!6Gm(0F`$r<^#t|)#u!Y3k+)J#$<3kU-vtNO)sO_q{+$@i%rFwpNJO#5 zOF?x{4nq;#>*AFGOePs0spX%FV3-HMp6L!iD^BIL5Vk&C!^@f8#1U9&2%hHbRFgw~ zZjf&(jjzXC?Z*P=T(AI%Ep9OH?X)(YS|GwU78v2geY03cPzb*Q1Gfr}Y%3gQCQ)M1 zuw@rO`adRZfkun4!d0SeSysv@Bu4~loRhA}?ck?JbrEC*H2VCi>Z))D7ZkijnB4E$ zu+V~lBaFtUYW2I1GQUp0aQPyks=6T(d%$7&O|8joNeN!k;fvH&B^+_igSg+l6p=_2 zL^=meM$6KYXG@gr8>{Y*D*cFa7?u7X?6RVx;-Zkof{>&AWVBd*n~Y0(*%~%3GO8hA z3%i8E1L_1sdwP$2sRF?nw>01;x4@rUh#nq4KrL35Fi zn!n+L3Z4PE88ZlY3Z6NJV*o0F7^)CNpm7e+!|omo90iErYCxiqn7P4%9|Dp;Z!iEu z7jP=ZAG;0)iifTHx+&5D#Nh@aiJL9LkZTIVuN(2K#)I%bttVI|*fz9VdBpt;{cPR9=dlv^)x5DFS*VFQiN*h)f5|8de7*s714tA3v< zCyqERiK2Cea2zP|RwX#Z!mT#t1Nq0eBI1Obs=xq?#pVo@a(cRnwh?-p;JFve;5#(wre2sY600oVtAcp`Jz#On% zZTpW4N0!zq3s4!z(&0Fhs*h};moOVtSv0{}zw=TAt9p_1z>XUqom(V8Xt@0nLq;`63uhBerkjFIr5C2L(_id
    AOQ96f4e|twN@amKEdAq+XMe)JgP={8gljcq4R%f{olR_NNK@YvY$PJ z`yUTn3E=<7eSdUhMD;9zTcd<1?nlf2*?&eN^R&)m%#Gh~Ak_P({=#J7q4b{!PGj}N zX%wIa{SOPHl%OR`hpmx;WXHEVh(aD9u-oosf4^PWWoR5y!?LnjWD->w{CDcVy-piV z2iUu`(ckVP)=PE6IE+fD*#2!8Sor`Pw(iq8xxNYU^l|&Yt8F~y4yqLls< zI;9U8fFGRX3CZSFy_XH+;q~hP22&^Q=aIgbB+Aw3Ee-~%F6tBDJR=93J)FZdofQBZ zdRo4ma57B|4gZF}EX?3_dqchwS8;t_EU%QT3Yt+$op z#J^jy8_|fTl=*!D{)H?AE&d9?2Azn2)CHAg08^EO?7v10|6K@)0uAd7U`LIB9aF_! z!0Z@@gS_KzBvxXSYbrO`2BQkT^(Gx6%u97QVTc;9ROz2V-e>79OT5bn;AQ-B6O2uR zP}o5nK-?cmqEysWAm`EnFd#HGRJeF>|AN_XUFn{UFT?T;Sf)3MK_jjdHPVx=_0qYEp#X=lZkg(8)ms&(2@HfPndKFz*yvZpB17d|*^Nyi_kN;=D!_rjVim z<4$~n<1CgzCX7X8b=5OcHAZ3cTPpcq385dYj-&G7Q_}+zNBypX{h7o2M2(v0Zwf-- z@0a3zlAsFS=t<}T1J2ygd~; zdYjHOkX4Xw`~95TMN|>o&}(|eOfn2=6BE$KAjPS zFBUeIVA`_ww!&mkJOC+WuhA!%ckVR7RRVDoDLgpLnu8XhQ9S?opGc1gp2UHDi!jv0 ztOa7;WoQI|aNSU1hLy%j>;eE@R=SW<1OIRN{NX+1)I#OE7cc;VqQ$nx*$_YVV0EhQB$_6e>?qpZhn79NlV{w>R@4A`P2Yjy3^Bv{M4Cn29|8fzg+Lc3Ui`s;f2 zTF!ZYp+=m?Djv&#+>OzrW0`u0<_R}Ku~#DOlk8<1odjXyKY4#HU6(LVRPI~lO6r+` z79GJXbttjV+o?`ORDy29cNy?|Rv8u`(1qVY^P&EAto_|j>(Zp^g+(R1I=g0^tVFo? z@umYuQ3)CQc{|9(z~B$S#HbmnerYg#pe!5aEoYA}B23HKgkiaPg$;W#l>``77u#dD z48+5G)aai&5+9pT5Qnp+L=5FkY(bS|=<#G>Q9!~q#~LRIwjSgIysvWhU}120Seny+bBn1OAS=Z`Cahj7qP(zCsI$t!m+5_AEmKJ6e58OhlXh z&r$#`DS-dE6A(w`uhOSR*RTm&N3TE9UH*ZyY5cX=WE6X<9uA^zfgGReOh>^}^l*S^ zj6vjBPM|+Tm=gzZ@$kTXm+=3RXF%9nBMIPD#|VceZay7;8x&%|Qk?S49RrYRJVFy7 z%o)W?raiO9!gdz%b-nm4Twy8B*G+dH@H2?k|V(KCpC4yJk z=)r^U4S4v;gn>Q0Zu#0@OSkQiGa2s&H{gFN$1>&wm3nb!r(gQLHQx(XDvW~`S4>jI z^F)_;%Bm0SDi!KMw>HT(nx1pU6+$){l^FO~7Yzv(6T{$A_}yTo@P*|)vO+ROWd^0Z zs_Q)Xv)FCF{cAwg);5N7kW&we|Huh39Z+9bt=ifNQig2?Auzkbrm|CpC*s?F_SOaFWU!fw{NW~vN>!Vi z>V;$QkDMp@A%3P40LeDBc50bHL8xI*QzAjt%QAXWR`@^33INA4#tH2SRZgMUKkG|2 z9y>+R>pzh4LLO`BPm51Z4-!5(1N00!#;=fwKt$-uGz2Q>WzR5^#8BZxN0`US=qMfg z>**7;2-0m%-#nIq*ShZyfzp35&x$Id^^wfUy~pi3$g(3$_-r!c2Fregfv#dE@%YtP znkUq)G^_*a6Vf?STj8JkEbot$3AjlfDz z_V&X8>f8kdv!xbzJT; zU4CI&K#thV-Ix+K&s#`0wD4O!1>I|7Uw`f{-w2ly94@4TSv=$^n;Y}FhVXnZz^B$O zc`Nr>kekNcl)L8wA8IW*r46q1?@4LQV7Mn-P#RU1&&oNdF<5gB9zVMYK}8NJKV*IC zDYszHQ*?RHT4{pf3XZubi;SnF4!Kbd#Z;{nE96cUW_3<%$aioY!$>;&+l_9hTEK=v z`R*AvzIJpxCpbQuQsZ$8HGM9#5L@f7JKAt>HYC=;ueLdQC33{S%umHeZ_}Z0V0mN8 zPfo8(OK00cVA}O~wZ=0`4OiShgLxMF!Rjqt=dPS%%;^h;LRt)oJf|AVvM-My$_;U8ch9$&eAK^(h+cwOLq;mNUNXO_tckrfuq=SAzFUF!SP`PN=`MP8y=2wjZB$#6eG3#k$~Lpw z?AldMHY8ti&{e^i99}^saW%mc`a4yDw-ho>zr%>;pu|A7Q3_#6aZPFwcCXn6Zo&-s zn#0!`t1dTxZr(m#JnA27)F2SaawC%jg|M3bWc5C^O=+Vs2)X;1GOE5IRf$P!H@WM% zy=4u-e*}W{Yjqr5{JWp0TYS#PhSj4(U6itTk?$p5cnqPUpYe!nC-^94~iRl}> zCeo`|@~$A{)_kl)M`5)ZN)9=kO7i4(w^NK`QJSDHsXRTi+vxb?QcdqLtjYd|Qi7LTLW8fdt zxQVbe9>!b@VjefB{KR!-aHDzfu#*x3Gy|OEh$HtHrdm9?%JK6r$zm zv~CjJbv53RFEJL0kINY1nNCWIk%8Iq^mnr@+&!J^@SMgbw^F5dU%OmmArYN+Du%8UPtSFkuEO}iP18I*&1WLC4b3TUS5snE zQ*<^{bXM=mP3hDev}9MyBO;uNOm0n-LLEvr^%(PbrL7Z+gmP=GQ<@>%xsZ2!zDQ|V z1Q(BRJnVcG!CZeYLXXJ#7R)k1foR^V$G}_QD_~i~@sNzm@@c}t2Oi(yCCb$)a<%?e zrR&U{bavNkWjeG-{uwR+AY+7r8PZDNNvxnz$Y6#s6bF#a=xKu85paMJgQ)@?O}Y@Y zIPwT^ET$A7_%(pta5*+BiE0AQ-v8(7m_25j%iT^M6h>>?Qe?OiQ z>UY~ql#Ae%b4(e6{&d^_^(x^>&7-qDDDNKeQRwlbFcO}GGm4#zefOgQ6cS&wS%m&} z4Fykg02X}o{NEO6vk*Wwd6aboAO+Rty}&nc-55Z8A(nVyIHvLjmxGh6d76P;F=Hv_ zC}}-{-PP0z2Qa7f(2Rs+d68~!(ZG!uJb1Jqn;8&E-omgI=tn5HJL!@9aSE~2gb1;f zg&4R|XBw-0V+;>2Y)WFFY(4mj+krfmz?-OufzjmeA=jUICp3&0o5efCp+{yY@zV}W z*hTlP$>QyM63!jw*na!|8zML&y*seeDsOR8j4ipHkC|o}Qdfy3yWQ3j*~ezPYUHu+8oj?-qkGS3jF4L6LHTJO9v4X; zj=a|)@i31VhL@~yuWTh*T;Gptu(0BQwBvTNO_!1`qb?V}9M){s)KWzH0f1_n3XGtkCQ(3Ip(}!tGJ1|cTCK%YRJ`kvD4cAo8?lO{bovALW zzFtzIqfzu;HoQ2=Uwkx*TV2IEWkC*0oL*2aCf@~@q!pRHtE)j}sjclcD)9p{a^4au zKZHLky7~nBZ!B~kl~gFX)=>n85hc(cd|7*GHYvK!FTU7cXH;&{o_Um>DgP@#LiZ-O z(EMXszecNIDx6Oyat-hzh_y(&PP!URp(~O*z(38wRi}Y26bR<=D(gi8LISjkMs9OpXKzUdSJ1ggR zJt-DBDi%9_bu_Y8O`89)-)9WnYj^O(^4ep)L))1>h2$*2nN(O=lgR5)b$*7jeq&Nk zKD&$Ul%MJZF{%UVQBxG37*}j+h!WXA>uXIh56)8sqLPymV(W5g6?2hrTFC-g#57r+ zp}upaHvw=`TUq11xf&t~*M!Y-;~D&v$Dk4UpU?tdvq{A5SY%K2i^uz|Iwl0G%bICD z;42RWg}^Taej)G+fnNyxzli`L`v2kCuYf=Q zhoS-V?l34J6h;CAAOtY*{Sp}X+kgO+m`09-_844NMxIGhnvR}CsoS=!nt9$OQ6*M-ZQ zspeJVM`r#j;k0p|Zcz-pC6OUj^e45<+>87;@u!Hi;PvOjC=6XB9hUE<~7i@3TAQJt5~XFtKE{KdnIEs}^MO-yq_ z=#;(l+2Y!=IYJw zfMO?o!Y=iX4?}ed=Vvc8c^`n>Gj_j12-v<^k-YL$=~efIvE3PdZaWrzhB&eof)knQ zuROoBt#e%c%<({hXzYtOUkK;1y(z1#hAWGGB&xlh5LNuFc1DC2>hhUP_JEuyBO!{r z=i1-LeQYpnsCnglZR{Z5GMTpHy-7gq)Sxu|!5dxQ-I}<}{+U4z*=Ow=AEOqX_SR!) zq>dk+qONV)dzBqXylrO~>}xa|UhDhvl~r5YoEUz@_ht8#yoDP_+He)WLuNpowY5i< zndREdY-@mi#Wktbd7DLR-?I<#WbR2yN@P}wl1kLwO;C}5M5SixV7?~-uj_>rGw7qr z1twbT9c+4$cp=&SZO)xgzt5I?pCWZ) zl{x#92;0Ln_QA6?ij)Rq4SOmtI|6a+4{uBCz0mbx@6@4U%%0pjBd)2Rn{~%}=snFX zvGezcB(lV+C7OF&KcA4`W4CJ*cyzZtp2Kp!z&f79DzjL3SeJFyz@kDsA!;CEES#@- zWvQY5OOW@$OIgLiBD%}3*3ykG=^I+ykZL|-jcKEUDql0Kdi-cjq_ACLM9XfI?ccW!N%f7c3) z_w%vk_j?t`Ji7+funY*(b0j8n2CwH8Ky8CS?th>SbBbB}dEBKq_LU z6bDQV`1rz_T}m*@5uvX0K;Py5z@0kPlK@w`t>H%h@nkc2ovM|(_XxCpv}2VKY%pn8 z-)@ob4o!Z&rs`_8KxXsXiK8xIt%NRLuHU45Aoi{9OE#+b6#$vsL0cZ(b!wTt>_wuP zVz2oGLja8>6Ank|BlHp{Od0>U^chp{#O4eBbc@qFb*Bb1xD9GrFS9uZl3E#0inMQi z*s07%KEmGMk7i-1;vt9`TB{IdL#uvd>iTFfMPBC#BWr1Y74_xbo*GW^a=#h8=KZ}} zpvf&1emJb5eq%r`d3QNB&6e7QW{b(UM)CEBZ_HLGstK{1A%dg39u@ZoyD@&q7bOFw zLwJ#`umyTV%MpL;mGy}0s^5-6mc8#0QRp*lN0GL$b&AuT&rp+2*Ku*M)wD$VQe*HT zE5mKpQ)@OgDy*-LThR6xc;a$c{crnDWsm?|mFo2#8O{h*@vRS!2)XB>XrkrF{y<&OK_iuiu^h=$ z$aed~rpvwGZc;u~8UFC#{q2U`i2c><5>c62qDK7_8|OcE7e4hsK4M0RQM~P-vV5q# zxwXP|gdS1#7xL>VJ?->nUjgEXXZXy~39=FMT}s~MNZ%SSGE?zjM^~-Er$cvxrw>V< zilKMr%W?+(5g4=b$$~|;REEf7=c7?W!|;bvKn*t;QPlmRy4&ORUBBVyGF;5vgwNdK zeWi7K<5FKt$E7jjI_?k7Ee^KsHz$`Lkbflqvbi<(>BLz!73-X#E8CBEWm%nEji$Vt z5I8-4{zb3Mm$DxolI$7sBGxFpb&|ZT7ijHNbPl*&w0h<~T0CJomeL*d^b5s@d+)z% z)mOkNOz!bDvqKm1X)p3xe?3J0`Hee|+POtXTIuX3`DcZG({`cx)MSOMm$lin`kcf+ zpPPRwnn9Jn0-=~B+#NUI(~ziVni0J5ymG)Ndpyvpl_ShJla)2mWq%|Z{&OcfN%`bj zlgOp;MR1Foc6->#Yp-qam|gFZmS>WvGEdK|m^)3e1dtERufq=b8jz=Hw8AaK`2=Fk z4-``F326$1Xavd(6|Ynhn-N5aX-YZWKfyEE$>yz{!?!u<;EDeV@MiBh9mMBr5}$GM zzwDA;s7-w#ac4xUCHishv&5@69_URZ7p`1b$m7TM*1g|)G?2x*z{$RF96!?7X6vK8 z2;ci=?$^bJ*B~=!Oo2!A4dcdyVpvYA?E+Kb!!>?n{bF1{^wR`w0-dih- z`HT`P>)XD0d(IT8Efv!2SuFl)fgjj?JS`l;y_@_4#bSSYV8N*0zWLwRU5Ag{c?X*e zI`!MX0;mmhrqiWawp%MqqdrvZX8`R}^0AKk4@YJV_X(#-m?m83G94*{-=tU*wZwWt z`3kbNmkLMuLQdx^zDfKO_ODk5KQ<_Xp@j?eDE?D>(}}GwL7oSJE}~z7?hE5v*9ZG! zCHhyreHq_hV}q>N+4|l&3(pigy^Ua+TYKfPeY`q4BOZ;Llerb1KyRW?Zy0gMa*ko< zVmuvR*1f{70QZKCdrRmQ#^KI|9%4RX)r-dAApY9O(*`d#rH zyRmca(nMy(N#6qvbZF3t%<*g za6IsiY@+bT=;Qc)leU)(b#0Ny%Y=~5AH<$cHQ~X(=RP{h|<1-)OWZ86;Kh+L}wUU zpA1TS)m^v#Up!+ZPfAtCGv!c1Ss{ix(GAi~SzkEAojKv8&QkH+p{s5J^l|jM4gNZD z>hyMK{tTzgy%YNyD!0cL>+I&adbS^Lev;~IQEstWG&K7tcQ9)) z7#1ucnqiT3^tS8gm(=a;f7vLghG!ngJbM~&Slq6* z+ZXC3`Q|;<@W|`+mNQqtRcKc$YCmHUIQ!tk-uB~nTVt2L0u9{qLZ{f&C=*yI6{;Jc zuyZX_N1m{jQ`f35_e)T~(&K#fHzViQJ7%sFecMJo#1@IZc#(XZ7EO}dH6z;D^=YmxtfLAasK4W9b!x88Q}$02Jhb++uWM~E zZtaZQNu+7}!Xp^wrh9>K?NwST**y9cE+zI|(kRauIkPa^ty@`M9$$eg=YDLU^$8+# zE{R%AmpX2%Ra0FGz426M?bZ8dbAEw7beM>|lV2LRc?^h89Y&7zuUN-ccln&2Truns zpQwq?P`HySl>Bl%pek=0YruInyuq@kf5;|n1tGMd2p2A3h@oXtNF-y#3x zcH=9c@p{d_xBWv?jB-xw_0BLmr_O*jYYS7h1)+vXk^N~Ouj=9ZlyQYzjJLVDZ!v{i zY277!`RRwcN~CUUD{ip`N2Jkf+fefoG+Bf=Bc_<)ii}78%w}JTD~N?*#@&O7|5Dea z)G-zN=yd@Bzpntr)|28HgzTD0KRtVS8`ap}O3+%EwJL4aSvOia5d8Bhyr(HMsMV+1cZ-U*bwPNEEsCt6IdW9&vBtRf z$0}9okKDp;(%A=AoOfxB)Ei%^eR6EcMC}JNb7u}c_c@(C!4dy^$k^~Dmi&{$I*BE5 zgKgwWNI)b(@EtIsJF6hNoxsKkp%xGvzDLMDi(Pp(iVDqM&2FLW@}H zEUV(UQkm7pjhhCq*N)S*!V)NCIRr>l2W8-LHc_SMPc&5Te{-X0C;lE~f5Vup(@(<9 z#x>e$mT*}#hCpAqE4)c*Xv^1^vtJ}(zx|Vbn2QE$QhxLu)bLjTx`a{>yxsV^_R4Rs zk5UAwu~6nkt67sa^S9;T=5gwN%Fx`){uK}XCh{C{O6AwT=^ktS5x&UM0ssvS{%MHQ z&^u$D(QR@Y6j`^2XwK~XhAoYV-C5UQp?$vi`!3?j7$JBXewP4A?D+(fDLjN@DqOpK zXSMu|73Qsy}jFW$hFT83QFre7msG_RQps{*SKXqdA z&rbBjRGy=jYi;Vmne?(DE9C3F>QLUSo72aMMr%}657lo6APgBCJIJZd{s>GAt)tUU zbLWJ2p+nCp*IeDb>So7U?! zncl^BpfP82TfNA5afx;QteaY8)SQe_gx}WvPx_7e=?)2UhC9+d^_SG@vszIxF~oG7 z;+PgMcE7BLl!;=M)@i^TYU7aqfICjw@&4)OB0kT$C*7;36oXOl#eR7XXDC60zUfUv z)e6a))$HO5*(52o&76i-KaKRHi+)w?wZ#fr+5^X(qMjvwS;)We6`*{IR+QHIyj4H6 zb)PglOD5r>V;^@DhJ$NuIDyHiNvdqwr=0!Vc}lg7-)PlKSd36#G;`cCJw^+!FES70 zJKwxxKjTtoS|jh_A@*qQ@bC%SrecTNrB`RpE3G5i1N53;ulg$$x<@w_q63Mlm5*oD zZHaxVeZamB(J(!8L(uV=Rc|W}`+#3ct0qftsmz;Q{-dbbML}_!*g%20L%wK!FY0=O z#`!(XJCS>leibf`1}~(j96#+ONxeH)EygC+yAVW|`e-#}L?!>lix>A�&=o`d>wh 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 Z0002M;Q!hH2mu2D0Y3o0{{R>d|Jl-pRdfIV literal 0 HcmV?d00001 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..14267e9 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / \ No newline at end of file diff --git a/styles/globals.css b/styles/globals.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/styles/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..8752b4d --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,14 @@ +module.exports = { + mode: "jit", + content: [ + "./pages/**/*.{js,ts,jsx,tsx}", + "./components/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + variants: { + extend: {}, + }, + plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")], +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..740eff3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "baseUrl": "./", + "paths": { + "@/components/*": ["src/components/*"], + "@/nodes/*": ["src/nodes/*"], + "@/paragraphs/*": ["src/paragraphs/*"], + "@/views/*": ["src/views/*"], + "@utils/*": ["src/utils/*"], + "@/config": ["src/config"] + }, + "incremental": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/types/.gitkeep b/types/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/types/drupal.d.ts b/types/drupal.d.ts new file mode 100644 index 0000000..8b9dd2b --- /dev/null +++ b/types/drupal.d.ts @@ -0,0 +1,17 @@ +interface NodeProperty { + id: string + title: string + field_location: { + name: string + drupal_internal__tid: string + } +} + +export interface DrupalMetatag { + tag: string + attributes: { + content: string + name?: string + rel?: string + } +}