├── .dccache ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .editorconfig ├── .env.example ├── .gitignore ├── .gitpod.yml ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── apps ├── .gitkeep ├── backend │ ├── .dockerignore │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc │ ├── .gitignore │ ├── .nvmrc │ ├── Dockerfile │ ├── README.md │ ├── config │ │ ├── admin.js │ │ ├── api.js │ │ ├── database.js │ │ ├── middlewares.js │ │ ├── plugins.js │ │ └── server.js │ ├── favicon.ico │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── robots.txt │ │ └── uploads │ │ │ └── .gitkeep │ └── src │ │ ├── admin │ │ ├── app.js │ │ └── webpack.config.example.js │ │ ├── api │ │ ├── .gitkeep │ │ ├── alert │ │ │ ├── content-types │ │ │ │ └── alert │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── alert.js │ │ │ ├── routes │ │ │ │ └── alert.js │ │ │ └── services │ │ │ │ └── alert.js │ │ ├── article │ │ │ ├── content-types │ │ │ │ └── article │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── article.js │ │ │ ├── routes │ │ │ │ └── article.js │ │ │ └── services │ │ │ │ └── article.js │ │ ├── blog │ │ │ ├── content-types │ │ │ │ └── blog │ │ │ │ │ ├── schema.json │ │ │ │ │ └── schema.json.save │ │ │ ├── controllers │ │ │ │ └── blog.js │ │ │ ├── routes │ │ │ │ └── blog.js │ │ │ └── services │ │ │ │ └── blog.js │ │ ├── category │ │ │ ├── content-types │ │ │ │ └── category │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── category.js │ │ │ ├── routes │ │ │ │ └── category.js │ │ │ └── services │ │ │ │ └── category.js │ │ ├── footer │ │ │ ├── content-types │ │ │ │ └── footer │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── footer.js │ │ │ ├── routes │ │ │ │ └── footer.js │ │ │ └── services │ │ │ │ └── footer.js │ │ ├── global │ │ │ ├── content-types │ │ │ │ └── global │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── global.js │ │ │ ├── routes │ │ │ │ └── global.js │ │ │ └── services │ │ │ │ └── global.js │ │ ├── homepage │ │ │ ├── content-types │ │ │ │ └── homepage │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── homepage.js │ │ │ ├── routes │ │ │ │ └── homepage.js │ │ │ └── services │ │ │ │ └── homepage.js │ │ ├── social-media │ │ │ ├── content-types │ │ │ │ └── social-media │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── social-media.js │ │ │ ├── routes │ │ │ │ └── social-media.js │ │ │ └── services │ │ │ │ └── social-media.js │ │ ├── substitusion │ │ │ ├── content-types │ │ │ │ └── substitusion │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── substitusion.js │ │ │ ├── routes │ │ │ │ └── substitusion.js │ │ │ └── services │ │ │ │ └── substitusion.js │ │ ├── translation │ │ │ ├── content-types │ │ │ │ └── translation │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── translation.js │ │ │ ├── routes │ │ │ │ └── translation.js │ │ │ └── services │ │ │ │ └── translation.js │ │ └── writer │ │ │ ├── content-types │ │ │ └── writer │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ └── writer.js │ │ │ ├── routes │ │ │ └── writer.js │ │ │ └── services │ │ │ └── writer.js │ │ ├── components │ │ ├── articles │ │ │ └── articles-section.json │ │ └── shared │ │ │ ├── meta-social.json │ │ │ └── seo.json │ │ └── extensions │ │ └── .gitkeep └── frontend │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── build-storybook.log │ ├── codegen.yml │ ├── components │ ├── Button.tsx │ ├── Layout.tsx │ ├── Search.tsx │ ├── StrapiImage.tsx │ ├── StrapiImageFuture.tsx │ ├── article │ │ ├── Category.tsx │ │ ├── Content.tsx │ │ ├── Grid.tsx │ │ ├── Index.tsx │ │ ├── ReadMore.tsx │ │ ├── Title.tsx │ │ └── grid.module.css │ ├── entry │ │ ├── Author.tsx │ │ ├── Content.tsx │ │ ├── Details.tsx │ │ └── Title.tsx │ ├── footer │ │ ├── Footer.tsx │ │ ├── PoweredByVercel.tsx │ │ ├── SocialMediaIcon.tsx │ │ └── TemplateAuthors.tsx │ ├── navigation │ │ ├── Alert.tsx │ │ ├── Header.tsx │ │ ├── Navigation.tsx │ │ └── menu │ │ │ ├── DesktopMenu.tsx │ │ │ ├── MobileMenu.tsx │ │ │ ├── desktop │ │ │ ├── ButtonList.tsx │ │ │ ├── MenuLink.tsx │ │ │ ├── NavigationButton.tsx │ │ │ └── popover │ │ │ │ ├── PopoverLinks.tsx │ │ │ │ └── PopoverPanel.tsx │ │ │ ├── mobile │ │ │ ├── ChildLink.tsx │ │ │ ├── DisclosurePanel.tsx │ │ │ ├── ParentLink.tsx │ │ │ ├── ParentLinks.tsx │ │ │ └── PopoverPanel.tsx │ │ │ └── shared │ │ │ └── SeeMore.tsx │ └── utils │ │ ├── NavigationLink.tsx │ │ ├── responsive.tsx │ │ └── symbols │ │ ├── AnimatedExpandButton.tsx │ │ ├── AnimatedMenuButton.tsx │ │ └── MaterialSymbol.tsx │ ├── constants │ └── index.ts │ ├── emotion.d.ts │ ├── features │ ├── 404 │ │ └── queries │ │ │ └── FetchErrorPage.graphql │ ├── entries.blog │ │ └── queries │ │ │ ├── FetchBlogBySlug.graphql │ │ │ ├── FetchBlogEntries.graphql │ │ │ ├── FetchBlogEntriesOfCategory.graphql │ │ │ ├── FetchBlogEntriesSlugs.graphql │ │ │ ├── FetchBlogPage.graphql │ │ │ └── FetchCategoriesSlugs.graphql │ ├── entries.info │ │ ├── fragments │ │ │ └── Info.graphql │ │ └── queries │ │ │ ├── FetchInfo.graphql │ │ │ └── FetchInfoSlugs.graphql │ ├── entries │ │ └── fragments │ │ │ ├── Author.graphql │ │ │ └── NavigationItem.graphql │ ├── homepage │ │ └── queries │ │ │ └── FetchHomepage.graphql │ ├── images │ │ └── fragments │ │ │ └── Image.graphql │ ├── layout │ │ └── queries │ │ │ ├── FetchAlert.graphql │ │ │ ├── FetchFooter.graphql │ │ │ └── FetchNavigation.graphql │ └── seo │ │ ├── components │ │ └── Seo.tsx │ │ ├── fragments │ │ └── Seo.graphql │ │ └── utils.ts │ ├── generated │ └── graphql.tsx │ ├── lib │ ├── apolloClient.ts │ ├── emotion.ts │ └── instantSearch.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── package-lock.json │ ├── package.json │ ├── pages │ ├── 404.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── _error.js │ ├── blog │ │ └── [slug].tsx │ ├── index.tsx │ ├── info │ │ └── [slug].tsx │ └── tag │ │ └── [slug].tsx │ ├── public │ ├── favicon.ico │ └── vercel.svg │ ├── queries │ ├── getGlobal.graphql │ └── getTranslations.graphql │ ├── sentry.client.config.js │ ├── sentry.properties │ ├── sentry.server.config.js │ ├── services │ ├── api.ts │ ├── media.ts │ └── utils.ts │ ├── styles │ ├── Home.module.css │ └── globals.css │ ├── tsconfig.json │ ├── types │ └── graphql.ts │ └── utils │ └── index.ts ├── docker-compose.yml ├── libs └── .gitkeep ├── nx.json ├── package-lock.json ├── package.json ├── tools ├── generators │ └── .gitkeep └── tsconfig.tools.json ├── tsconfig.base.json └── workspace.json /.dccache: -------------------------------------------------------------------------------- 1 | {"/home/konhi/school-website/apps/frontend/next-env.d.ts":[201,1657994671447.691,"9269d492817e359123ac64c8205e5d05dab63d71a3a7a229e68b5d9a0e8150bf"],"/home/konhi/school-website/apps/frontend/next.config.js":[2354,1658009774167.5576,"af667dfb0751081e05578398f77c7b37a1c54747d8db03a3c1a6870c8fba45c8"],"/home/konhi/school-website/apps/frontend/sentry.client.config.js":[774,1657994671457.691,"f69d0841038e18ac390b19da9adec1d5b749c556c1de87cbe377565c3f72af68"],"/home/konhi/school-website/apps/frontend/sentry.server.config.js":[784,1657994671457.691,"c9906186d54702c80bbf9e481b4bfa5dba6da123056b7409f82f8c443d725084"],"/home/konhi/school-website/apps/backend/config/admin.js":[141,1657994671417.691,"14f783d275cd745bf8afe36c411a6357a52d1a113fbe842e8a4d870c99caf1e1"],"/home/konhi/school-website/apps/backend/config/api.js":[99,1657994671417.691,"f372097c38d20f101a3220c38455c39b5701599baa7fbdbb1fce9e539f78da19"],"/home/konhi/school-website/apps/backend/config/database.js":[386,1657994671417.691,"1e5e268a42a682a119e30e6f2fe90a4fbd82f677bc7c50ababfd3e4d3f80819d"],"/home/konhi/school-website/apps/backend/config/middlewares.js":[224,1657994671417.691,"ba74e657785469080d773b90087a0ff1e17c78b680f65139c0f38764c7024e59"],"/home/konhi/school-website/apps/backend/config/plugins.js":[832,1657994671417.691,"0189a68c18c32510192f41e31143fb7a7ed3e4a00548ba340f40465347617591"],"/home/konhi/school-website/apps/backend/config/server.js":[219,1657994671417.691,"ef2bd9ff25d552670b11bc42ce387b3ffc8e481ac9e79fa94cc1136906ad4e9f"],"/home/konhi/school-website/apps/frontend/components/Layout.tsx":[1361,1658011189777.545,"42eb725d1666e596910cbf780e6cc9cb7ff4cdfb63477d6d302ff8c661b9b144"],"/home/konhi/school-website/apps/frontend/components/Search.tsx":[1062,1657994671447.691,"03b2718747f827c24a29f6af844a5056ce71a09c19e917cb683728d8817b7fc7"],"/home/konhi/school-website/apps/frontend/components/StrapiImage.tsx":[1229,1658011232597.5447,"d5b6085934df2d6a9f2ebb340d141c7a5450f8fb41115e926dab5476361acc6b"],"/home/konhi/school-website/apps/frontend/components/StrapiImageFuture.tsx":[958,1658011466727.5425,"aaacdaffbe266627895c173e550dbfbe5d6d8ef567c73344aa1063cd4ff60778"],"/home/konhi/school-website/apps/frontend/generated/graphql.tsx":[92874,1657994671447.691,"5a4bc92ef099c4d3737b6134f7feec840ebbf2f9aae4de82adc8b8aa818cab8e"],"/home/konhi/school-website/apps/frontend/lib/apolloClient.ts":[290,1657994671447.691,"47dcd0eb805833c98f226c73f8b7786ff647dba860ca4b3b02e84f76bac228b5"],"/home/konhi/school-website/apps/frontend/lib/chakraUi.ts":[224,1657994671447.691,"199e3e475531b34f4f7ec4b406da875e352e7fd2175b5492ad0fe644a0b3ff6e"],"/home/konhi/school-website/apps/frontend/lib/instantSearch.ts":[224,1657994671447.691,"d749a453a7fd36247d071081da96386948a0c3125156403fd5b3877711efe9ba"],"/home/konhi/school-website/apps/frontend/pages/_app.tsx":[3060,1658011072537.5461,"79481e7001ceab34f44de60d0fd120377cbdc530a1d40088d65f994d1ede0d0b"],"/home/konhi/school-website/apps/frontend/pages/_document.tsx":[640,1657994671447.691,"d574bedf1419a366a942bd7a8b1aab558b8b2463737ed0d8b75a0a32ff705caa"],"/home/konhi/school-website/apps/frontend/pages/_error.js":[2318,1657994671447.691,"d4fae6a40e7a1be58bc97add1ff77d1e4f56ab5afcf999203170e785bdce4a11"],"/home/konhi/school-website/apps/frontend/pages/index.tsx":[1902,1657994671447.691,"f27d4be275e0ab01d8f436bdc9d82be26fc43152153c5f352fe1eb762d6834c5"],"/home/konhi/school-website/apps/frontend/services/api.ts":[807,1657994671457.691,"3f6623f6d3aadd36fbbec8a799211cadb125918989e7795cadf80f1c45def11e"],"/home/konhi/school-website/apps/frontend/services/media.ts":[438,1657994671457.691,"ba3db1269db0d998d144e2c8475321e16a60921bc1cc8c3d2543241c665c2e0b"],"/home/konhi/school-website/apps/frontend/services/utils.ts":[248,1657994671457.691,"5bc778d50835a47cff84e53c7160bdbd5fd9542c9d4d55af8962b4b665edd1ce"],"/home/konhi/school-website/apps/frontend/types/graphql.ts":[228,1657994671457.691,"9df2241b7eb4e9d5ca177122f7f448b1d4714a1f0cb4e609ca482a0364ef5c33"],"/home/konhi/school-website/apps/backend/src/admin/app.js":[104,1657994671437.691,"120fa024329445d9fdc93c2eb5ffc05cbd84895a3a96122864e534414a80e33b"],"/home/konhi/school-website/apps/backend/src/admin/webpack.config.example.js":[267,1657994671437.691,"080582486fae5d28aefd86cee5a9a01622976ba6d53c94795ca20e4c22f5e1ad"],"/home/konhi/school-website/apps/frontend/components/article/Author.tsx":[1657,1657994671447.691,"e2d7f42946eb8b7c70381a79bd72b1a07d0b1e6b25525a2dddae89fff7b8bb08"],"/home/konhi/school-website/apps/frontend/components/article/Category.tsx":[234,1657994671447.691,"4ae99fbbf3bbeb3310db6bc8e116a8631b7652288626efcb607cd66879596dba"],"/home/konhi/school-website/apps/frontend/components/article/Content.tsx":[1293,1657994671447.691,"9a8fb7044f1e914c3754946a9ef01222659b57b756ace9eec546486fa6169c18"],"/home/konhi/school-website/apps/frontend/components/article/Date.tsx":[343,1657994671447.691,"bd3d60855fa33f822521cce7d51debf1a229277962b5d60c8743b525892dedc1"],"/home/konhi/school-website/apps/frontend/components/article/Details.tsx":[680,1657994671447.691,"a7ddc394a77a9ade43abdf5bfc7d966ba31ac23b0f2f5dfbd106f7356d4441e3"],"/home/konhi/school-website/apps/frontend/components/article/Grid.tsx":[1848,1657994671447.691,"17fab8b0d72934b50186d76aad0eed150ea8901cf27e964b528da51bbf97de34"],"/home/konhi/school-website/apps/frontend/components/article/Index.tsx":[5134,1658010152977.5542,"5e119e05e7d58ca9e5cd0a446c75806b36fdfbaf8cf3025b82f1b28681f56abf"],"/home/konhi/school-website/apps/frontend/components/article/ReadMore.tsx":[572,1657994671447.691,"22854fc3dbb2951ea1836882a1d78f252240b8f1e5807d8a23b2c2156f0c6f6c"],"/home/konhi/school-website/apps/frontend/components/article/Title.tsx":[749,1657994671447.691,"1c98b5de20305fa4e2b830696cb591b2292f6485badf1894ab9370b95a7c98b9"],"/home/konhi/school-website/apps/frontend/components/footer/Footer.tsx":[5219,1657994671447.691,"f8ee30e671ee9c44f14f3a7e32c8e00c327819c5becc57266c321fd86aba1da7"],"/home/konhi/school-website/apps/frontend/components/footer/PoweredByVercel.tsx":[5712,1657994671447.691,"ede10ce2e971d3aaa5589ff3e7e4454fc045d02f43c95b83a1a547ce5d6f3b60"],"/home/konhi/school-website/apps/frontend/components/footer/SocialMediaIcon.tsx":[252,1657994671447.691,"50b47ce2633dd75efcdf791712ed07ebaf50db25cb2c28c82b798afb7cb1c246"],"/home/konhi/school-website/apps/frontend/components/footer/TemplateAuthors.tsx":[609,1657994671447.691,"38d28dbc9ec73cb2304eff4c064563923786f221dac2a1e2df78e60e42dc8767"],"/home/konhi/school-website/apps/frontend/components/navigation/Alert.tsx":[588,1657994671447.691,"4233220e3adad27d8c6e710cc509611623aabc11dd2d7f308dbc61ac16d2df3c"],"/home/konhi/school-website/apps/frontend/components/navigation/Header.tsx":[551,1658011973307.538,"855dabe91f1e1ac592f3210957da19e17f301ff4a4d4c7821ea39fbecc743439"],"/home/konhi/school-website/apps/frontend/components/navigation/Navigation.tsx":[2850,1658011089197.546,"bfd764724a1d5ab0f5f9e9a29a42f341ede47c5138826d824d75798be31bf983"],"/home/konhi/school-website/apps/frontend/pages/blog/[slug].tsx":[2263,1657994671447.691,"02669bf2b4dad664645d4f901e98f8c5d8dffac487b9937496ad22b0592ecb86"],"/home/konhi/school-website/apps/frontend/pages/tag/[slug].tsx":[2493,1657994671457.691,"1193c47a09af3ebc94ec1339eb83d9f1ef8d83ca099df8627656ab01070532a9"],"/home/konhi/school-website/apps/backend/src/api/alert/controllers/alert.js":[175,1657994671437.691,"a0db4d121b7e22b4f3338f905b361b02bd7e01be6a94703ae2742e1e7dc5db77"],"/home/konhi/school-website/apps/backend/src/api/alert/routes/alert.js":[163,1657994671437.691,"f41248e65b822d6b0e76c720af4e6305a6bfc9c3cc29883dd1d31dddb52d227c"],"/home/konhi/school-website/apps/backend/src/api/alert/services/alert.js":[166,1657994671437.691,"057fb6d4cbf02b825826902784ae493d93203d5dc8eab2b3a5ac1a0f71c148b0"],"/home/konhi/school-website/apps/backend/src/api/article/controllers/article.js":[181,1657994671437.691,"f1a8792039756cd025dc96994c98ee2144f1afaeb93375264ed59eee3fe640e0"],"/home/konhi/school-website/apps/backend/src/api/article/routes/article.js":[169,1657994671437.691,"ef4bbc3032b9e3ec42dd497564bb9a0a7c96e7aa12431d04b6233498a89a2900"],"/home/konhi/school-website/apps/backend/src/api/article/services/article.js":[172,1657994671437.691,"3f57dc0b6c05a6f327cf971ea77f5a4262f2a3e1f022c12bc6c6b6ea131cb3ff"],"/home/konhi/school-website/apps/backend/src/api/blog/controllers/blog.js":[172,1657994671437.691,"93f97285ee6ac8ad5193f4e3fb172307ea25d5806868f2b9278f4d4cf7a4a1a2"],"/home/konhi/school-website/apps/backend/src/api/blog/routes/blog.js":[160,1657994671437.691,"94e59b6d7d1fb850e95df3871997a8dd5f385528ffb83b03b14c4b3d3c9d9148"],"/home/konhi/school-website/apps/backend/src/api/blog/services/blog.js":[163,1657994671437.691,"158999889590a3c51c70088e26bd457adafa20e9f26d5a4ffee7b2456ac917e5"],"/home/konhi/school-website/apps/backend/src/api/category/controllers/category.js":[184,1657994671437.691,"ec52beb7a3f76e508c3f1c5db13679b7e0c252ee66bf8d5fd74f390afe05c1a2"],"/home/konhi/school-website/apps/backend/src/api/category/routes/category.js":[172,1657994671437.691,"f5d1ccf6307c7b5e7a4911e585b9e93f5279d306b18a5aa132c97cb3a886b669"],"/home/konhi/school-website/apps/backend/src/api/category/services/category.js":[175,1657994671437.691,"ac6a472abd8849492b40eaa87e2bb4effce05ac9c98c8015733e13cc9519c752"],"/home/konhi/school-website/apps/backend/src/api/footer/controllers/footer.js":[178,1657994671437.691,"3eda86311924f5cfe8bf99f8cbc914eb9a82f7334674752a04506b7a6e6d459b"],"/home/konhi/school-website/apps/backend/src/api/footer/routes/footer.js":[166,1657994671437.691,"611d937b4674e6d1811daae35657337de65ae4695fa30109932349281a1c35c0"],"/home/konhi/school-website/apps/backend/src/api/footer/services/footer.js":[169,1657994671437.691,"ddb7edc69c646d8aef5dc42e2b35fb7a2f31b6504e4fe7d03377dcc711da3c7b"],"/home/konhi/school-website/apps/backend/src/api/global/controllers/global.js":[178,1657994671437.691,"f180a871ae36c7c5c628e0c2f5a46485370d1523285267c22d2faa42750dd110"],"/home/konhi/school-website/apps/backend/src/api/global/routes/global.js":[166,1657994671437.691,"7a15b5c434542a270659cfd592e9fa201f2069f2450ca7c2849c0fba43fffde6"],"/home/konhi/school-website/apps/backend/src/api/global/services/global.js":[169,1657994671437.691,"292e7c00b431cb49ea301dfea56a89ef55b49e3a73f90c4bdfd0200436391cb1"],"/home/konhi/school-website/apps/backend/src/api/homepage/controllers/homepage.js":[184,1657994671437.691,"1dcb5fbb505822431e6e713e725e95bd2ba62cdbbe808a215e32588fcd34f86c"],"/home/konhi/school-website/apps/backend/src/api/homepage/routes/homepage.js":[172,1657994671437.691,"75cf3345f04aedd50625822dd1d205488df7519ccfd63614f5bc138d16b7378e"],"/home/konhi/school-website/apps/backend/src/api/homepage/services/homepage.js":[175,1657994671437.691,"682fcc4bd615b9a130f55bdd4cd843080297d08f0261e0e75d9c7aec941ae024"],"/home/konhi/school-website/apps/backend/src/api/social-media/controllers/social-media.js":[196,1657994671437.691,"7fe181b19b044886aec7bffc33404bb567a03b6e9c421b76d1dc08df075aa37c"],"/home/konhi/school-website/apps/backend/src/api/social-media/routes/social-media.js":[184,1657994671447.691,"e080fcf17a29203f40056b8e9bfacdb8654e39c326ccb8508ef006e98bf78c36"],"/home/konhi/school-website/apps/backend/src/api/social-media/services/social-media.js":[187,1657994671447.691,"64cb06dd9ee65ec02ea80ac3b4cb640e4885933aef26c2f636bbec01039bd220"],"/home/konhi/school-website/apps/backend/src/api/substitusion/controllers/substitusion.js":[196,1657994671447.691,"fd69cceabbe9796eef3a2181635f654f53a22b5191e3689b5db58c5181e2a978"],"/home/konhi/school-website/apps/backend/src/api/substitusion/routes/substitusion.js":[184,1657994671447.691,"0369aecf82ae4b1f06a9e2fc4f4f8fa16e67817a54e02a28eeaabf5ebab14ece"],"/home/konhi/school-website/apps/backend/src/api/substitusion/services/substitusion.js":[187,1657994671447.691,"da8908269e1cb5d410958c7a9ff8e268f724e11be0d7a5a05e804aba7f0b9d7d"],"/home/konhi/school-website/apps/backend/src/api/translation/controllers/translation.js":[193,1657994671447.691,"57fcd27a6df2e72dd1c10effd14e3eee9cb05e99732d7aae5f85524c1953cb45"],"/home/konhi/school-website/apps/backend/src/api/translation/routes/translation.js":[181,1657994671447.691,"636efe1497f7e037ce58cc32e5f4afceb65acf076273568a038b75a4fd8d8a9c"],"/home/konhi/school-website/apps/backend/src/api/translation/services/translation.js":[184,1657994671447.691,"d12991bd50e8769aa0f5bd7901738e8c8cfced199e38f74c9a748fd8fdf6d0f7"],"/home/konhi/school-website/apps/backend/src/api/writer/controllers/writer.js":[178,1657994671447.691,"32246629518b78b15ddc39a5b2dbb8eca638132b8b8be1e9cf9363b1cbae3ef1"],"/home/konhi/school-website/apps/backend/src/api/writer/routes/writer.js":[166,1657994671447.691,"ebcaf7dc850d4db8ca2a91dc51e2579e016dc16a47923abcda64cf6d4bcb542a"],"/home/konhi/school-website/apps/backend/src/api/writer/services/writer.js":[169,1657994671447.691,"6e4925cd414e2df6c6dccb46a18f514c57be1eca5df5dc941df2f8d7517dea55"]} -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.236.0/containers/typescript-node/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster 4 | ARG VARIANT="18-bullseye" 5 | FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT} 6 | 7 | # [Optional] Uncomment this section to install additional OS packages. 8 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 9 | # && apt-get -y install --no-install-recommends 10 | 11 | # [Optional] Uncomment if you want to install an additional version of node using nvm 12 | # ARG EXTRA_NODE_VERSION=10 13 | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 14 | 15 | # [Optional] Uncomment if you want to install more global node packages 16 | # RUN su node -c "npm install -g " 17 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.236.0/containers/typescript-node 3 | { 4 | "name": "Node.js & TypeScript", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | // Update 'VARIANT' to pick a Node version: 18, 16, 14. 8 | // Append -bullseye or -buster to pin to an OS version. 9 | // Use -bullseye variants on local on arm64/Apple Silicon. 10 | "args": { 11 | "VARIANT": "16-bullseye" 12 | } 13 | }, 14 | 15 | // Configure tool-specific properties. 16 | "customizations": { 17 | // Configure properties specific to VS Code. 18 | "vscode": { 19 | // Add the IDs of extensions you want installed when the container is created. 20 | "extensions": [ 21 | "dbaeumer.vscode-eslint", 22 | "esbenp.prettier-vscode", 23 | "visualstudioexptteam.vscodeintellicode", 24 | "GitHub.copilot", 25 | "vivaxy.vscode-conventional-commits", 26 | "apollographql.vscode-apollo" 27 | ] 28 | } 29 | }, 30 | 31 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 32 | // "forwardPorts": [8080], 33 | 34 | // Use 'postCreateCommand' to run commands after the container is created. 35 | "postCreateCommand": "cd apps/frontend && npm install", 36 | 37 | // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 38 | "remoteUser": "node", 39 | "portsAttributes": { 40 | "8080": { 41 | "label": "Next.js" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | ADMIN_JWT_SECRET="" 2 | API_TOKEN_SALT="" 3 | APP_KEYS="" 4 | DATABASE_HOST="0.0.0.0" 5 | JWT_SECRET="randomly generated secret 6 | MEILI_HOST="https://zseis-zgora-meilisearch-konhi.koyeb.app" 7 | MEILI_MASTER_KEY="randomly generated key" 8 | MYSQL_DATABASE="strapi" 9 | MYSQL_PASSWORD="password" 10 | MYSQL_PORT="3306" 11 | MYSQL_ROOT_PASSWORD="password" 12 | MYSQL_USER="strapi" 13 | NODE_ENV="development" 14 | STRAPI_HOST="0.0.0.0" # change if you use a different host 15 | STRAPI_PORT="8888" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | .env 42 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | tasks: 6 | - init: npm install && npm run build 7 | command: npm run start 8 | 9 | 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "apollographql.vscode-apollo", 6 | "visualstudioexptteam.vscodeintellicode" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "conventionalCommits.scopes": [ 3 | "frontend", 4 | "infrastructure" 5 | ] 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Elektron++ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Projekt graficzny strony 2 | 3 | 4 | 5 |
6 | Components Preview (Chromatic) 7 | Vercel status 8 |
9 |

🎒 School Website Starter

10 |

11 | Accessible and extremely user-friendly website template for schools, built on fun and modern stack. 12 |

13 | 14 | ## Folder Structure 15 | - **`📁 apps`** 16 | - **`📁 backend`**: headless CMS (API) that uses [Strapi](https://strapi.com/). 17 | - **`📁 frontend`** 18 | - **`📁 queries`**: GraphQL queries. Just create a `.graphql` that you will want to use. 19 | - **`📁 generated`**: Generated GraphQL queries with [GraphQL Code Generator](https://www.graphql-code-generator.com/). Runs automatically while developing. You import types and queries from there (not from `📁 queries`!) 20 | - **`📁 stories`**: [Storybook stories.](https://storybook.js.org/docs/react/writing-stories/introduction/) 21 | - **`📁 pages`**: [Next.js pages](https://nextjs.org/docs/basic-features/pages). 22 | - **`📁 public`**: [Next.js static file serving](https://nextjs.org/docs/basic-features/static-file-serving). 23 | - **`📁 styles`**: Global CSS Styles. It's better to use CSS modules (`component.module.css` in `📁 components` 24 | - **`📁 lib`**: Libraries wrappers (such as for Apollo Client) and configs. 25 | - **`📁 components`**: React components and their styles. There's also a [Next.js Layout](https://nextjs.org/docs/basic-features/layouts) 26 | 27 | ## Features 28 | - **⚠️ Alerts**: customizable information on top of the page 29 | - **📅 Substitusions**: inform about changes in a timetable 30 | - **🔎 Search Bar**: let users quickly find what they're looking for 31 | - **✨ Rich Footer**: customizable social media icons, e-mail, copyright, related links 32 | 33 | ## Technology 34 | ![Next JS](https://img.shields.io/badge/Next-black?style=for-the-badge&logo=next.js&logoColor=white) 35 | ![React](https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB) 36 | ![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) 37 | ![Storybook](https://img.shields.io/badge/-Storybook-FF4785?style=for-the-badge&logo=storybook&logoColor=white) 38 | ![Chakra](https://img.shields.io/badge/chakra-%234ED1C5.svg?style=for-the-badge&logo=chakraui&logoColor=white) 39 | ![Vercel](https://img.shields.io/badge/vercel-%23000000.svg?style=for-the-badge&logo=vercel&logoColor=white) 40 | ![GraphQL](https://img.shields.io/badge/-GraphQL-E10098?style=for-the-badge&logo=graphql&logoColor=white) 41 | ![MySQL](https://img.shields.io/badge/mysql-%2300f.svg?style=for-the-badge&logo=mysql&logoColor=white) 42 | ![Sentry](https://img.shields.io/badge/Sentry-6c5fc7?style=for-the-badge&logo=sentry&logoColor=white) 43 | 44 | | Technology | Description | 45 | |------------|-------------| 46 | | [**Doppler**](https://www.doppler.com/) | Doppler streamlines secrets management with a beautiful and intuitive dashboard, powerful CLI, and integrations for syncing secrets across development environments, cloud providers, hosting platforms, CI/CD tools, Docker, Kubernetes, and Terraform. | 47 | 48 | ### Front-end 49 | 50 | | Technology | Description | 51 | |------------|-------------| 52 | | [**Next.js**](https://nextjs.org/) | The React Framework for Production. Next.js gives you the best developer experience with all the features you need for production: hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and more. No config needed. | 53 | | [**React**](https://reactjs.org/) | The most popular JavaScript library for building user interfaces. | 54 | | [**TypeScript**](https://www.typescriptlang.org/) | TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. | 55 | | [**Storybook**](https://storybook.js.org/) | Tool for building UI components and pages in isolation. It streamlines UI development, testing, and documentation. | 56 | 57 | #### UI Libs 58 | | Library | Description | 59 | |---------|-------------| 60 | | [**Chakra UI**](https://chakra-ui.com/) | Chakra UI is a simple, modular and accessible component library that gives you the building blocks you need to build your React applications. We use templates from [**Chakra Templates**](https://chakra-templates.dev/). | 61 | | [**React Masonry CSS**](https://github.com/paulcollett/react-masonry-css) | A Masonry component leveraging CSS and native React rendering, for fast, responsive masonry layouts. | 62 | | [**React Accessible Headings**](https://github.com/springload/react-accessible-headings) | Makes it easier to keep heading levels semantic and accessible (WCAG) | 63 | 64 | #### Fetching 65 | | Technology | Description | 66 | |------------|-------------| 67 | | [**GraphQL**](https://graphql.org/) | Main way to fetch data from the Strapi API. Gives us automatic TypeScript support thanks to [**GraphQL Code Generator**](https://www.graphql-code-generator.com/). We use [**Apollo Client**](https://www.apollographql.com/docs/) | 68 | | **REST API** | REST is rarely used, but sometimes it works out better with Strapi plugins, for example Navigation. External developers also might want to prefer use it in their projects. [**See documentation »**](https://github.com/ElektronPlus/school-website#api) | 69 | 70 | ### Back-end 71 | | Technology | Description | 72 | |------------|-------------| 73 | | [**Strapi**](https://strapi.io) | Design APIs fast, manage content easily. Strapi is the leading open-source headless CMS. It’s 100% JavaScript, fully customizable and developer-first. | 74 | | [**MySQL**](https://www.mysql.com/) | Database with PhpMyAdmin. Might be changed to PostgreSQL. | 75 | | [**Meilisearch**](https://www.meilisearch.com/) | Meilisearch is a RESTful search API. It aims to be a ready-to-go solution for everyone who wants a fast and relevant search experience for their end-users ⚡️🔎 | 76 | 77 | ### Analytics 78 | | Technology | Description | 79 | |------------|-------------| 80 | | [**Sentry**](https://sentry.io/) | From error tracking to performance monitoring, developers can see what actually matters, solve quicker, and learn continuously about their applications - from the frontend to the backend. | 81 | 82 | ### Strapi Plugins 83 | - [**SEO**](https://market.strapi.io/plugins/@strapi-plugin-seo) 84 | 85 | ## Links 86 | - [🖼 Figma Project](https://www.figma.com/file/q12uPmoO5j5LdxRQhHIIGe/Elektronik?node-id=0%3A1) 87 | 88 | --- 89 | 90 | 91 | ## API 92 | API is based on [**Strapi**](https://strapi.io/) and it's publicly available - you're free to use it in your project! It's recommended to use GraphQL API. 93 | 94 | Here are some REST API routes: 95 | 96 | - [**strapi.elektronplus.pl/api**](https://strapi.elektronplus.pl/api) 97 | - [/substitusions](https://strapi.elektronplus.pl/api/substitusions) 98 | - [/articles](https://strapi.elektronplus.pl/api/aritcles) 99 | - [/alerts](https://strapi.elektronplus.pl/api/alerts) 100 | - [/pages](https://strapi.elektronplus.pl/api/pages) 101 | 102 | 103 | ## License & Open-Source 104 | 105 | - The project is open-sourced and available on [**⚖ MIT License**](https://github.com/ElektronPlus/school-website/blob/main/LICENSE). 106 | - We offer free assistance with implementation as needed for schools and educational organizations. **Contact Us! »** 107 | - You noticed a mistake or want to suggest something? [**Create an Issue »**](https://github.com/ElektronPlus/school-website/issues) or **Contact Us! »** 108 | 109 | 110 | ## Infrastructure 111 | 112 | This section explains this specific instance infrastructure. You're free to use anything you want. 113 | 114 | | Type | Infrastructure | Instance | Description | 115 | |-------|-----------------|----------|------------ 116 | | Frontend | [**▲ Vercel**](https://vercel.com/?utm_source=ElektronPlus&utm_campaign=oss) | [dev.elektronplus.pl](https://dev.elektronplus.pl) | Website | 117 | | Backend | **Self-hosted VPS** | [strapi.elektronplus.pl](https://strapi.elektronplus.pl/) | Strapi (Headless CMS), MySQL Database | 118 | | Meilisearch | [**Koyeb**](https://www.koyeb.com/) | [zseis-zgora-meilisearch-konhi.koyeb.app](https://zseis-zgora-meilisearch-konhi.koyeb.app/) | Search Engine | 119 | | Developer-only | [**Chromatic**](https://www.chromatic.com/) | [**See components »**](https://main--6284fb53d2efc2004a5d01dd.chromatic.com/) | Storybook | 120 | 121 |
122 | 123 | --- 124 | 125 | Powered by Vercel 126 |
127 | 128 | 129 | This project is proudly powered by Vercel - a platform for frontend frameworks and static sites, built to integrate with your headless content, commerce, or database that provide a frictionless developer experience to take care of the hard things: deploying instantly, scaling automatically, and serving personalized content around the globe. 130 | 131 |

Interested? Check ▲ Vercel! »

132 | 133 | 134 |
135 | 136 |
137 | -------------------------------------------------------------------------------- /apps/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElektronPlus/school-website/c1e15485c34d8ce88d46e5f5f7269baf78c740cb/apps/.gitkeep -------------------------------------------------------------------------------- /apps/backend/.dockerignore: -------------------------------------------------------------------------------- 1 | .tmp/ 2 | .cache/ 3 | .git/ 4 | build/ 5 | node_modules/ 6 | data/ -------------------------------------------------------------------------------- /apps/backend/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [{package.json,*.yml}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /apps/backend/.eslintignore: -------------------------------------------------------------------------------- 1 | .cache 2 | build 3 | **/node_modules/** 4 | -------------------------------------------------------------------------------- /apps/backend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint:recommended", 4 | "env": { 5 | "commonjs": true, 6 | "es6": true, 7 | "node": true, 8 | "browser": false 9 | }, 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "experimentalObjectRestSpread": true, 13 | "jsx": false 14 | }, 15 | "sourceType": "module" 16 | }, 17 | "globals": { 18 | "strapi": true 19 | }, 20 | "rules": { 21 | "indent": ["error", 2, { "SwitchCase": 1 }], 22 | "linebreak-style": ["error", "unix"], 23 | "no-console": 0, 24 | "quotes": ["error", "single"], 25 | "semi": ["error", "always"] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/backend/.gitignore: -------------------------------------------------------------------------------- 1 | ############################ 2 | # OS X 3 | ############################ 4 | 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | Icon 9 | .Spotlight-V100 10 | .Trashes 11 | ._* 12 | 13 | 14 | ############################ 15 | # Linux 16 | ############################ 17 | 18 | *~ 19 | 20 | 21 | ############################ 22 | # Windows 23 | ############################ 24 | 25 | Thumbs.db 26 | ehthumbs.db 27 | Desktop.ini 28 | $RECYCLE.BIN/ 29 | *.cab 30 | *.msi 31 | *.msm 32 | *.msp 33 | 34 | 35 | ############################ 36 | # Packages 37 | ############################ 38 | 39 | *.7z 40 | *.csv 41 | *.dat 42 | *.dmg 43 | *.gz 44 | *.iso 45 | *.jar 46 | *.rar 47 | *.tar 48 | *.zip 49 | *.com 50 | *.class 51 | *.dll 52 | *.exe 53 | *.o 54 | *.seed 55 | *.so 56 | *.swo 57 | *.swp 58 | *.swn 59 | *.swm 60 | *.out 61 | *.pid 62 | 63 | 64 | ############################ 65 | # Logs and databases 66 | ############################ 67 | 68 | .tmp 69 | *.log 70 | *.sql 71 | *.sqlite 72 | *.sqlite3 73 | 74 | 75 | ############################ 76 | # Misc. 77 | ############################ 78 | 79 | *# 80 | ssl 81 | .idea 82 | nbproject 83 | public/uploads/* 84 | !public/uploads/.gitkeep 85 | 86 | ############################ 87 | # Node.js 88 | ############################ 89 | 90 | lib-cov 91 | lcov.info 92 | pids 93 | logs 94 | results 95 | node_modules 96 | .node_history 97 | 98 | ############################ 99 | # Tests 100 | ############################ 101 | 102 | testApp 103 | coverage 104 | 105 | ############################ 106 | # Strapi 107 | ############################ 108 | 109 | .env 110 | license.txt 111 | exports 112 | *.cache 113 | build 114 | .strapi-updater.json 115 | -------------------------------------------------------------------------------- /apps/backend/.nvmrc: -------------------------------------------------------------------------------- 1 | 16 -------------------------------------------------------------------------------- /apps/backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | # Strapi - https://blog.dehlin.dev/docker-with-strapi-v4 3 | 4 | # Installing libvips-dev for sharp compatability (image processing library) 5 | RUN apk add --upgrade --no-cache vips-dev build-base --repository https://alpine.global.ssl.fastly.net/alpine/v3.10/community/ 6 | 7 | WORKDIR /opt/ 8 | 9 | COPY ./package.json ./package-lock.json ./ 10 | ENV PATH /opt/node_modules/.bin:$PATH 11 | RUN ["npm", "install", "--legacy-peer-deeps"] 12 | WORKDIR /opt/app 13 | COPY ./ . 14 | 15 | RUN ["npm", "run", "build"] 16 | 17 | EXPOSE ${PORT} 18 | 19 | # on production: ["npm", "run", "start"] 20 | CMD ["npm", "run", "develop"] -------------------------------------------------------------------------------- /apps/backend/README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Getting started with Strapi 2 | 3 | Strapi comes with a full featured [Command Line Interface](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html) (CLI) which lets you scaffold and manage your project in seconds. 4 | 5 | ### `develop` 6 | 7 | Start your Strapi application with autoReload enabled. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-develop) 8 | 9 | ``` 10 | npm run develop 11 | # or 12 | yarn develop 13 | ``` 14 | 15 | ### `start` 16 | 17 | Start your Strapi application with autoReload disabled. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-start) 18 | 19 | ``` 20 | npm run start 21 | # or 22 | yarn start 23 | ``` 24 | 25 | ### `build` 26 | 27 | Build your admin panel. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-build) 28 | 29 | ``` 30 | npm run build 31 | # or 32 | yarn build 33 | ``` 34 | 35 | ## ⚙️ Deployment 36 | 37 | Strapi gives you many possible deployment options for your project. Find the one that suits you on the [deployment section of the documentation](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/deployment.html). 38 | 39 | ## 📚 Learn more 40 | 41 | - [Resource center](https://strapi.io/resource-center) - Strapi resource center. 42 | - [Strapi documentation](https://docs.strapi.io) - Official Strapi documentation. 43 | - [Strapi tutorials](https://strapi.io/tutorials) - List of tutorials made by the core team and the community. 44 | - [Strapi blog](https://docs.strapi.io) - Official Strapi blog containing articles made by the Strapi team and the community. 45 | - [Changelog](https://strapi.io/changelog) - Find out about the Strapi product updates, new features and general improvements. 46 | 47 | Feel free to check out the [Strapi GitHub repository](https://github.com/strapi/strapi). Your feedback and contributions are welcome! 48 | 49 | ## ✨ Community 50 | 51 | - [Discord](https://discord.strapi.io) - Come chat with the Strapi community including the core team. 52 | - [Forum](https://forum.strapi.io/) - Place to discuss, ask questions and find answers, show your Strapi project and get feedback or just talk with other Community members. 53 | - [Awesome Strapi](https://github.com/strapi/awesome-strapi) - A curated list of awesome things related to Strapi. 54 | 55 | --- 56 | 57 | 🤫 Psst! [Strapi is hiring](https://strapi.io/careers). 58 | -------------------------------------------------------------------------------- /apps/backend/config/admin.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | auth: { 3 | secret: env('ADMIN_JWT_SECRET'), 4 | }, 5 | apiToken: { 6 | salt: env('API_TOKEN_SALT'), 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /apps/backend/config/api.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rest: { 3 | defaultLimit: 25, 4 | maxLimit: 100, 5 | withCount: true, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /apps/backend/config/database.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | connection: { 3 | client: env('DATABASE_CLIENT', 'mysql'), 4 | 5 | connection: { 6 | host: env('DATABASE_HOST', '127.0.0.1'), 7 | port: env.int('DATABASE_PORT', 5432), 8 | database: env('DATABASE_NAME', 'strapi'), 9 | user: env('DATABASE_USERNAME', 'strapi'), 10 | password: env('DATABASE_PASSWORD', 'strapi'), 11 | }, 12 | debug: false, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /apps/backend/config/middlewares.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 'strapi::errors', 3 | 'strapi::security', 4 | 'strapi::cors', 5 | 'strapi::poweredBy', 6 | 'strapi::logger', 7 | 'strapi::query', 8 | 'strapi::body', 9 | 'strapi::session', 10 | 'strapi::favicon', 11 | 'strapi::public', 12 | ]; 13 | -------------------------------------------------------------------------------- /apps/backend/config/plugins.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => { 2 | return { 3 | ckeditor: { 4 | enabled: true, 5 | }, 6 | seo: { 7 | enabled: true, 8 | }, 9 | navigation: { 10 | enabled: true, 11 | }, 12 | placeholder: { 13 | enabled: true, 14 | config: { 15 | size: 10, 16 | } 17 | }, 18 | publisher: { 19 | enabled: true, 20 | }, 21 | graphql: { 22 | enabled: true, 23 | }, 24 | i18n: { 25 | enabled: false 26 | }, 27 | 'users-permissions': { 28 | enabled: true, 29 | }, 30 | 'url-alias': { 31 | enabled: true 32 | }, 33 | meilisearch: { 34 | enabled: true, 35 | config: { 36 | host: env('MEILI_HOST'), 37 | apiKey: env('MEILI_MASTER_KEY'), 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /apps/backend/config/server.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | host: env('HOST', '0.0.0.0'), 3 | port: env.int('PORT', 1337), 4 | app: { 5 | keys: env.array('APP_KEYS'), 6 | }, 7 | cron: { 8 | enabled: true // strapi-plugin-publisher relies on it 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /apps/backend/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElektronPlus/school-website/c1e15485c34d8ce88d46e5f5f7269baf78c740cb/apps/backend/favicon.ico -------------------------------------------------------------------------------- /apps/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@_sh/strapi-plugin-ckeditor": "^1.1.2", 4 | "@exfabrica/strapi-plugin-awesome-help": "^1.0.8", 5 | "@strapi-community/strapi-plugin-url-alias": "^1.0.0-alpha.4", 6 | "@strapi/plugin-graphql": "^4.3.4", 7 | "@strapi/plugin-i18n": "^4.3.4", 8 | "@strapi/plugin-seo": "^1.7.4", 9 | "@strapi/plugin-users-permissions": "^4.3.4", 10 | "@strapi/strapi": "^4.3.4", 11 | "lodash.set": "^4.3.2", 12 | "mime-types": "^2.1.35", 13 | "mysql": "^2.18.1", 14 | "strapi-plugin-meilisearch": "^0.7.1", 15 | "strapi-plugin-navigation": "^2.2.1", 16 | "strapi-plugin-placeholder": "^4.1.7", 17 | "strapi-plugin-publisher": "^1.1.3" 18 | }, 19 | "name": "backend", 20 | "private": true, 21 | "version": "0.1.0", 22 | "description": "A Strapi application", 23 | "scripts": { 24 | "develop": "strapi develop", 25 | "start": "strapi start", 26 | "build": "strapi build", 27 | "strapi": "strapi" 28 | }, 29 | "author": { 30 | "name": "A Strapi developer" 31 | }, 32 | "strapi": { 33 | "uuid": "0ef42828-e794-4f7b-b7d7-308de87c569b", 34 | "template": "@strapi/template-blog@1.0.0" 35 | }, 36 | "engines": { 37 | "node": ">=12.x.x <=16.x.x", 38 | "npm": ">=6.0.0" 39 | }, 40 | "license": "MIT" 41 | } 42 | -------------------------------------------------------------------------------- /apps/backend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # To prevent search engines from seeing the site altogether, uncomment the next two lines: 2 | # User-Agent: * 3 | # Disallow: / 4 | -------------------------------------------------------------------------------- /apps/backend/public/uploads/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElektronPlus/school-website/c1e15485c34d8ce88d46e5f5f7269baf78c740cb/apps/backend/public/uploads/.gitkeep -------------------------------------------------------------------------------- /apps/backend/src/admin/app.js: -------------------------------------------------------------------------------- 1 | export default { 2 | config: { 3 | locales: ['pl'], 4 | }, 5 | bootstrap(app) { 6 | console.log(app); 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /apps/backend/src/admin/webpack.config.example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable no-unused-vars */ 4 | module.exports = (config, webpack) => { 5 | // Note: we provide webpack above so you should not `require` it 6 | // Perform customizations to webpack config 7 | // Important: return the modified config 8 | return config; 9 | }; 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElektronPlus/school-website/c1e15485c34d8ce88d46e5f5f7269baf78c740cb/apps/backend/src/api/.gitkeep -------------------------------------------------------------------------------- /apps/backend/src/api/alert/content-types/alert/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "alerts", 4 | "info": { 5 | "singularName": "alert", 6 | "pluralName": "alerts", 7 | "displayName": "Layout.Alert", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": false 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "message": { 16 | "type": "string", 17 | "default": "Important changes to our schedule" 18 | }, 19 | "link": { 20 | "type": "string", 21 | "default": "https://en.wikipedia.org/wiki/Warsaw_University_of_Technology" 22 | }, 23 | "isVisible": { 24 | "type": "boolean", 25 | "default": true, 26 | "required": true 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/backend/src/api/alert/controllers/alert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * alert controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::alert.alert'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/alert/routes/alert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * alert router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::alert.alert'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/alert/services/alert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * alert service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::alert.alert'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/article/content-types/article/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "articles", 4 | "info": { 5 | "singularName": "article", 6 | "pluralName": "articles", 7 | "displayName": "Article", 8 | "name": "article", 9 | "description": "" 10 | }, 11 | "options": { 12 | "increments": true, 13 | "timestamps": true, 14 | "draftAndPublish": true 15 | }, 16 | "pluginOptions": {}, 17 | "attributes": { 18 | "title": { 19 | "type": "string", 20 | "required": true 21 | }, 22 | "content": { 23 | "type": "richtext", 24 | "required": true 25 | }, 26 | "slug": { 27 | "type": "uid", 28 | "targetField": "title", 29 | "required": true 30 | }, 31 | "category": { 32 | "type": "relation", 33 | "relation": "manyToOne", 34 | "target": "api::category.category", 35 | "inversedBy": "articles" 36 | }, 37 | "image": { 38 | "type": "media", 39 | "multiple": false, 40 | "required": false, 41 | "allowedTypes": [ 42 | "images" 43 | ] 44 | }, 45 | "author": { 46 | "type": "relation", 47 | "relation": "manyToOne", 48 | "target": "api::writer.writer", 49 | "inversedBy": "articles" 50 | }, 51 | "seo": { 52 | "type": "component", 53 | "repeatable": false, 54 | "component": "shared.seo" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /apps/backend/src/api/article/controllers/article.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * article controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::article.article'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/article/routes/article.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * article router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::article.article'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/article/services/article.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * article service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::article.article'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/blog/content-types/blog/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "blogs", 4 | "info": { 5 | "singularName": "blog", 6 | "pluralName": "blogs", 7 | "displayName": "Page.Blog", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": false 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "articlesSection": { 16 | "displayName": "Articles Section", 17 | "type": "component", 18 | "repeatable": false, 19 | "component": "articles.articles-section", 20 | "required": true 21 | }, 22 | "categorySection": { 23 | "type": "component", 24 | "repeatable": false, 25 | "component": "articles.articles-section" 26 | }, 27 | "seo": { 28 | "type": "component", 29 | "repeatable": false, 30 | "component": "shared.seo" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/backend/src/api/blog/content-types/blog/schema.json.save: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "blogs", 4 | "info": { 5 | "singularName": "blog", 6 | "pluralName": "blogs", 7 | "displayName": "Blog", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": true 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "articlesSection": { 16 | "displayName": "Articles Section", 17 | "type": "component", 18 | "repeatable": false, 19 | "component": "articles.articles-section", 20 | "required": true 21 | }, 22 | "categorySection": { 23 | "type": "component", 24 | "repeatable": false, 25 | "component": "articles.articles-section" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/backend/src/api/blog/controllers/blog.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * blog controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::blog.blog'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/blog/routes/blog.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * blog router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::blog.blog'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/blog/services/blog.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * blog service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::blog.blog'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/category/content-types/category/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "categories", 4 | "info": { 5 | "singularName": "category", 6 | "pluralName": "categories", 7 | "displayName": "Category", 8 | "name": "category", 9 | "description": "" 10 | }, 11 | "options": { 12 | "increments": true, 13 | "timestamps": true, 14 | "draftAndPublish": false 15 | }, 16 | "attributes": { 17 | "name": { 18 | "type": "string", 19 | "required": true 20 | }, 21 | "slug": { 22 | "type": "uid", 23 | "targetField": "name", 24 | "required": true 25 | }, 26 | "articles": { 27 | "type": "relation", 28 | "relation": "oneToMany", 29 | "target": "api::article.article", 30 | "mappedBy": "category" 31 | }, 32 | "description": { 33 | "type": "richtext" 34 | }, 35 | "seo": { 36 | "type": "component", 37 | "repeatable": false, 38 | "component": "shared.seo" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /apps/backend/src/api/category/controllers/category.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * category controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::category.category'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/category/routes/category.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * category router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::category.category'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/category/services/category.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * category service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::category.category'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/footer/content-types/footer/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "footers", 4 | "info": { 5 | "singularName": "footer", 6 | "pluralName": "footers", 7 | "displayName": "Layout.Footer", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": false 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "showVercelBadge": { 16 | "type": "boolean", 17 | "default": true, 18 | "required": true 19 | }, 20 | "copyright": { 21 | "type": "richtext", 22 | "default": "Copyright ©" 23 | }, 24 | "email": { 25 | "type": "email", 26 | "default": "hello@uni.edu" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/backend/src/api/footer/controllers/footer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * footer controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::footer.footer'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/footer/routes/footer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * footer router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::footer.footer'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/footer/services/footer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * footer service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::footer.footer'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/global/content-types/global/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "globals", 4 | "info": { 5 | "singularName": "global", 6 | "pluralName": "globals", 7 | "displayName": "Global", 8 | "name": "global", 9 | "description": "" 10 | }, 11 | "options": { 12 | "increments": true, 13 | "timestamps": true, 14 | "draftAndPublish": false 15 | }, 16 | "attributes": { 17 | "siteName": { 18 | "type": "string", 19 | "required": true, 20 | "default": "Warsaw University" 21 | }, 22 | "favicon": { 23 | "type": "media", 24 | "multiple": false, 25 | "required": false, 26 | "allowedTypes": [ 27 | "images", 28 | "files", 29 | "videos" 30 | ] 31 | }, 32 | "logo": { 33 | "type": "media", 34 | "multiple": false, 35 | "required": false, 36 | "allowedTypes": [ 37 | "images" 38 | ] 39 | }, 40 | "language": { 41 | "type": "enumeration", 42 | "enum": [ 43 | "aa", 44 | "ab", 45 | "ae", 46 | "af", 47 | "ak", 48 | "am", 49 | "an", 50 | "ar", 51 | "as", 52 | "av", 53 | "ay", 54 | "az", 55 | "ba", 56 | "be", 57 | "bg", 58 | "bh", 59 | "bi", 60 | "bm", 61 | "bn", 62 | "bo", 63 | "br", 64 | "bs", 65 | "ca", 66 | "ce", 67 | "ch", 68 | "co", 69 | "cr", 70 | "cs", 71 | "cu", 72 | "cv", 73 | "cy", 74 | "da", 75 | "de", 76 | "dv", 77 | "dz", 78 | "ee", 79 | "el", 80 | "en", 81 | "eo", 82 | "es", 83 | "et", 84 | "eu", 85 | "fa", 86 | "ff", 87 | "fi", 88 | "fj", 89 | "fo", 90 | "fr", 91 | "fy", 92 | "ga", 93 | "gd", 94 | "gl", 95 | "gn", 96 | "gu", 97 | "gv", 98 | "ha", 99 | "he", 100 | "hi", 101 | "ho", 102 | "hr", 103 | "ht", 104 | "hu", 105 | "hy", 106 | "hz", 107 | "ia", 108 | "id", 109 | "ie", 110 | "ig", 111 | "ii", 112 | "ik", 113 | "io", 114 | "is", 115 | "it", 116 | "iu", 117 | "ja", 118 | "jv", 119 | "ka", 120 | "kg", 121 | "ki", 122 | "kj", 123 | "kk", 124 | "kl", 125 | "km", 126 | "kn", 127 | "ko", 128 | "kr", 129 | "ks", 130 | "ku", 131 | "kv", 132 | "kw", 133 | "ky", 134 | "la", 135 | "lb", 136 | "lg", 137 | "li", 138 | "ln", 139 | "lo", 140 | "lt", 141 | "lu", 142 | "lv", 143 | "mg", 144 | "mh", 145 | "mi", 146 | "mk", 147 | "ml", 148 | "mn", 149 | "mr", 150 | "ms", 151 | "mt", 152 | "my", 153 | "na", 154 | "nb", 155 | "nd", 156 | "ne", 157 | "ng", 158 | "nl", 159 | "nn", 160 | "no", 161 | "nr", 162 | "nv", 163 | "ny", 164 | "oc", 165 | "oj", 166 | "om", 167 | "or", 168 | "os", 169 | "pa", 170 | "pi", 171 | "pl", 172 | "ps", 173 | "pt", 174 | "qu", 175 | "rm", 176 | "rn", 177 | "ro", 178 | "ru", 179 | "rw", 180 | "sa", 181 | "sc", 182 | "sd", 183 | "se", 184 | "sg", 185 | "si", 186 | "sk", 187 | "sl", 188 | "sm", 189 | "sn", 190 | "so", 191 | "sq", 192 | "sr", 193 | "ss", 194 | "st", 195 | "su", 196 | "sv", 197 | "sw", 198 | "ta", 199 | "te", 200 | "tg", 201 | "th", 202 | "ti", 203 | "tk", 204 | "tl", 205 | "tn", 206 | "to", 207 | "tr", 208 | "ts", 209 | "tt", 210 | "tw", 211 | "ty", 212 | "ug", 213 | "uk", 214 | "ur", 215 | "uz", 216 | "ve", 217 | "vi", 218 | "vo", 219 | "wa", 220 | "wo", 221 | "xh", 222 | "yi", 223 | "yo", 224 | "za", 225 | "zh", 226 | "zu" 227 | ], 228 | "required": true, 229 | "default": "en" 230 | }, 231 | "background": { 232 | "type": "media", 233 | "multiple": false, 234 | "required": false, 235 | "allowedTypes": [ 236 | "images", 237 | "videos" 238 | ] 239 | }, 240 | "shareImage": { 241 | "type": "media", 242 | "multiple": false, 243 | "required": false, 244 | "allowedTypes": [ 245 | "images" 246 | ] 247 | }, 248 | "siteDescription": { 249 | "type": "string", 250 | "maxLength": 160, 251 | "minLength": 50, 252 | "required": true 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /apps/backend/src/api/global/controllers/global.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * global controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::global.global'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/global/routes/global.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * global router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::global.global'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/global/services/global.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * global service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::global.global'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/homepage/content-types/homepage/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "homepages", 4 | "info": { 5 | "singularName": "homepage", 6 | "pluralName": "homepages", 7 | "displayName": "Page.Home", 8 | "name": "homepage", 9 | "description": "" 10 | }, 11 | "options": { 12 | "increments": true, 13 | "timestamps": true, 14 | "draftAndPublish": false 15 | }, 16 | "pluginOptions": {}, 17 | "attributes": { 18 | "articlesSection": { 19 | "type": "component", 20 | "repeatable": false, 21 | "component": "articles.articles-section" 22 | }, 23 | "seo": { 24 | "type": "component", 25 | "repeatable": false, 26 | "component": "shared.seo" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/backend/src/api/homepage/controllers/homepage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * homepage controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::homepage.homepage'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/homepage/routes/homepage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * homepage router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::homepage.homepage'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/homepage/services/homepage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * homepage service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::homepage.homepage'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/social-media/content-types/social-media/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "social_medias", 4 | "info": { 5 | "singularName": "social-media", 6 | "pluralName": "social-medias", 7 | "displayName": "Social Media", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": false 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "link": { 16 | "type": "string", 17 | "required": true 18 | }, 19 | "iconSlug": { 20 | "type": "string", 21 | "required": true 22 | }, 23 | "showInFooter": { 24 | "type": "boolean", 25 | "default": true, 26 | "required": true 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/backend/src/api/social-media/controllers/social-media.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * social-media controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::social-media.social-media'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/social-media/routes/social-media.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * social-media router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::social-media.social-media'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/social-media/services/social-media.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * social-media service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::social-media.social-media'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/substitusion/content-types/substitusion/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "substitusions", 4 | "info": { 5 | "singularName": "substitusion", 6 | "pluralName": "substitusions", 7 | "displayName": "Substitusion" 8 | }, 9 | "options": { 10 | "draftAndPublish": true 11 | }, 12 | "pluginOptions": {}, 13 | "attributes": { 14 | "date": { 15 | "type": "date" 16 | }, 17 | "content": { 18 | "type": "richtext" 19 | }, 20 | "author": { 21 | "type": "relation", 22 | "relation": "manyToOne", 23 | "target": "api::writer.writer", 24 | "inversedBy": "substitusions" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/backend/src/api/substitusion/controllers/substitusion.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * substitusion controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::substitusion.substitusion'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/substitusion/routes/substitusion.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * substitusion router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::substitusion.substitusion'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/substitusion/services/substitusion.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * substitusion service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::substitusion.substitusion'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/translation/content-types/translation/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "translations", 4 | "info": { 5 | "singularName": "translation", 6 | "pluralName": "translations", 7 | "displayName": "Translation", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": false 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "paginationNextPage": { 16 | "type": "string", 17 | "default": "Next Page", 18 | "required": true 19 | }, 20 | "articleReadMore": { 21 | "type": "string", 22 | "default": "Read More", 23 | "required": true 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/backend/src/api/translation/controllers/translation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * translation controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::translation.translation'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/translation/routes/translation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * translation router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::translation.translation'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/translation/services/translation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * translation service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::translation.translation'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/writer/content-types/writer/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "writers", 4 | "info": { 5 | "singularName": "writer", 6 | "pluralName": "writers", 7 | "displayName": "Writer", 8 | "name": "writer", 9 | "description": "" 10 | }, 11 | "options": { 12 | "increments": true, 13 | "timestamps": true, 14 | "draftAndPublish": false 15 | }, 16 | "attributes": { 17 | "name": { 18 | "type": "string", 19 | "required": true 20 | }, 21 | "picture": { 22 | "type": "media", 23 | "multiple": false, 24 | "required": false, 25 | "allowedTypes": [ 26 | "images", 27 | "files", 28 | "videos" 29 | ] 30 | }, 31 | "articles": { 32 | "type": "relation", 33 | "relation": "oneToMany", 34 | "target": "api::article.article", 35 | "mappedBy": "author" 36 | }, 37 | "email": { 38 | "type": "string" 39 | }, 40 | "substitusions": { 41 | "type": "relation", 42 | "relation": "oneToMany", 43 | "target": "api::substitusion.substitusion", 44 | "mappedBy": "author" 45 | }, 46 | "description": { 47 | "type": "string", 48 | "unique": false, 49 | "required": true 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /apps/backend/src/api/writer/controllers/writer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * writer controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::writer.writer'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/writer/routes/writer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * writer router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::writer.writer'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/api/writer/services/writer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * writer service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::writer.writer'); 10 | -------------------------------------------------------------------------------- /apps/backend/src/components/articles/articles-section.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_articles_articles_sections", 3 | "info": { 4 | "displayName": "Articles.Section", 5 | "icon": "border-all", 6 | "description": "" 7 | }, 8 | "options": {}, 9 | "attributes": { 10 | "entriesPerPage": { 11 | "type": "integer", 12 | "required": true, 13 | "default": 6, 14 | "min": 1 15 | }, 16 | "header": { 17 | "type": "string", 18 | "required": true, 19 | "default": "What's new" 20 | }, 21 | "previewMaxCharacters": { 22 | "type": "integer", 23 | "default": 500, 24 | "min": 0 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/backend/src/components/shared/meta-social.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_shared_meta_socials", 3 | "info": { 4 | "displayName": "metaSocial", 5 | "icon": "project-diagram" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "socialNetwork": { 10 | "type": "enumeration", 11 | "enum": [ 12 | "Facebook", 13 | "Twitter" 14 | ], 15 | "required": true 16 | }, 17 | "title": { 18 | "type": "string", 19 | "required": true, 20 | "maxLength": 60 21 | }, 22 | "description": { 23 | "type": "string", 24 | "maxLength": 65, 25 | "required": true 26 | }, 27 | "image": { 28 | "allowedTypes": [ 29 | "images", 30 | "files", 31 | "videos" 32 | ], 33 | "type": "media", 34 | "multiple": false 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /apps/backend/src/components/shared/seo.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_shared_seos", 3 | "info": { 4 | "displayName": "seo", 5 | "icon": "search", 6 | "description": "" 7 | }, 8 | "options": {}, 9 | "attributes": { 10 | "metaDescription": { 11 | "type": "string", 12 | "required": false, 13 | "maxLength": 160, 14 | "minLength": 50 15 | }, 16 | "preventIndexing": { 17 | "type": "boolean", 18 | "default": false 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/backend/src/extensions/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElektronPlus/school-website/c1e15485c34d8ce88d46e5f5f7269baf78c740cb/apps/backend/src/extensions/.gitkeep -------------------------------------------------------------------------------- /apps/frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "prettier"], 3 | "rules": { 4 | "react/no-children-prop": "off", 5 | "react/display-name": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | *.graphql.d.ts 35 | *.graphqls.d.ts 36 | /.cache 37 | 38 | # Sentry 39 | .sentryclirc 40 | -------------------------------------------------------------------------------- /apps/frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /apps/frontend/build-storybook.log: -------------------------------------------------------------------------------- 1 | info @storybook/react v6.4.22 2 | info 3 | info => Cleaning outputDir: /tmp/chromatic--2754-sL4ajaHKAg0O 4 | info => Loading presets 5 | info => Compiling manager.. 6 | info => Compiling preview.. 7 | info => Using implicit CSS loaders 8 | info => Using default Webpack5 setup 9 | [webpack.Progress] 0% 10 | 11 | [webpack.Progress] 1% setup before run 12 | [webpack.Progress] 1% setup before run NodeEnvironmentPlugin 13 | [webpack.Progress] 1% setup before run 14 | [webpack.Progress] 2% setup run 15 | [webpack.Progress] 2% setup run 16 | [webpack.Progress] 4% setup normal module factory 17 | [webpack.Progress] 4% setup normal module factory CaseSensitivePathsPlugin 18 | [webpack.Progress] 4% setup normal module factory 19 | [webpack.Progress] 5% setup context module factory 20 | [webpack.Progress] 5% setup context module factory 21 | [webpack.Progress] 6% setup before compile 22 | [webpack.Progress] 6% setup before compile ProgressPlugin 23 | [webpack.Progress] 6% setup before compile 24 | [webpack.Progress] 7% setup compile 25 | [webpack.Progress] 7% setup compile ExternalsPlugin 26 | [webpack.Progress] 7% setup compile 27 | [webpack.Progress] 8% setup compilation 28 | [webpack.Progress] 8% setup compilation ArrayPushCallbackChunkFormatPlugin 29 | [webpack.Progress] 8% setup compilation JsonpChunkLoadingPlugin 30 | [webpack.Progress] 8% setup compilation StartupChunkDependenciesPlugin 31 | [webpack.Progress] 8% setup compilation ImportScriptsChunkLoadingPlugin 32 | [webpack.Progress] 8% setup compilation FetchCompileWasmPlugin 33 | [webpack.Progress] 8% setup compilation FetchCompileAsyncWasmPlugin 34 | [webpack.Progress] 8% setup compilation WorkerPlugin 35 | [webpack.Progress] 8% setup compilation SplitChunksPlugin 36 | [webpack.Progress] 8% setup compilation RuntimeChunkPlugin 37 | [webpack.Progress] 8% setup compilation ResolverCachePlugin 38 | [webpack.Progress] 8% setup compilation HtmlWebpackPlugin 39 | [webpack.Progress] 8% setup compilation 40 | [webpack.Progress] 9% setup compilation 41 | [webpack.Progress] 9% setup compilation DefinePlugin 42 | [webpack.Progress] 9% setup compilation ProvidePlugin 43 | [webpack.Progress] 9% setup compilation ProgressPlugin 44 | [webpack.Progress] 9% setup compilation DocGenPlugin 45 | [webpack.Progress] 9% setup compilation ChunkPrefetchPreloadPlugin 46 | [webpack.Progress] 9% setup compilation SourceMapDevToolPlugin 47 | [webpack.Progress] 9% setup compilation JavascriptModulesPlugin 48 | [webpack.Progress] 9% setup compilation JsonModulesPlugin 49 | [webpack.Progress] 9% setup compilation AssetModulesPlugin 50 | [webpack.Progress] 9% setup compilation EntryPlugin 51 | [webpack.Progress] 9% setup compilation EntryPlugin 52 | [webpack.Progress] 9% setup compilation EntryPlugin 53 | [webpack.Progress] 9% setup compilation EntryPlugin 54 | [webpack.Progress] 9% setup compilation EntryPlugin 55 | [webpack.Progress] 9% setup compilation EntryPlugin 56 | [webpack.Progress] 9% setup compilation EntryPlugin 57 | [webpack.Progress] 9% setup compilation EntryPlugin 58 | [webpack.Progress] 9% setup compilation EntryPlugin 59 | [webpack.Progress] 9% setup compilation EntryPlugin 60 | [webpack.Progress] 9% setup compilation EntryPlugin 61 | [webpack.Progress] 9% setup compilation EntryPlugin 62 | [webpack.Progress] 9% setup compilation EntryPlugin 63 | [webpack.Progress] 9% setup compilation EntryPlugin 64 | [webpack.Progress] 9% setup compilation EntryPlugin 65 | [webpack.Progress] 9% setup compilation EntryPlugin 66 | [webpack.Progress] 9% setup compilation RuntimePlugin 67 | [webpack.Progress] 9% setup compilation InferAsyncModulesPlugin 68 | [webpack.Progress] 9% setup compilation DataUriPlugin 69 | [webpack.Progress] 9% setup compilation FileUriPlugin 70 | [webpack.Progress] 9% setup compilation CompatibilityPlugin 71 | [webpack.Progress] 9% setup compilation HarmonyModulesPlugin 72 | [webpack.Progress] 9% setup compilation AMDPlugin 73 | [webpack.Progress] 9% setup compilation RequireJsStuffPlugin 74 | [webpack.Progress] 9% setup compilation CommonJsPlugin 75 | [webpack.Progress] 9% setup compilation LoaderPlugin 76 | [webpack.Progress] 9% setup compilation LoaderPlugin 77 | [webpack.Progress] 9% setup compilation NodeStuffPlugin 78 | [webpack.Progress] 9% setup compilation APIPlugin 79 | [webpack.Progress] 9% setup compilation ExportsInfoApiPlugin 80 | [webpack.Progress] 9% setup compilation WebpackIsIncludedPlugin 81 | [webpack.Progress] 9% setup compilation ConstPlugin 82 | [webpack.Progress] 9% setup compilation UseStrictPlugin 83 | [webpack.Progress] 9% setup compilation RequireIncludePlugin 84 | [webpack.Progress] 9% setup compilation RequireEnsurePlugin 85 | [webpack.Progress] 9% setup compilation RequireContextPlugin 86 | [webpack.Progress] 9% setup compilation ImportPlugin 87 | [webpack.Progress] 9% setup compilation RequireContextPlugin 88 | [webpack.Progress] 9% setup compilation SystemPlugin 89 | [webpack.Progress] 9% setup compilation ImportMetaPlugin 90 | [webpack.Progress] 9% setup compilation URLPlugin 91 | [webpack.Progress] 9% setup compilation DefaultStatsFactoryPlugin 92 | [webpack.Progress] 9% setup compilation DefaultStatsPresetPlugin 93 | [webpack.Progress] 9% setup compilation DefaultStatsPrinterPlugin 94 | [webpack.Progress] 9% setup compilation JavascriptMetaInfoPlugin 95 | [webpack.Progress] 9% setup compilation EnsureChunkConditionsPlugin 96 | [webpack.Progress] 9% setup compilation RemoveEmptyChunksPlugin 97 | [webpack.Progress] 9% setup compilation MergeDuplicateChunksPlugin 98 | [webpack.Progress] 9% setup compilation FlagIncludedChunksPlugin 99 | [webpack.Progress] 9% setup compilation SideEffectsFlagPlugin 100 | [webpack.Progress] 9% setup compilation FlagDependencyExportsPlugin 101 | [webpack.Progress] 9% setup compilation FlagDependencyUsagePlugin 102 | [webpack.Progress] 9% setup compilation InnerGraphPlugin 103 | [webpack.Progress] 9% setup compilation MangleExportsPlugin 104 | [webpack.Progress] 9% setup compilation ModuleConcatenationPlugin 105 | [webpack.Progress] 9% setup compilation NoEmitOnErrorsPlugin 106 | [webpack.Progress] 9% setup compilation RealContentHashPlugin 107 | [webpack.Progress] 9% setup compilation WasmFinalizeExportsPlugin 108 | [webpack.Progress] 9% setup compilation NamedModuleIdsPlugin 109 | [webpack.Progress] 9% setup compilation DeterministicChunkIdsPlugin 110 | [webpack.Progress] 9% setup compilation DefinePlugin 111 | [webpack.Progress] 9% setup compilation TerserPlugin 112 | [webpack.Progress] 9% setup compilation TemplatedPathPlugin 113 | [webpack.Progress] 9% setup compilation RecordIdsPlugin 114 | [webpack.Progress] 9% setup compilation WarnCaseSensitiveModulesPlugin 115 | [webpack.Progress] 9% setup compilation IgnoreWarningsPlugin 116 | [webpack.Progress] 9% setup compilation 117 | [webpack.Progress] 10% building 118 | [webpack.Progress] 10% building 0/1 entries 0/0 dependencies 0/0 modules 119 | [webpack.Progress] 10% building import loader ./node_modules/babel-loader/lib/index.js 120 | [webpack.Progress] 10% building 0/16 entries 3/16 dependencies 0/3 modules 121 | [webpack.Progress] 10% building 0/16 entries 17/70 dependencies 0/16 modules 122 | [webpack.Progress] 13% building 1/16 entries 269/382 dependencies 8/150 modules 123 | [webpack.Progress] 13% building import loader ./node_modules/@mdx-js/loader/index.js 124 | [webpack.Progress] 13% building 1/16 entries 311/382 dependencies 8/151 modules 125 | [webpack.Progress] 13% building import loader ./node_modules/@storybook/source-loader/dist/cjs/index.js 126 | [webpack.Progress] 13% building 1/16 entries 311/382 dependencies 8/151 modules 127 | [webpack.Progress] 13% building 1/16 entries 417/700 dependencies 13/187 modules 128 | [webpack.Progress] 13% building 1/16 entries 1100/1280 dependencies 54/342 modules 129 | [webpack.Progress] 13% building 1/16 entries 1568/1800 dependencies 164/400 modules 130 | [webpack.Progress] 13% building 1/16 entries 1897/2000 dependencies 214/445 modules 131 | [webpack.Progress] 13% building import loader ./node_modules/@storybook/builder-webpack5/node_modules/style-loader/dist/cjs.js 132 | [webpack.Progress] 13% building import loader ./node_modules/@storybook/builder-webpack5/node_modules/css-loader/dist/cjs.js 133 | [webpack.Progress] 13% building 1/16 entries 2210/2232 dependencies 277/497 modules 134 | [webpack.Progress] 13% building 1/16 entries 2647/2700 dependencies 343/580 modules 135 | [webpack.Progress] 14% building 1/16 entries 3100/3349 dependencies 411/656 modules 136 | [webpack.Progress] 15% building 1/16 entries 3500/3598 dependencies 472/766 modules 137 | [webpack.Progress] 15% building 1/16 entries 3700/3758 dependencies 507/826 modules 138 | [webpack.Progress] 16% building 1/16 entries 3917/4000 dependencies 554/899 modules 139 | [webpack.Progress] 16% building 1/16 entries 4265/4300 dependencies 607/1018 modules 140 | [webpack.Progress] 17% building 1/16 entries 4545/4600 dependencies 667/1076 modules 141 | [webpack.Progress] 18% building 1/16 entries 4728/4800 dependencies 743/1108 modules 142 | [webpack.Progress] 19% building 1/16 entries 5066/5100 dependencies 821/1186 modules 143 | [webpack.Progress] 20% building 1/16 entries 5437/5500 dependencies 924/1229 modules 144 | [webpack.Progress] 21% building 2/16 entries 5688/5791 dependencies 1021/1238 modules 145 | [webpack.Progress] 21% building 3/16 entries 5688/5791 dependencies 1022/1238 modules 146 | [webpack.Progress] 23% building 4/16 entries 5688/5791 dependencies 1023/1238 modules 147 | [webpack.Progress] 27% building 5/16 entries 5688/5791 dependencies 1024/1238 modules 148 | [webpack.Progress] 30% building 6/16 entries 5688/5791 dependencies 1025/1238 modules 149 | [webpack.Progress] 34% building 7/16 entries 5688/5791 dependencies 1026/1238 modules 150 | [webpack.Progress] 37% building 8/16 entries 5688/5791 dependencies 1027/1238 modules 151 | [webpack.Progress] 40% building 9/16 entries 5688/5791 dependencies 1028/1238 modules 152 | [webpack.Progress] 44% building 10/16 entries 5688/5791 dependencies 1029/1238 modules 153 | [webpack.Progress] 47% building 11/16 entries 5688/5791 dependencies 1030/1238 modules 154 | [webpack.Progress] 47% building 11/16 entries 6085/6200 dependencies 1104/1432 modules 155 | [webpack.Progress] 51% building 12/16 entries 6139/6221 dependencies 1140/1432 modules 156 | [webpack.Progress] 51% building 12/16 entries 6218/6300 dependencies 1189/1450 modules 157 | [webpack.Progress] 54% building 13/16 entries 6684/6689 dependencies 1495/1525 modules 158 | [webpack.Progress] 58% building 14/16 entries 6689/6689 dependencies 1511/1530 modules 159 | [webpack.Progress] 61% building 15/16 entries 6689/6689 dependencies 1511/1530 modules 160 | [webpack.Progress] 65% building 16/16 entries 6695/6695 dependencies 1530/1530 modules 161 | [webpack.Progress] 65% building 162 | [webpack.Progress] 69% building finish 163 | [webpack.Progress] 69% building finish 164 | [webpack.Progress] 70% sealing finish module graph 165 | [webpack.Progress] 70% sealing finish module graph ResolverCachePlugin 166 | [webpack.Progress] 70% sealing finish module graph InferAsyncModulesPlugin 167 | [webpack.Progress] 70% sealing finish module graph FlagDependencyExportsPlugin 168 | [webpack.Progress] 70% sealing finish module graph InnerGraphPlugin 169 | [webpack.Progress] 70% sealing finish module graph WasmFinalizeExportsPlugin 170 | [webpack.Progress] 70% sealing finish module graph 171 | [webpack.Progress] 70% sealing plugins 172 | [webpack.Progress] 70% sealing plugins DocGenPlugin 173 | [webpack.Progress] 70% sealing plugins WarnCaseSensitiveModulesPlugin 174 | [webpack.Progress] 70% sealing plugins 175 | [webpack.Progress] 71% sealing dependencies optimization 176 | [webpack.Progress] 71% sealing dependencies optimization SideEffectsFlagPlugin 177 | [webpack.Progress] 71% sealing dependencies optimization FlagDependencyUsagePlugin 178 | [webpack.Progress] 71% sealing dependencies optimization 179 | [webpack.Progress] 71% sealing after dependencies optimization 180 | [webpack.Progress] 71% sealing after dependencies optimization 181 | [webpack.Progress] 72% sealing chunk graph 182 | [webpack.Progress] 72% sealing chunk graph 183 | [webpack.Progress] 73% sealing after chunk graph 184 | [webpack.Progress] 73% sealing after chunk graph 185 | [webpack.Progress] 73% sealing optimizing 186 | [webpack.Progress] 73% sealing optimizing 187 | [webpack.Progress] 74% sealing module optimization 188 | [webpack.Progress] 74% sealing module optimization 189 | [webpack.Progress] 75% sealing after module optimization 190 | [webpack.Progress] 75% sealing after module optimization 191 | [webpack.Progress] 75% sealing chunk optimization 192 | [webpack.Progress] 75% sealing chunk optimization EnsureChunkConditionsPlugin 193 | [webpack.Progress] 75% sealing chunk optimization RemoveEmptyChunksPlugin 194 | [webpack.Progress] 75% sealing chunk optimization MergeDuplicateChunksPlugin 195 | [webpack.Progress] 75% sealing chunk optimization SplitChunksPlugin 196 | [webpack.Progress] 75% sealing chunk optimization RemoveEmptyChunksPlugin 197 | [webpack.Progress] 75% sealing chunk optimization 198 | [webpack.Progress] 76% sealing after chunk optimization 199 | [webpack.Progress] 76% sealing after chunk optimization 200 | [webpack.Progress] 77% sealing module and chunk tree optimization 201 | [webpack.Progress] 77% sealing module and chunk tree optimization PersistentChildCompilerSingletonPlugin 202 | [webpack.Progress] 77% sealing module and chunk tree optimization 203 | [webpack.Progress] 77% sealing after module and chunk tree optimization 204 | [webpack.Progress] 77% sealing after module and chunk tree optimization 205 | [webpack.Progress] 78% sealing chunk modules optimization 206 | [webpack.Progress] 78% sealing chunk modules optimization ModuleConcatenationPlugin 207 | [webpack.Progress] 78% sealing chunk modules optimization 208 | [webpack.Progress] 78% sealing after chunk modules optimization 209 | [webpack.Progress] 78% sealing after chunk modules optimization 210 | [webpack.Progress] 79% sealing module reviving 211 | [webpack.Progress] 79% sealing module reviving RecordIdsPlugin 212 | [webpack.Progress] 79% sealing module reviving 213 | [webpack.Progress] 80% sealing before module ids 214 | [webpack.Progress] 80% sealing before module ids 215 | [webpack.Progress] 80% sealing module ids 216 | [webpack.Progress] 80% sealing module ids NamedModuleIdsPlugin 217 | [webpack.Progress] 80% sealing module ids 218 | [webpack.Progress] 81% sealing module id optimization 219 | [webpack.Progress] 81% sealing module id optimization 220 | [webpack.Progress] 82% sealing module id optimization 221 | [webpack.Progress] 82% sealing module id optimization 222 | [webpack.Progress] 82% sealing chunk reviving 223 | [webpack.Progress] 82% sealing chunk reviving RecordIdsPlugin 224 | [webpack.Progress] 82% sealing chunk reviving 225 | [webpack.Progress] 83% sealing before chunk ids 226 | [webpack.Progress] 83% sealing before chunk ids 227 | [webpack.Progress] 84% sealing chunk ids 228 | [webpack.Progress] 84% sealing chunk ids DeterministicChunkIdsPlugin 229 | [webpack.Progress] 84% sealing chunk ids 230 | [webpack.Progress] 84% sealing chunk id optimization 231 | [webpack.Progress] 84% sealing chunk id optimization FlagIncludedChunksPlugin 232 | [webpack.Progress] 84% sealing chunk id optimization 233 | [webpack.Progress] 85% sealing after chunk id optimization 234 | [webpack.Progress] 85% sealing after chunk id optimization 235 | [webpack.Progress] 86% sealing record modules 236 | [webpack.Progress] 86% sealing record modules RecordIdsPlugin 237 | [webpack.Progress] 86% sealing record modules 238 | [webpack.Progress] 86% sealing record chunks 239 | [webpack.Progress] 86% sealing record chunks RecordIdsPlugin 240 | [webpack.Progress] 86% sealing record chunks 241 | [webpack.Progress] 87% sealing module hashing 242 | [webpack.Progress] 87% sealing module hashing 243 | [webpack.Progress] 87% sealing code generation 244 | [webpack.Progress] 87% sealing code generation 245 | [webpack.Progress] 88% sealing runtime requirements 246 | [webpack.Progress] 88% sealing runtime requirements 247 | [webpack.Progress] 89% sealing hashing 248 | [webpack.Progress] 89% sealing hashing 249 | [webpack.Progress] 89% sealing after hashing 250 | [webpack.Progress] 89% sealing after hashing 251 | [webpack.Progress] 90% sealing record hash 252 | [webpack.Progress] 90% sealing record hash 253 | [webpack.Progress] 91% sealing module assets processing 254 | [webpack.Progress] 91% sealing module assets processing 255 | [webpack.Progress] 91% sealing chunk assets processing 256 | [webpack.Progress] 91% sealing chunk assets processing 257 | [webpack.Progress] 92% sealing asset processing 258 | [webpack.Progress] 92% sealing asset processing PersistentChildCompilerSingletonPlugin 259 | [webpack.Progress] 92% sealing asset processing TerserPlugin 260 | info => Manager built (30 s) 261 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 262 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 263 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin main.045da604.iframe.bundle.js generate SourceMap 264 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin main.045da604.iframe.bundle.js generated SourceMap 265 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin runtime~main.3e50cb91.iframe.bundle.js generate SourceMap 266 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin runtime~main.3e50cb91.iframe.bundle.js generated SourceMap 267 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 701.f4bbd141.iframe.bundle.js generate SourceMap 268 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 701.f4bbd141.iframe.bundle.js generated SourceMap 269 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 278.8d811030.iframe.bundle.js generate SourceMap 270 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 278.8d811030.iframe.bundle.js generated SourceMap 271 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 574.65f19919.iframe.bundle.js generate SourceMap 272 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 574.65f19919.iframe.bundle.js generated SourceMap 273 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 881.5e1f8feb.iframe.bundle.js generate SourceMap 274 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 881.5e1f8feb.iframe.bundle.js generated SourceMap 275 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 459.a2401911.iframe.bundle.js generate SourceMap 276 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 459.a2401911.iframe.bundle.js generated SourceMap 277 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 331.2be2e5c8.iframe.bundle.js generate SourceMap 278 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 331.2be2e5c8.iframe.bundle.js generated SourceMap 279 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 83.fe317237.iframe.bundle.js generate SourceMap 280 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 83.fe317237.iframe.bundle.js generated SourceMap 281 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin resolve sources 282 | [webpack.Progress] 92% sealing asset processing SourceMapDevToolPlugin 283 | [webpack.Progress] 92% sealing asset processing HtmlWebpackPlugin 284 | [webpack.Progress] 92% sealing asset processing HtmlWebpackPlugin 285 | [webpack.Progress] 92% sealing asset processing HtmlWebpackPlugin resolve sources 286 | [webpack.Progress] 92% sealing asset processing HtmlWebpackPlugin 287 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 288 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 289 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin main.a32f75df.iframe.bundle.js generate SourceMap 290 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin main.a32f75df.iframe.bundle.js generated SourceMap 291 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin runtime~main.a2777503.iframe.bundle.js generate SourceMap 292 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin runtime~main.a2777503.iframe.bundle.js generated SourceMap 293 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 701.f592b5ab.iframe.bundle.js generate SourceMap 294 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 701.f592b5ab.iframe.bundle.js generated SourceMap 295 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 574.12afad18.iframe.bundle.js generate SourceMap 296 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 574.12afad18.iframe.bundle.js generated SourceMap 297 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 881.1c506def.iframe.bundle.js generate SourceMap 298 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 881.1c506def.iframe.bundle.js generated SourceMap 299 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 459.d72ec5aa.iframe.bundle.js generate SourceMap 300 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 459.d72ec5aa.iframe.bundle.js generated SourceMap 301 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 278.07cf644e.iframe.bundle.js generate SourceMap 302 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 278.07cf644e.iframe.bundle.js generated SourceMap 303 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 331.467c5b7b.iframe.bundle.js generate SourceMap 304 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 331.467c5b7b.iframe.bundle.js generated SourceMap 305 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 83.975199be.iframe.bundle.js generate SourceMap 306 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 83.975199be.iframe.bundle.js generated SourceMap 307 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin resolve sources 308 | [webpack.Progress] 92% sealing asset processing RealContentHashPlugin 309 | [webpack.Progress] 92% sealing asset processing 310 | [webpack.Progress] 93% sealing after asset optimization 311 | [webpack.Progress] 93% sealing after asset optimization 312 | [webpack.Progress] 93% sealing recording 313 | [webpack.Progress] 93% sealing recording 314 | [webpack.Progress] 94% sealing after seal 315 | [webpack.Progress] 94% sealing after seal 316 | [webpack.Progress] 95% emitting emit 317 | [webpack.Progress] 95% emitting emit 318 | [webpack.Progress] 98% emitting after emit 319 | [webpack.Progress] 98% emitting after emit SizeLimitsPlugin 320 | [webpack.Progress] 98% emitting after emit 321 | [webpack.Progress] 99% done plugins 322 | [webpack.Progress] 99% done plugins CaseSensitivePathsPlugin 323 | [webpack.Progress] 99% done plugins 324 | [webpack.Progress] 99% 325 | 326 | [webpack.Progress] 99% cache store build dependencies 327 | [webpack.Progress] 99% cache store build dependencies 328 | [webpack.Progress] 99% cache begin idle 329 | [webpack.Progress] 99% cache begin idle 330 | [webpack.Progress] 100% 331 | 332 | info => Preview built (31 s) 333 | WARN asset size limit: The following asset(s) exceed the recommended size limit (244 KiB). 334 | WARN This can impact web performance. 335 | WARN Assets: 336 | WARN 83.975199be.iframe.bundle.js (2.66 MiB) 337 | WARN entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance. 338 | WARN Entrypoints: 339 | WARN main (2.7 MiB) 340 | WARN runtime~main.a2777503.iframe.bundle.js 341 | WARN 83.975199be.iframe.bundle.js 342 | WARN main.a32f75df.iframe.bundle.js 343 | WARN 344 | [webpack.Progress] 99% cache shutdown 345 | [webpack.Progress] 99% cache shutdown 346 | [webpack.Progress] 100% 347 | 348 | info => Output directory: /tmp/chromatic--2754-sL4ajaHKAg0O 349 | -------------------------------------------------------------------------------- /apps/frontend/codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: 'https://strapi.elektronplus.pl/graphql' 3 | documents: '**/*.graphql' 4 | generates: 5 | generated/graphql.tsx: 6 | plugins: 7 | - 'typescript' 8 | - 'typescript-operations' 9 | - 'typescript-react-apollo' 10 | -------------------------------------------------------------------------------- /apps/frontend/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "@emotion/react"; 2 | import Link from "next/link"; 3 | 4 | type ButtonProps = { 5 | children: React.ReactNode; 6 | href: string; 7 | } 8 | 9 | export const Button = ({children, href}: ButtonProps) => { 10 | const theme = useTheme(); 11 | 12 | return 13 | 14 |
15 | {children} 16 |
17 |
18 | 19 | } -------------------------------------------------------------------------------- /apps/frontend/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { css, useTheme } from '@emotion/react'; 2 | import Footer from 'components/footer/Footer'; 3 | import Navigation from 'components/navigation/Navigation'; 4 | import { StrapiImage } from 'components/StrapiImage'; 5 | import { GlobalContext } from 'pages/_app'; 6 | import { useContext } from 'react'; 7 | 8 | export default function Layout({ children }: { children: React.ReactNode }) { 9 | const context = useContext(GlobalContext); 10 | const theme = useTheme(); 11 | 12 | return ( 13 |
14 |
23 | 32 |
33 | 34 |
35 |
{children}
36 |
37 |
38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /apps/frontend/components/Search.tsx: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | import { searchClient } from 'lib/instantSearch'; 3 | import Link from 'next/link'; 4 | import { 5 | Highlight, 6 | Hits, 7 | InstantSearch, 8 | SearchBox, 9 | useInstantSearch, 10 | } from 'react-instantsearch-hooks-web'; 11 | import { getArticlePathBySlug } from 'services/utils'; 12 | 13 | function Hit({ hit }) { 14 | return ( 15 | <> 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | 25 | export default function Search() { 26 | return ( 27 |
33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | ); 41 | } 42 | 43 | function EmptyQueryBoundary({ children, fallback }) { 44 | const { indexUiState } = useInstantSearch(); 45 | 46 | if (!indexUiState.query) { 47 | return fallback; 48 | } 49 | 50 | return children; 51 | } 52 | -------------------------------------------------------------------------------- /apps/frontend/components/StrapiImage.tsx: -------------------------------------------------------------------------------- 1 | import NextImage, { ImageProps } from 'next/image'; 2 | import { PartialDeep } from 'type-fest'; 3 | import { UploadFileEntityResponse } from '../generated/graphql'; 4 | import { getStrapiMedia } from '../services/media'; 5 | 6 | export function StrapiImage({ 7 | image, 8 | imageProps, 9 | }: { 10 | image: PartialDeep; 11 | imageProps?: PartialDeep; 12 | }) { 13 | if (image.data === null) { 14 | console.debug( 15 | 'No image data for item. This is correct for optional fields!' 16 | ); 17 | 18 | return null; 19 | } 20 | 21 | const { alternativeText, width, height, placeholder } = image.data.attributes; 22 | 23 | const defaultImageProps: PartialDeep = { 24 | layout: 'responsive', 25 | objectFit: 'contain', 26 | }; 27 | 28 | const imagePropsWithDefaults = { 29 | ...defaultImageProps, 30 | ...imageProps, 31 | }; 32 | 33 | const blurProps = placeholder 34 | ? { placeholder: 'blur', blurDataURL: placeholder } 35 | : null; 36 | 37 | // according to the next.js docs, layout fill shouldn't have size props 38 | const sizeProps = 39 | imagePropsWithDefaults.layout === 'fill' ? null : { width, height }; 40 | 41 | return ( 42 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /apps/frontend/components/StrapiImageFuture.tsx: -------------------------------------------------------------------------------- 1 | import { getStrapiMedia } from '../services/media'; 2 | import NextImage, { ImageProps } from 'next/future/image'; 3 | import { UploadFileEntityResponse } from '../generated/graphql'; 4 | import { PartialDeep } from 'type-fest'; 5 | 6 | export function StrapiImageFuture({ 7 | image, 8 | imageProps, 9 | }: { 10 | image: PartialDeep; 11 | imageProps?: PartialDeep; 12 | }) { 13 | if (image.data === null) { 14 | console.debug( 15 | 'No image data for item. This is correct for optional fields!' 16 | ); 17 | 18 | return null; 19 | } 20 | 21 | const { alternativeText, width, height, placeholder } = image.data.attributes; 22 | 23 | const imagePropsWithDefaults = { 24 | ...imageProps, 25 | }; 26 | 27 | const blurProps = placeholder 28 | ? { placeholder: 'blur', blurDataURL: placeholder } 29 | : null; 30 | 31 | return ( 32 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /apps/frontend/components/article/Category.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export function ArticleCategory({ 4 | path, 5 | name, 6 | }: { 7 | path: string; 8 | name: string; 9 | }) { 10 | return ( 11 | 12 | 13 | {name} 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/frontend/components/article/Content.tsx: -------------------------------------------------------------------------------- 1 | import { css, useTheme } from '@emotion/react'; 2 | import { ArticleReadMore } from 'components/article/ReadMore'; 3 | import DOMPurify from 'isomorphic-dompurify'; 4 | import { getArticlePathBySlug } from 'services/utils'; 5 | 6 | export function ArticleContent({ 7 | content, 8 | slug, 9 | isSingleArticlePage, 10 | readMore, 11 | cardMaxCharacters, 12 | }: { 13 | content: string; 14 | slug: string; 15 | readMore: string; 16 | isSingleArticlePage: boolean; 17 | cardMaxCharacters: number; 18 | }) { 19 | function getTrimmedContent(content: string) { 20 | return content.substring(0, cardMaxCharacters) + '...'; 21 | } 22 | 23 | const article = { 24 | content, 25 | isTrimmed: false, 26 | }; 27 | 28 | if (!isSingleArticlePage) { 29 | if (article.content.length > cardMaxCharacters) { 30 | article.content = getTrimmedContent(article.content); 31 | article.isTrimmed = true; 32 | } 33 | } 34 | 35 | // disable styling on the preview of the article 36 | const forbiddenAttrs = isSingleArticlePage ? [] : ['style']; 37 | const forbiddenTags = isSingleArticlePage ? [] : ['strong']; 38 | 39 | const theme = useTheme(); 40 | 41 | return ( 42 | <> 43 |
52 | {article.isTrimmed && ( 53 | 54 | )} 55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /apps/frontend/components/article/Grid.tsx: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | import { H, Level } from 'react-accessible-headings'; 3 | import Masonry from 'react-masonry-css'; 4 | import { 5 | GetArticlesQuery, 6 | GetCategoriesBySlugQuery, 7 | } from '../../generated/graphql'; 8 | import styles from './grid.module.css'; 9 | import Article from './Index'; 10 | 11 | type Articles = 12 | | GetArticlesQuery['articles'] 13 | | GetCategoriesBySlugQuery['categories']['data']['0']['attributes']['articles']; 14 | 15 | function ArticlesGrid({ 16 | articles, 17 | sectionHeader, 18 | readMore, 19 | cardMaxCharacters, 20 | }: { 21 | articles: Articles; 22 | sectionHeader: string; 23 | readMore: string; 24 | cardMaxCharacters: number; 25 | }) { 26 | return ( 27 |
28 | 29 |
37 | {sectionHeader} 38 |
39 |
    44 | 52 | {articles.data.map((article, index) => { 53 | // optimizing largest contentful paint https://web.dev/optimize-lcp/ 54 | const isPriority = index <= 1; 55 | 56 | return ( 57 |
    65 | ); 66 | })} 67 | 68 |
69 |
70 |
71 | ); 72 | } 73 | 74 | export default ArticlesGrid; 75 | -------------------------------------------------------------------------------- /apps/frontend/components/article/Index.tsx: -------------------------------------------------------------------------------- 1 | import { ArticleEntity } from 'generated/graphql'; 2 | import { StrapiImage } from '../StrapiImage'; 3 | import { css } from '@emotion/react'; 4 | import { getCategoryPathBySlug } from 'services/utils'; 5 | import { EntryAuthor } from 'components/entry/Author'; 6 | import { ArticleTitle } from 'components/article/Title'; 7 | import { EntryDetails } from 'components/entry/Details'; 8 | import { ArticleContent } from 'components/article/Content'; 9 | import { Level } from 'react-accessible-headings'; 10 | import { getStrapiMedia } from 'services/media'; 11 | import styled from '@emotion/styled'; 12 | import { PartialDeep } from 'type-fest'; 13 | 14 | function Article({ 15 | article, 16 | isSingleArticlePage, 17 | readMore, 18 | cardMaxCharacters = 500, 19 | isPriority = false, 20 | }: { 21 | article: PartialDeep; 22 | isSingleArticlePage: boolean; 23 | readMore?: string; 24 | cardMaxCharacters?: number; 25 | isPriority?: boolean; 26 | }) { 27 | const publishedAt = article.attributes.publishedAt; 28 | 29 | const categoryPath = getCategoryPathBySlug( 30 | article.attributes.category?.data?.attributes?.slug 31 | ); 32 | const categoryName = article.attributes.category?.data?.attributes?.name; 33 | 34 | const authorName = article.attributes.author?.data?.attributes?.name; 35 | const authorDescription = 36 | article.attributes.author?.data?.attributes?.description; 37 | const authorPictureUrl = getStrapiMedia( 38 | article.attributes.author?.data?.attributes?.picture 39 | ); 40 | 41 | const { title, slug, content } = article.attributes; 42 | 43 | const CenterIfSingleArticlePage = isSingleArticlePage 44 | ? styled.div( 45 | css` 46 | text-align: center; 47 | margin: auto; 48 | width: 75%; 49 | ` 50 | ) 51 | : styled.div(); 52 | const CoverImageIfNotSingleArticlePage = isSingleArticlePage 53 | ? styled.div() 54 | : styled.div` 55 | position: relative; 56 | height: 340px; 57 | `; 58 | 59 | const ShadowAndBorderIfNotSingleArticlePage = isSingleArticlePage 60 | ? styled.div` 61 | width: 100%; 62 | ` 63 | : styled.div` 64 | background-color: white; 65 | border: 1px solid #e2e8f0; 66 | width: 100%; 67 | box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 68 | 0 4px 6px -2px rgba(0, 0, 0, 0.05); 69 | border-radius: 4px; 70 | `; 71 | 72 | const NarrowWrapperIfSingleArticlePage = isSingleArticlePage 73 | ? styled.div(`max-width: 900px; margin: auto;`) 74 | : styled.div(); 75 | 76 | return ( 77 |
  • 83 |
    84 | 85 |
    91 | 92 | {article.attributes.image.data != null && ( 93 | 94 | 113 | 114 | )} 115 |
    120 | 121 | 122 | 127 | 132 | 133 | 134 | 141 | {isSingleArticlePage && authorName && ( 142 | 147 | )} 148 | 149 |
    150 |
    151 |
    152 |
    153 |
    154 |
  • 155 | ); 156 | } 157 | 158 | export function BulletPoint() { 159 | return  • ; 160 | } 161 | 162 | export default Article; 163 | -------------------------------------------------------------------------------- /apps/frontend/components/article/ReadMore.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { css, useTheme } from '@emotion/react'; 3 | 4 | export function ArticleReadMore({ 5 | text, 6 | path, 7 | }: { 8 | text: string; 9 | path: string; 10 | }) { 11 | const theme = useTheme(); 12 | 13 | return ( 14 |
    21 | 22 | 25 | {text} 26 | 27 | 28 |
    29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /apps/frontend/components/article/Title.tsx: -------------------------------------------------------------------------------- 1 | import { getArticlePathBySlug } from '../../services/utils'; 2 | import Link from 'next/link'; 3 | import { css } from '@emotion/react'; 4 | import { H } from 'react-accessible-headings'; 5 | 6 | export function ArticleTitle({ title, slug, isSingleArticlePage }) { 7 | if (isSingleArticlePage) { 8 | return ( 9 | 16 | {title} 17 | 18 | ); 19 | } else { 20 | return ( 21 | 22 | 23 | 29 | {title} 30 | 31 | 32 | 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /apps/frontend/components/article/grid.module.css: -------------------------------------------------------------------------------- 1 | .articlesGrid { 2 | display: flex; 3 | width: auto; 4 | } 5 | 6 | .articlesColumn { 7 | background-clip: padding-box; 8 | } 9 | -------------------------------------------------------------------------------- /apps/frontend/components/entry/Author.tsx: -------------------------------------------------------------------------------- 1 | import { css, useTheme } from '@emotion/react'; 2 | import { H, Level } from 'react-accessible-headings'; 3 | import Avatar from 'react-avatar'; 4 | 5 | function AuthorDescription({ content }: { content: string }) { 6 | const theme = useTheme(); 7 | 8 | if (content !== null) { 9 | return ( 10 | 18 | {content} 19 | 20 | ); 21 | } 22 | 23 | return null; 24 | } 25 | 26 | function AuthorName({ content }: { content: string }) { 27 | if (content === null) { 28 | return null; 29 | } 30 | 31 | return ( 32 | 35 | {content} 36 | 37 | ); 38 | } 39 | 40 | export function EntryAuthor({ 41 | authorName, 42 | authorDescription, 43 | authorPictureUrl, 44 | }: { 45 | authorName: string; 46 | authorDescription?: string; 47 | authorPictureUrl?: string; 48 | }) { 49 | return ( 50 |
    61 | 62 |
    63 | {authorPictureUrl !== null && ( 64 | 72 | )} 73 |
    74 |
    75 | 76 | 77 |
    78 |
    79 |
    80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /apps/frontend/components/entry/Content.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from '@emotion/react'; 2 | import { ReactNode } from 'react'; 3 | 4 | type ContentProps = { 5 | children: ReactNode; 6 | }; 7 | 8 | export const EntryContent = ({ children }: ContentProps) => { 9 | const theme = useTheme(); 10 | 11 | return ( 12 |
    {children}
    13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /apps/frontend/components/entry/Details.tsx: -------------------------------------------------------------------------------- 1 | import { css, useTheme } from '@emotion/react'; 2 | import { ArticleCategory } from '../article/Category'; 3 | 4 | function EntryDate({ publishedAt }) { 5 | const date = new Date(publishedAt); 6 | 7 | return ( 8 | 15 | ); 16 | } 17 | 18 | 19 | export function EntryDetails({ 20 | publishedAt, 21 | categoryName = null, 22 | categoryPath = null, 23 | }) { 24 | const theme = useTheme(); 25 | return ( 26 | 36 | 37 | {categoryName !== null && categoryPath !== null && ( 38 | <> 39 |  •  40 | 41 | 42 | )} 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /apps/frontend/components/entry/Title.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "@emotion/react"; 2 | import { H } from "react-accessible-headings"; 3 | 4 | type TitleProps = { 5 | title: string; 6 | } 7 | 8 | export const EntryTitle = ({ title }: TitleProps) => { 9 | return {title} 10 | } -------------------------------------------------------------------------------- /apps/frontend/components/footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | import { PoweredByVercel } from 'components/footer/PoweredByVercel'; 3 | import { SocialMediaIcon } from 'components/footer/SocialMediaIcon'; 4 | import { TemplateAuthors } from 'components/footer/TemplateAuthors'; 5 | import DOMPurify from 'isomorphic-dompurify'; 6 | import Link from 'next/link'; 7 | import { GlobalContext } from 'pages/_app'; 8 | import { useContext } from 'react'; 9 | import { H, Level } from 'react-accessible-headings'; 10 | import { MdMail } from 'react-icons/md'; 11 | 12 | export default function Footer() { 13 | const context = useContext(GlobalContext); 14 | 15 | return ( 16 |
    17 | 18 |
    21 |
    31 |
    32 | 33 |
    34 | 35 | 36 |
    44 |
    45 | 46 |
    47 |
    48 | 49 |
    50 |
    51 | 52 |

    53 | 54 |

    55 |
    56 | 57 |
    58 |
    59 |
    60 |
    61 |
    62 | ); 63 | } 64 | 65 | function ListHeader({ children }: { children: React.ReactNode }) { 66 | return ( 67 | 73 | {children} 74 | 75 | ); 76 | } 77 | 78 | function SocialButtonsList() { 79 | const context = useContext(GlobalContext); 80 | 81 | const socialMedias = context.footerContent.socialMedias.data; 82 | const email = context.footerContent.footer.data.attributes.email; 83 | 84 | return ( 85 |
      86 | {socialMedias.map((socialMedia) => { 87 | const { iconSlug, showInFooter, link } = socialMedia.attributes; 88 | 89 | if (!showInFooter) { 90 | return null; 91 | } 92 | 93 | return ( 94 |
    • 95 | 96 | {iconSlug} 102 | 103 |
    • 104 | ); 105 | })} 106 |
    • 107 | 108 | 115 | 116 |
    • 117 |
    118 | ); 119 | } 120 | 121 | function HorizontalLine() { 122 | return
    ; 123 | } 124 | 125 | function Copyright() { 126 | const context = useContext(GlobalContext); 127 | 128 | const copyright = context.footerContent.footer.data.attributes.copyright; 129 | 130 | return ( 131 |
    139 | ); 140 | } 141 | 142 | function Columns() { 143 | const context = useContext(GlobalContext); 144 | const footerLinks = context.footerLinks.renderNavigation; 145 | 146 | return ( 147 |
    156 | {footerLinks.map((column) => { 157 | return ( 158 |
    159 | {column.title} 160 |
      161 | {column.items.map((item) => ( 162 | 163 | ))} 164 |
    165 |
    166 | ); 167 | })} 168 |
    169 | ); 170 | } 171 | 172 | function ColumnLink({ link }) { 173 | return ( 174 |
  • 178 | 179 | {link.title} 180 | 181 |
  • 182 | ); 183 | } 184 | -------------------------------------------------------------------------------- /apps/frontend/components/footer/PoweredByVercel.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { GlobalContext } from 'pages/_app'; 3 | import { useContext } from 'react'; 4 | 5 | export function PoweredByVercel() { 6 | const context = useContext(GlobalContext); 7 | 8 | const showVercelBadge = 9 | context.footerContent.footer.data.attributes.showVercelBadge; 10 | 11 | if (!showVercelBadge) { 12 | return null; 13 | } 14 | 15 | return ( 16 | 20 | 21 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /apps/frontend/components/footer/SocialMediaIcon.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export function SocialMediaIcon({ 4 | children, 5 | label, 6 | href, 7 | }: { 8 | children: React.ReactNode; 9 | label: string; 10 | href: string; 11 | }) { 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/frontend/components/footer/TemplateAuthors.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export function TemplateAuthors() { 4 | const authors = [ 5 | { name: 'Jan Szymański,', link: 'https://github.com/konhi' }, 6 | { name: 'Krystian Wybranowski,', link: 'https://github.com/wybran' }, 7 | { name: 'Bartosz Maciejewski +', link: 'https://github.com/bkmac511'}, 8 | { 9 | name: 'Contributors = ❤✏', 10 | link: 'https://github.com/ElektronPlus/school-website/graphs/contributors', 11 | }, 12 | ]; 13 | 14 | return ( 15 | <> 16 | {authors.map((author) => { 17 | return ( 18 | 19 | {author.name}  20 | 21 | ); 22 | })} 23 | 24 | ); 25 | } 26 | // 27 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/Alert.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from '@emotion/react'; 2 | import link from 'next/link'; 3 | import Link from 'next/link'; 4 | import { GlobalContext } from 'pages/_app'; 5 | import { useContext } from 'react'; 6 | 7 | function Content({ message, link }: { message: string; link: string | null }) { 8 | if (link) { 9 | return ( 10 | 11 | {message} ➞ 12 | 13 | ); 14 | } 15 | 16 | return <>{message}; 17 | } 18 | 19 | export function Alert() { 20 | const context = useContext(GlobalContext); 21 | const theme = useTheme(); 22 | 23 | const alert = context.alert.alert.data.attributes; 24 | 25 | if (!alert.isVisible) { 26 | return null; 27 | } 28 | 29 | return ( 30 |
    38 | 39 |
    40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/Header.tsx: -------------------------------------------------------------------------------- 1 | import { StrapiImageFuture } from 'components/StrapiImageFuture'; 2 | import Link from 'next/link'; 3 | import { GlobalContext } from 'pages/_app'; 4 | import { useContext } from 'react'; 5 | import { H } from 'react-accessible-headings'; 6 | 7 | export function Header() { 8 | const context = useContext(GlobalContext); 9 | 10 | const logo = context.global.global.data.attributes.logo; 11 | 12 | return ( 13 |
    14 | 15 | 16 | 24 | 32 | 33 | 34 | 35 |
    36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/Navigation.tsx: -------------------------------------------------------------------------------- 1 | import { css, useTheme } from '@emotion/react'; 2 | import styled from '@emotion/styled'; 3 | import { Alert } from 'components/navigation/Alert'; 4 | import { DesktopMenu } from 'components/navigation/menu/DesktopMenu'; 5 | import { MobileMenu } from 'components/navigation/menu/MobileMenu'; 6 | import { DesktopContainer, TabletAndBelow } from 'components/utils/responsive'; 7 | import { useEffect, useState } from 'react'; 8 | 9 | export default function Navigation() { 10 | const theme = useTheme(); 11 | 12 | const [isWindowScrolled, setIsWindowScrolled] = useState(false); 13 | 14 | function handleWindowScroll() { 15 | if (window.scrollY > 0) { 16 | setIsWindowScrolled(true); 17 | } else { 18 | setIsWindowScrolled(false); 19 | } 20 | } 21 | 22 | useEffect(() => { 23 | window.addEventListener('scroll', handleWindowScroll); 24 | }); 25 | 26 | const scrolledNav = css`background-color: ${theme.color.background.transculent.hexa()}; backdrop-filter: blur(48px) saturate(5);`; 27 | 28 | return ( 29 |
    32 | 46 |
    47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/DesktopMenu.tsx: -------------------------------------------------------------------------------- 1 | import { Header } from 'components/navigation/Header'; 2 | import { ButtonList } from 'components/navigation/menu/desktop/ButtonList'; 3 | 4 | export function DesktopMenu() { 5 | return ( 6 |
    16 |
    17 | 18 |
    19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/MobileMenu.tsx: -------------------------------------------------------------------------------- 1 | import { Popover } from '@headlessui/react'; 2 | import { AnimatedMenuButton } from 'components/utils/symbols/AnimatedMenuButton'; 3 | import { Level } from 'react-accessible-headings'; 4 | import { Header } from '../Header'; 5 | import { PopoverPanel } from './mobile/PopoverPanel'; 6 | 7 | export function MobileMenu() { 8 | return ( 9 | 10 | {({ open }) => ( 11 |
    18 |
    19 |
    20 | 21 | 22 | 23 |
    24 |
    25 | 26 |
    27 | 28 |
    29 |
    30 |
    31 |
    32 | )} 33 |
    34 | ); 35 | } 36 | 37 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/desktop/ButtonList.tsx: -------------------------------------------------------------------------------- 1 | import { GlobalContext } from 'pages/_app'; 2 | import { useContext } from 'react'; 3 | import { MenuLink } from 'components/navigation/menu/desktop/MenuLink'; 4 | import { Popover } from '@headlessui/react'; 5 | 6 | export function ButtonList() { 7 | const context = useContext(GlobalContext); 8 | 9 | const menuLinks = context.menuLinks.renderNavigation; 10 | 11 | return ( 12 | 21 | {menuLinks.map((item) => ( 22 | 23 | ))} 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/desktop/MenuLink.tsx: -------------------------------------------------------------------------------- 1 | import { Popover } from '@headlessui/react'; 2 | import { NavigationItem } from 'generated/graphql'; 3 | import Link from 'next/link'; 4 | import { PartialDeep } from 'type-fest'; 5 | import { NavigationButton } from 'components/navigation/menu/desktop/NavigationButton'; 6 | import { DesktopMenuPopoverPanel } from 'components/navigation/menu/desktop/popover/PopoverPanel'; 7 | import { useEffect, useRef, useState } from 'react'; 8 | 9 | export function MenuLink({ item }: { item: PartialDeep }) { 10 | const [hovered, setHovered] = useState({ 11 | popover: false, 12 | link: false, 13 | }); 14 | const [open, setOpen] = useState(false); 15 | 16 | const buttonRef = useRef(null); 17 | 18 | function handleHoverState(element: 'popover' | 'link', isHovered: boolean) { 19 | setHovered({ 20 | ...hovered, 21 | [element]: isHovered, 22 | }); 23 | } 24 | 25 | function toggleMenu() { 26 | const shouldBeOpenned = hovered.popover || hovered.link; 27 | 28 | if ((shouldBeOpenned && !open) || (!shouldBeOpenned && open)) { 29 | setOpen(!open); 30 | buttonRef?.current?.click(); 31 | } 32 | } 33 | 34 | useEffect(() => { 35 | toggleMenu(); 36 | }); 37 | 38 | return ( 39 |
  • 40 | 41 | 42 | {({ open }) => ( 43 | <> 44 | 50 | {open && ( 51 | 56 | )} 57 | 58 | )} 59 | 60 | 61 |
  • 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/desktop/NavigationButton.tsx: -------------------------------------------------------------------------------- 1 | import { Popover } from '@headlessui/react'; 2 | import { NavigationLink } from 'components/utils/NavigationLink'; 3 | import { AnimatedExpandButton } from 'components/utils/symbols/AnimatedExpandButton'; 4 | import { NavigationItem } from 'generated/graphql'; 5 | import { forwardRef } from 'react'; 6 | import { PartialDeep } from 'type-fest'; 7 | 8 | export const NavigationButton = forwardRef( 9 | ( 10 | { 11 | item, 12 | open, 13 | handleHoverState, 14 | }: { 15 | item: PartialDeep; 16 | open: boolean; 17 | handleHoverState: ( 18 | element: 'popover' | 'link', 19 | isHovered: boolean 20 | ) => void; 21 | }, 22 | ref: React.Ref 23 | ) => { 24 | return ( 25 | handleHoverState('link', false)} 28 | onMouseEnter={() => handleHoverState('link', true)} 29 | css={{ 30 | display: 'flex', 31 | alignItems: 'center', 32 | justifyContent: 'space-between', 33 | width: '100%', 34 | gap: '4px', 35 | '&:focus': { 36 | outline: 'none', 37 | backgroundColor: "#0000000d", 38 | borderRadius: "8px" 39 | } 40 | }} 41 | > 42 | 43 | 44 | 45 | ); 46 | } 47 | ); 48 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/desktop/popover/PopoverLinks.tsx: -------------------------------------------------------------------------------- 1 | import { NavigationLink } from 'components/utils/NavigationLink'; 2 | import { NavigationItem } from 'generated/graphql'; 3 | import { PartialDeep } from 'type-fest'; 4 | 5 | export function PopoverLinks({ items }: { items: PartialDeep }) { 6 | return ( 7 |
      16 | {items.map((item) => ( 17 |
    • {}} 21 | > 22 | 23 |
    • 24 | ))} 25 |
    26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/desktop/popover/PopoverPanel.tsx: -------------------------------------------------------------------------------- 1 | import { Popover } from '@headlessui/react'; 2 | import { NavigationItem } from 'generated/graphql'; 3 | import { PartialDeep } from 'type-fest'; 4 | import { SeeMore } from 'components/navigation/menu/shared/SeeMore'; 5 | import { PopoverLinks } from 'components/navigation/menu/desktop/popover/PopoverLinks'; 6 | import { useTheme } from '@emotion/react'; 7 | 8 | export function DesktopMenuPopoverPanel({ 9 | items, 10 | parent, 11 | handleHoverState, 12 | }: { 13 | items: PartialDeep; 14 | parent: PartialDeep; 15 | handleHoverState: (element: 'popover' | 'link', isHovered: boolean) => void; 16 | }) { 17 | const theme = useTheme(); 18 | 19 | return ( 20 | handleHoverState('popover', true)} 23 | onMouseLeave={() => handleHoverState('popover', false)} 24 | css={{ 25 | inset: '72px 0 auto 0px', 26 | position: 'absolute', 27 | margin: 'auto', 28 | width: '60%', 29 | color: theme.color.text.secondary.hexa(), 30 | borderRadius: '8px', 31 | border: `1px solid ${theme.color.border.withShadow.hexa()}`, 32 | backgroundColor: '#ffffff', 33 | boxShadow: `1px 1px 20px 8px ${theme.color.shadow.hexa()}`, 34 | }} 35 | > 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/mobile/ChildLink.tsx: -------------------------------------------------------------------------------- 1 | import { NavigationLink } from 'components/utils/NavigationLink'; 2 | import { NavigationItem } from 'generated/graphql'; 3 | import { PartialDeep } from 'type-fest'; 4 | 5 | 6 | export function ChildLink({ item }: { item: PartialDeep; }) { 7 | return ( 8 |
  • 9 | 10 |
  • 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/mobile/DisclosurePanel.tsx: -------------------------------------------------------------------------------- 1 | import { Disclosure } from '@headlessui/react'; 2 | import { SeeMore } from 'components/navigation/menu/shared/SeeMore'; 3 | import { NavigationItem } from 'generated/graphql'; 4 | import { PartialDeep } from 'type-fest'; 5 | import { ChildLink } from './ChildLink'; 6 | 7 | export function DisclosurePanel({ item }: { item: PartialDeep; }) { 8 | return 9 |
      17 | {item.items.map((child) => ( 18 | 21 | ))} 22 | 23 |
    24 |
    ; 25 | } 26 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/mobile/ParentLink.tsx: -------------------------------------------------------------------------------- 1 | import { Disclosure } from '@headlessui/react'; 2 | import { NavigationLink } from 'components/utils/NavigationLink'; 3 | import { NavigationItem } from 'generated/graphql'; 4 | import { H } from 'react-accessible-headings'; 5 | import { PartialDeep } from 'type-fest'; 6 | import { AnimatedExpandButton } from '../../../utils/symbols/AnimatedExpandButton'; 7 | 8 | export function ParentLink({ item, open }: { item: PartialDeep; open: boolean; }) { 9 | return 20 | 21 | 22 | ; 23 | } 24 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/mobile/ParentLinks.tsx: -------------------------------------------------------------------------------- 1 | import { Disclosure } from '@headlessui/react'; 2 | import { GlobalContext } from 'pages/_app'; 3 | import { useContext } from 'react'; 4 | import { DisclosurePanel } from './DisclosurePanel'; 5 | import { ParentLink } from './ParentLink'; 6 | 7 | 8 | export function ParentLinks() { 9 | const context = useContext(GlobalContext); 10 | 11 | const menuLinks = context.menuLinks.renderNavigation; 12 | 13 | return ( 14 |
      15 | {menuLinks.map((item) => ( 16 | 21 | {({ open }) => ( 22 | <> 23 | 24 | 25 | 26 | )} 27 | 28 | ))} 29 |
    30 | ); 31 | } 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/mobile/PopoverPanel.tsx: -------------------------------------------------------------------------------- 1 | import { Popover } from '@headlessui/react'; 2 | import { ParentLinks } from './ParentLinks'; 3 | 4 | export function PopoverPanel() { 5 | return 12 | {/*
    13 | 14 |
    */} 15 | 16 |
    ; 17 | } 18 | -------------------------------------------------------------------------------- /apps/frontend/components/navigation/menu/shared/SeeMore.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from '@emotion/react'; 2 | import styled from '@emotion/styled'; 3 | import { MaterialSymbol } from 'components/utils/symbols/MaterialSymbol'; 4 | import Link from 'next/link'; 5 | import { GlobalContext } from 'pages/_app'; 6 | import { useContext } from 'react'; 7 | 8 | export function SeeMore({ 9 | path, 10 | isMobile = false, 11 | }: { 12 | path: string; 13 | isMobile?: boolean; 14 | }) { 15 | const context = useContext(GlobalContext); 16 | const theme = useTheme(); 17 | 18 | const { navigationSeeMore } = 19 | context.translations.translation.data.attributes; 20 | 21 | const Wrapper = isMobile 22 | ? styled.div({ 23 | padding: '16px 0 0 0', 24 | margin: '8px 0 0 0', 25 | borderTop: `${theme.color.border.primary.hexa()} 1px solid`, 26 | }) 27 | : styled.div({ 28 | margin: '32px', 29 | padding: '32px 32px 0 32px', 30 | display: 'flex', 31 | justifyContent: 'flex-end', 32 | borderTop: `${theme.color.border.primary.hexa()} 1px solid`, 33 | }); 34 | 35 | return ( 36 | 37 | 38 | 46 | {navigationSeeMore} 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /apps/frontend/components/utils/NavigationLink.tsx: -------------------------------------------------------------------------------- 1 | import { MaterialSymbol } from 'components/utils/symbols/MaterialSymbol'; 2 | import { NavigationItem } from 'generated/graphql'; 3 | import Link from 'next/link'; 4 | import { PartialDeep } from 'type-fest'; 5 | 6 | function Content({ icon, title }: { icon: string; title: string }) { 7 | return ( 8 | // https://stackoverflow.com/questions/11078509/how-to-increase-the-clickable-area-of-a-a-tag-button 9 | 18 | 26 | {icon && } 27 | {title} 28 | 29 | 30 | ); 31 | } 32 | 33 | /* strapi-plugin-navigation */ 34 | export function NavigationLink({ 35 | navigationItem, 36 | isLink = true, 37 | }: { 38 | navigationItem: PartialDeep; 39 | isLink?: boolean; 40 | }) { 41 | const { title, path, icon } = navigationItem; 42 | 43 | if (isLink) { 44 | return ( 45 | 46 | 47 | 48 | 49 | 50 | ); 51 | } else { 52 | return ; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /apps/frontend/components/utils/responsive.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | // https://medium.com/fredwong-it/react-nextjs-ssr-and-responsive-design-ae33e658975c 4 | 5 | export const DesktopContainer = styled.div` 6 | @media (max-width: 1280px) { 7 | display: none !important; 8 | } 9 | `; 10 | export const TabletAndBelow = styled.div` 11 | @media (min-width: 1279px) { 12 | display: none !important; 13 | } 14 | `; 15 | -------------------------------------------------------------------------------- /apps/frontend/components/utils/symbols/AnimatedExpandButton.tsx: -------------------------------------------------------------------------------- 1 | import { MaterialSymbol } from "components/utils/symbols/MaterialSymbol"; 2 | 3 | export function AnimatedExpandButton({ 4 | open, 5 | scale = 1, 6 | }: { 7 | open: boolean; 8 | scale?: number; 9 | }) { 10 | return ( 11 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /apps/frontend/components/utils/symbols/AnimatedMenuButton.tsx: -------------------------------------------------------------------------------- 1 | import { MaterialSymbol } from 'components/utils/symbols/MaterialSymbol'; 2 | 3 | export function AnimatedMenuButton({ open }: { open: boolean }) { 4 | const name = open ? 'close' : 'menu'; 5 | 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /apps/frontend/components/utils/symbols/MaterialSymbol.tsx: -------------------------------------------------------------------------------- 1 | interface SymbolProps { 2 | name: string; 3 | fill?: boolean; 4 | weight?: number; 5 | grade?: number; 6 | opticalSize?: number 7 | } 8 | 9 | export const MaterialSymbol: React.FunctionComponent< 10 | SymbolProps & React.HTMLProps 11 | > = ({ children, name, fill = false, weight = 400, grade = 0, opticalSize = 48, ...props }) => ( 12 | 21 | {name} 22 | 23 | ); 24 | -------------------------------------------------------------------------------- /apps/frontend/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const META_DESCRIPTION_MAX_LENGTH = 155; -------------------------------------------------------------------------------- /apps/frontend/emotion.d.ts: -------------------------------------------------------------------------------- 1 | import '@emotion/react' 2 | import Color from '@types/color' 3 | 4 | declare module '@emotion/react' { 5 | export interface Theme { 6 | color: { 7 | primary: Color 8 | text: { 9 | primary: Color 10 | secondary: Color 11 | light: Color 12 | } 13 | background: { 14 | primary: Color 15 | transculent: Color 16 | } 17 | shadow: Color 18 | border: { 19 | primary: Color 20 | withShadow: Color 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /apps/frontend/features/404/queries/FetchErrorPage.graphql: -------------------------------------------------------------------------------- 1 | query FetchErrorPage { 2 | page404 { 3 | data { 4 | attributes { 5 | title 6 | description 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/features/entries.blog/queries/FetchBlogBySlug.graphql: -------------------------------------------------------------------------------- 1 | query FetchBlogBySlug($slug: String) { 2 | articles(filters: { slug: { eq: $slug } }) { 3 | data { 4 | attributes { 5 | title 6 | content 7 | slug 8 | createdAt 9 | publishedAt 10 | updatedAt 11 | category { 12 | data { 13 | attributes { 14 | name 15 | slug 16 | } 17 | } 18 | } 19 | seo { 20 | ...Seo 21 | } 22 | author { 23 | data { 24 | ...Author 25 | } 26 | } 27 | image { 28 | data { 29 | attributes { 30 | ...Image 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /apps/frontend/features/entries.blog/queries/FetchBlogEntries.graphql: -------------------------------------------------------------------------------- 1 | query FetchBlogEntries($articlesPerPage: Int) { 2 | articles(sort: "createdAt:desc", pagination: { limit: $articlesPerPage }) { 3 | data { 4 | id 5 | attributes { 6 | title 7 | content 8 | slug 9 | image { 10 | data { 11 | attributes { 12 | ...Image 13 | } 14 | } 15 | } 16 | category { 17 | data { 18 | attributes { 19 | name 20 | slug 21 | } 22 | } 23 | } 24 | author { 25 | data { 26 | ...Author 27 | } 28 | } 29 | createdAt 30 | updatedAt 31 | publishedAt 32 | seo { 33 | ...Seo 34 | } 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /apps/frontend/features/entries.blog/queries/FetchBlogEntriesOfCategory.graphql: -------------------------------------------------------------------------------- 1 | query FetchBlogEntriesOfCategory($entriesPerPage: Int, $slug: String) { 2 | categories(filters: { slug: { eq: $slug } }) { 3 | data { 4 | attributes { 5 | seo { 6 | ...Seo 7 | } 8 | name 9 | articles( 10 | sort: "createdAt:desc" 11 | pagination: { limit: $entriesPerPage } 12 | ) { 13 | data { 14 | id 15 | attributes { 16 | title 17 | content 18 | slug 19 | image { 20 | data { 21 | attributes { 22 | ...Image 23 | } 24 | } 25 | } 26 | category { 27 | data { 28 | attributes { 29 | name 30 | slug 31 | } 32 | } 33 | } 34 | author { 35 | data { 36 | ...Author 37 | } 38 | } 39 | createdAt 40 | updatedAt 41 | publishedAt 42 | seo { 43 | ...Seo 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /apps/frontend/features/entries.blog/queries/FetchBlogEntriesSlugs.graphql: -------------------------------------------------------------------------------- 1 | query FetchBlogEntriesSlugs { 2 | articles { 3 | data { 4 | id 5 | attributes { 6 | slug 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/features/entries.blog/queries/FetchBlogPage.graphql: -------------------------------------------------------------------------------- 1 | query FetchBlogPage { 2 | blog { 3 | data { 4 | attributes { 5 | articlesSection { 6 | ...articleSectionComponent 7 | } 8 | categorySection { 9 | ...articleSectionComponent 10 | } 11 | } 12 | } 13 | } 14 | } 15 | 16 | fragment articleSectionComponent on ComponentArticlesArticlesSection { 17 | entriesPerPage 18 | header 19 | previewMaxCharacters 20 | } 21 | -------------------------------------------------------------------------------- /apps/frontend/features/entries.blog/queries/FetchCategoriesSlugs.graphql: -------------------------------------------------------------------------------- 1 | query FetchCategoriesSlugs { 2 | categories { 3 | data { 4 | id 5 | attributes { 6 | slug 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/features/entries.info/fragments/Info.graphql: -------------------------------------------------------------------------------- 1 | fragment Info on Page { 2 | content 3 | slug 4 | title 5 | createdAt 6 | publishedAt 7 | updatedAt 8 | } 9 | -------------------------------------------------------------------------------- /apps/frontend/features/entries.info/queries/FetchInfo.graphql: -------------------------------------------------------------------------------- 1 | query FetchInfo($slug: String!) { 2 | pages(filters: { slug: { eq: $slug } }) { 3 | data { 4 | attributes { 5 | ...Info 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/frontend/features/entries.info/queries/FetchInfoSlugs.graphql: -------------------------------------------------------------------------------- 1 | query FetchInfoSlugs { 2 | pages { 3 | data { 4 | attributes { 5 | slug 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/frontend/features/entries/fragments/Author.graphql: -------------------------------------------------------------------------------- 1 | fragment Author on WriterEntity { 2 | attributes { 3 | name 4 | description 5 | picture { 6 | data { 7 | attributes { 8 | ...Image 9 | } 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/frontend/features/entries/fragments/NavigationItem.graphql: -------------------------------------------------------------------------------- 1 | fragment NavigationItem on NavigationItem { 2 | title 3 | path 4 | uiRouterKey 5 | icon 6 | } 7 | -------------------------------------------------------------------------------- /apps/frontend/features/homepage/queries/FetchHomepage.graphql: -------------------------------------------------------------------------------- 1 | query FetchHomepage { 2 | homepage { 3 | data { 4 | attributes { 5 | seo { 6 | ...Seo 7 | } 8 | articlesSection { 9 | ...articlesSection 10 | } 11 | } 12 | } 13 | } 14 | } 15 | 16 | fragment articlesSection on ComponentArticlesArticlesSection { 17 | header 18 | entriesPerPage 19 | previewMaxCharacters 20 | } -------------------------------------------------------------------------------- /apps/frontend/features/images/fragments/Image.graphql: -------------------------------------------------------------------------------- 1 | fragment Image on UploadFile { 2 | alternativeText 3 | width 4 | height 5 | placeholder 6 | updatedAt 7 | url 8 | hash 9 | } -------------------------------------------------------------------------------- /apps/frontend/features/layout/queries/FetchAlert.graphql: -------------------------------------------------------------------------------- 1 | query FetchAlert { 2 | alert { 3 | data { 4 | attributes { 5 | message 6 | link 7 | isVisible 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/features/layout/queries/FetchFooter.graphql: -------------------------------------------------------------------------------- 1 | query FetchFooter { 2 | footer { 3 | data { 4 | attributes { 5 | copyright 6 | showVercelBadge 7 | email 8 | } 9 | } 10 | } 11 | 12 | socialMedias { 13 | data { 14 | attributes { 15 | iconSlug 16 | showInFooter 17 | link 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/frontend/features/layout/queries/FetchNavigation.graphql: -------------------------------------------------------------------------------- 1 | query FetchNavigation($navigationIdOrSlug: String!) { 2 | renderNavigation(navigationIdOrSlug: $navigationIdOrSlug, type: TREE) { 3 | ...NavigationItem 4 | items { 5 | ...NavigationItem 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/frontend/features/seo/components/Seo.tsx: -------------------------------------------------------------------------------- 1 | import { NextSeo, NextSeoProps } from "next-seo" 2 | import { getMetaDescription } from "../utils"; 3 | 4 | export const Seo = (props: NextSeoProps) => { 5 | return 6 | } -------------------------------------------------------------------------------- /apps/frontend/features/seo/fragments/Seo.graphql: -------------------------------------------------------------------------------- 1 | fragment Seo on ComponentSharedSeo { 2 | metaDescription 3 | keywords 4 | structuredData 5 | preventIndexing 6 | } 7 | -------------------------------------------------------------------------------- /apps/frontend/features/seo/utils.ts: -------------------------------------------------------------------------------- 1 | import { META_DESCRIPTION_MAX_LENGTH } from 'constants/index'; 2 | import parse from 'node-html-parser'; 3 | 4 | import { truncate } from 'utils'; 5 | 6 | export const getMetaDescription = (content: string) => { 7 | return truncate(parse(content).textContent, META_DESCRIPTION_MAX_LENGTH); 8 | }; 9 | -------------------------------------------------------------------------------- /apps/frontend/lib/apolloClient.ts: -------------------------------------------------------------------------------- 1 | import { ApolloClient, InMemoryCache } from '@apollo/client'; 2 | 3 | // https://www.apollographql.com/blog/apollo-client/next-js/next-js-getting-started/ 4 | 5 | const client = new ApolloClient({ 6 | uri: 'https://strapi.elektronplus.pl/graphql', 7 | cache: new InMemoryCache(), 8 | }); 9 | 10 | export default client; 11 | -------------------------------------------------------------------------------- /apps/frontend/lib/emotion.ts: -------------------------------------------------------------------------------- 1 | import Color from 'color'; 2 | import { Theme } from '@emotion/react'; 3 | 4 | const colors = { 5 | colors: { 6 | primary: '#006699', 7 | }, 8 | }; 9 | 10 | // remember to update emotion.d.ts for typing 11 | export const theme: Theme = { 12 | color: { 13 | primary: Color(colors.colors.primary), 14 | text: { 15 | primary: Color(colors.colors.primary).darken(0.55), 16 | secondary: Color(colors.colors.primary).darken(0.35).alpha(0.8), 17 | light: Color(colors.colors.primary).lighten(0.3), 18 | }, 19 | background: { 20 | primary: Color(colors.colors.primary), 21 | transculent: Color('#ffffff').alpha(0.8), 22 | }, 23 | shadow: Color(colors.colors.primary).darken(0.5).alpha(0.1), 24 | border: { 25 | primary: Color(colors.colors.primary).alpha(0.2).darken(0.2), 26 | withShadow: Color(colors.colors.primary).darken(0.5).alpha(0.15), 27 | }, 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /apps/frontend/lib/instantSearch.ts: -------------------------------------------------------------------------------- 1 | import { instantMeiliSearch } from '@meilisearch/instant-meilisearch'; 2 | 3 | export const searchClient = instantMeiliSearch( 4 | process.env.NEXT_PUBLIC_MEILISEARCH_INSTANCE_URL, 5 | process.env.NEXT_PUBLIC_MEILISEARCH_PUBLIC_KEY 6 | ); 7 | -------------------------------------------------------------------------------- /apps/frontend/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/frontend/next.config.js: -------------------------------------------------------------------------------- 1 | // This file sets a custom webpack configuration to use your Next.js app 2 | // with Sentry. 3 | // https://nextjs.org/docs/api-reference/next.config.js/introduction 4 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/ 5 | 6 | const { withSentryConfig } = require('@sentry/nextjs'); 7 | 8 | const withBundleAnalyzer = require('@next/bundle-analyzer')({ 9 | enabled: process.env.ANALYZE === 'true', 10 | }); 11 | 12 | const images = 13 | process.env.NEXT_PUBLIC_STRAPI_API_URL ?? 'strapi.elektronplus.pl'; 14 | 15 | const moduleExports = { 16 | reactStrictMode: true, 17 | compiler: { 18 | emotion: true, 19 | }, 20 | // This template doesn't support internationalization, but we use it to dynamcially set html lang (https://newdevzone.com/posts/how-to-set-html-lang-attribute-dynamically-on-nextjs-document) 21 | i18n: { 22 | locales: [process.env.NEXT_PUBLIC_LANGUAGE], 23 | defaultLocale: process.env.NEXT_PUBLIC_LANGUAGE, 24 | }, 25 | images: { 26 | formats: ['image/avif', 'image/webp'], 27 | minimumCacheTTL: 604800, 28 | loader: 'default', 29 | domains: [images], 30 | }, 31 | experimental: { 32 | images: { 33 | allowFutureImage: true, 34 | }, 35 | }, 36 | env: { 37 | NEXT_PUBLIC_MEILISEARCH_INSTANCE_URL: process.env.MEILI_HOST, 38 | }, 39 | typescript: { 40 | // !! WARN !! 41 | // Dangerously allow production builds to successfully complete even if 42 | // your project has type errors. 43 | // !! WARN !! 44 | ignoreBuildErrors: true, 45 | }, 46 | webpack(config, { dev, isServer }) { 47 | config.module.rules.push({ 48 | test: /\.svg$/i, 49 | issuer: /\.[jt]sx?$/, 50 | use: ['@svgr/webpack'], 51 | }); 52 | 53 | config.module.rules.push({ 54 | test: /\.(graphql|gql)$/, 55 | exclude: /node_modules/, 56 | loader: 'graphql-tag/loader', 57 | }); 58 | 59 | return config; 60 | }, 61 | }; 62 | 63 | const sentryWebpackPluginOptions = { 64 | // Additional config options for the Sentry Webpack plugin. Keep in mind that 65 | // the following options are set automatically, and overriding them is not 66 | // recommended: 67 | // release, url, org, project, authToken, configFile, stripPrefix, 68 | // urlPrefix, include, ignore 69 | 70 | silent: true, // Suppresses all logs 71 | // For all available options, see: 72 | // https://github.com/getsentry/sentry-webpack-plugin#options. 73 | }; 74 | 75 | // Make sure adding Sentry options is the last code to run before exporting, to 76 | // ensure that your source maps include changes from all other Webpack plugins 77 | module.exports = withBundleAnalyzer( 78 | withSentryConfig(moduleExports, sentryWebpackPluginOptions) 79 | ); 80 | -------------------------------------------------------------------------------- /apps/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "graphql-codegen && next dev -p 8080", 7 | "build": "graphql-codegen && next build", 8 | "start": "graphql-codegen && next start", 9 | "lint": "next lint", 10 | "generate": "graphql-codegen --config codegen.yml", 11 | "analyze": "ANALYZE=true npm run build" 12 | }, 13 | "dependencies": { 14 | "@headlessui/react": "^1.6.6", 15 | "@meilisearch/instant-meilisearch": "^0.7.1", 16 | "@radix-ui/react-navigation-menu": "^0.1.3-rc.46", 17 | "@sentry/nextjs": "^7.3.0", 18 | "@types/color": "^3.0.3", 19 | "color": "^4.2.3", 20 | "isomorphic-dompurify": "^0.20.0", 21 | "next": "^12.2.0", 22 | "next-seo": "^5.4.0", 23 | "node-html-parser": "^6.1.1", 24 | "prettier": "^2.7.1", 25 | "qs": "^6.10.3", 26 | "react": "18.1.0", 27 | "react-accessible-headings": "^4.2.0", 28 | "react-avatar": "^5.0.1", 29 | "react-dom": "18.1.0", 30 | "react-icons": "^4.4.0", 31 | "react-instantsearch-hooks-web": "^6.29.0", 32 | "react-masonry-css": "^1.0.16" 33 | }, 34 | "devDependencies": { 35 | "@apollo/client": "^3.6.8", 36 | "@emotion/react": "^11.9.3", 37 | "@emotion/styled": "^11.9.3", 38 | "@graphql-codegen/cli": "2.6.2", 39 | "@graphql-codegen/introspection": "2.1.1", 40 | "@graphql-codegen/typescript": "2.5.1", 41 | "@graphql-codegen/typescript-graphql-files-modules": "2.1.1", 42 | "@graphql-codegen/typescript-operations": "2.4.2", 43 | "@graphql-codegen/typescript-react-apollo": "3.2.16", 44 | "@next/bundle-analyzer": "^12.2.0", 45 | "@types/node": "^17.0.34", 46 | "@types/qs": "^6.9.7", 47 | "@types/react": "^18.0.15", 48 | "eslint": "8.15.0", 49 | "eslint-config-next": "12.1.6", 50 | "eslint-config-prettier": "^8.5.0", 51 | "eslint-plugin-storybook": "^0.5.13", 52 | "map-obj-async": "^0.0.2", 53 | "react-icons": "^4.4.0", 54 | "type-fest": "^2.16.0", 55 | "typescript": "^4.6.4", 56 | "webpack": "^5.72.1" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /apps/frontend/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | FetchErrorPageQuery, 3 | FetchErrorPageDocument, 4 | } from 'generated/graphql'; 5 | import client from 'lib/apolloClient'; 6 | import { InferGetStaticPropsType } from 'next'; 7 | import { Level, H } from 'react-accessible-headings'; 8 | import { Button } from 'components/Button'; 9 | import { useTheme } from '@emotion/react'; 10 | 11 | export default function Custom404({ 12 | errorData, 13 | }: InferGetStaticPropsType) { 14 | const { title, description } = errorData.page404.data.attributes; 15 | 16 | const theme = useTheme(); 17 | 18 | return ( 19 | 20 |
    21 |
    22 | 23 | {title} 24 | 25 |

    {description}

    26 |
    27 |
    28 | 31 | 34 |
    35 |
    36 |
    37 | ); 38 | } 39 | 40 | export const getStaticProps = async () => { 41 | const errorData: FetchErrorPageQuery = ( 42 | await client.query({ 43 | query: FetchErrorPageDocument, 44 | }) 45 | ).data; 46 | 47 | return { 48 | props: { errorData }, 49 | revalidate: 1, 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /apps/frontend/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { DocumentNode } from '@apollo/client'; 2 | import { ThemeProvider } from '@emotion/react'; 3 | import Layout from 'components/Layout'; 4 | import { 5 | FetchAlertQuery, 6 | FetchAlertDocument, 7 | FetchFooterDocument, 8 | FetchFooterQuery, 9 | FetchNavigationDocument, 10 | FetchNavigationQuery, 11 | GetGlobalDocument, 12 | GetGlobalQuery, 13 | GetTranslationsDocument, 14 | GetTranslationsQuery, 15 | } from 'generated/graphql'; 16 | import client from 'lib/apolloClient'; 17 | import mapObject from 'map-obj-async'; 18 | import { DefaultSeo } from 'next-seo'; 19 | import type { AppProps } from 'next/app'; 20 | import App, { AppContext } from 'next/app'; 21 | import Head from 'next/head'; 22 | import { createContext } from 'react'; 23 | import { getStrapiMedia } from 'services/media'; 24 | import 'styles/globals.css'; 25 | import { theme } from 'lib/emotion'; 26 | 27 | interface GlobalContextInterface { 28 | menuLinks: FetchNavigationQuery; 29 | footerLinks: FetchNavigationQuery; 30 | footerContent: FetchFooterQuery; 31 | global: GetGlobalQuery; 32 | alert: FetchAlertQuery; 33 | translations: GetTranslationsQuery; 34 | } 35 | 36 | export const GlobalContext = createContext(null); 37 | 38 | export default function MyApp({ Component, pageProps }: AppProps) { 39 | const globalContext: GlobalContextInterface = pageProps.globalData; 40 | 41 | const attributes = globalContext.global.global.data.attributes; 42 | 43 | return ( 44 | <> 45 | 46 | 47 | 48 | 49 | {/* */} 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | ); 73 | } 74 | 75 | MyApp.getInitialProps = async (ctx: AppContext) => { 76 | const appProps = await App.getInitialProps(ctx); 77 | 78 | interface Query { 79 | query: DocumentNode; 80 | variables?: object; 81 | } 82 | 83 | const queries: Record = { 84 | menuLinks: { 85 | query: FetchNavigationDocument, 86 | variables: { 87 | navigationIdOrSlug: 'menu', 88 | }, 89 | }, 90 | footerLinks: { 91 | query: FetchNavigationDocument, 92 | variables: { 93 | navigationIdOrSlug: 'footer', 94 | }, 95 | }, 96 | footerContent: { 97 | query: FetchFooterDocument, 98 | }, 99 | global: { 100 | query: GetGlobalDocument, 101 | }, 102 | alert: { 103 | query: FetchAlertDocument, 104 | }, 105 | translations: { 106 | query: GetTranslationsDocument, 107 | }, 108 | }; 109 | 110 | const globalData = await mapObject(queries, async (key, parameters) => { 111 | const { query, variables } = parameters; 112 | const result = (await client.query({ query, variables })).data; 113 | 114 | return [key, result]; 115 | }); 116 | 117 | return { 118 | ...appProps, 119 | pageProps: { 120 | globalData, 121 | }, 122 | }; 123 | }; 124 | -------------------------------------------------------------------------------- /apps/frontend/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { Head, Html, Main, NextScript } from 'next/document'; 2 | 3 | class MyDocument extends Document { 4 | render() { 5 | return ( 6 | 7 | 8 | 12 | 16 | 17 | 18 |
    19 | 20 | 21 | 22 | ); 23 | } 24 | } 25 | 26 | export default MyDocument; 27 | -------------------------------------------------------------------------------- /apps/frontend/pages/_error.js: -------------------------------------------------------------------------------- 1 | import NextErrorComponent from 'next/error'; 2 | 3 | import * as Sentry from '@sentry/nextjs'; 4 | 5 | const MyError = ({ statusCode, hasGetInitialPropsRun, err }) => { 6 | if (!hasGetInitialPropsRun && err) { 7 | // getInitialProps is not called in case of 8 | // https://github.com/vercel/next.js/issues/8592. As a workaround, we pass 9 | // err via _app.js so it can be captured 10 | Sentry.captureException(err); 11 | // Flushing is not required in this case as it only happens on the client 12 | } 13 | 14 | return ; 15 | }; 16 | 17 | MyError.getInitialProps = async (context) => { 18 | const errorInitialProps = await NextErrorComponent.getInitialProps(context); 19 | 20 | const { res, err, asPath } = context; 21 | 22 | // Workaround for https://github.com/vercel/next.js/issues/8592, mark when 23 | // getInitialProps has run 24 | errorInitialProps.hasGetInitialPropsRun = true; 25 | 26 | // Returning early because we don't want to log 404 errors to Sentry. 27 | if (res?.statusCode === 404) { 28 | return errorInitialProps; 29 | } 30 | 31 | // Running on the server, the response object (`res`) is available. 32 | // 33 | // Next.js will pass an err on the server if a page's data fetching methods 34 | // threw or returned a Promise that rejected 35 | // 36 | // Running on the client (browser), Next.js will provide an err if: 37 | // 38 | // - a page's `getInitialProps` threw or returned a Promise that rejected 39 | // - an exception was thrown somewhere in the React lifecycle (render, 40 | // componentDidMount, etc) that was caught by Next.js's React Error 41 | // Boundary. Read more about what types of exceptions are caught by Error 42 | // Boundaries: https://reactjs.org/docs/error-boundaries.html 43 | 44 | if (err) { 45 | Sentry.captureException(err); 46 | 47 | // Flushing before returning is necessary if deploying to Vercel, see 48 | // https://vercel.com/docs/platform/limits#streaming-responses 49 | await Sentry.flush(2000); 50 | 51 | return errorInitialProps; 52 | } 53 | 54 | // If this point is reached, getInitialProps was called without any 55 | // information about what the error might be. This is unexpected and may 56 | // indicate a bug introduced in Next.js, so record it in Sentry 57 | Sentry.captureException( 58 | new Error(`_error.js getInitialProps missing data at path: ${asPath}`) 59 | ); 60 | await Sentry.flush(2000); 61 | 62 | return errorInitialProps; 63 | }; 64 | 65 | export default MyError; 66 | -------------------------------------------------------------------------------- /apps/frontend/pages/blog/[slug].tsx: -------------------------------------------------------------------------------- 1 | import { NextSeo } from 'next-seo'; 2 | import Article from 'components/article/Index'; 3 | import { 4 | FetchBlogEntriesSlugsDocument, 5 | } from 'generated/graphql'; 6 | import client from 'lib/apolloClient'; 7 | import { GetStaticPaths, InferGetStaticPropsType } from 'next'; 8 | import { getStrapiMedia } from 'services/media'; 9 | import { FetchBlogBySlugDocument, FetchBlogBySlugQuery } from "generated/graphql"; 10 | import { FetchBlogEntriesSlugsQuery } from "../../generated/graphql"; 11 | 12 | export default function ArticlePage({ 13 | articleData, 14 | }: InferGetStaticPropsType) { 15 | const { title, seo, publishedAt, updatedAt, category, image, slug } = 16 | articleData.articles.data[0].attributes; 17 | 18 | return ( 19 | <> 20 | 47 |
    52 | 53 | ); 54 | } 55 | 56 | export const getStaticPaths: GetStaticPaths = async () => { 57 | const articleSlugsData: FetchBlogEntriesSlugsQuery = ( 58 | await client.query({ 59 | query: FetchBlogEntriesSlugsDocument, 60 | }) 61 | ).data; 62 | 63 | return { 64 | paths: articleSlugsData.articles.data.map((article) => ({ 65 | params: { 66 | slug: article.attributes.slug, 67 | }, 68 | })), 69 | fallback: 'blocking', 70 | }; 71 | }; 72 | 73 | export const getStaticProps = async ({ params }) => { 74 | const articleData: FetchBlogBySlugQuery = ( 75 | await client.query({ 76 | variables: { 77 | slug: params.slug, 78 | }, 79 | query: FetchBlogBySlugDocument, 80 | }) 81 | ).data; 82 | 83 | return { 84 | props: { articleData }, 85 | revalidate: 1, 86 | }; 87 | }; 88 | -------------------------------------------------------------------------------- /apps/frontend/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import ArticlesGrid from 'components/article/Grid'; 2 | import { 3 | FetchBlogEntriesDocument, 4 | FetchBlogEntriesQuery, 5 | GetTranslationsDocument, 6 | GetTranslationsQuery, 7 | } from 'generated/graphql'; 8 | import client from 'lib/apolloClient'; 9 | import { NextSeo } from 'next-seo'; 10 | import { InferGetStaticPropsType } from 'next/types'; 11 | import { FetchHomepageQuery, FetchHomepageDocument } from "generated/graphql"; 12 | 13 | export default function Home({ 14 | articlesData, 15 | homePageData, 16 | translationsData, 17 | }: InferGetStaticPropsType) { 18 | const { articles } = articlesData; 19 | const { header, previewMaxCharacters } = 20 | homePageData.homepage.data.attributes.articlesSection; 21 | const { articleReadMore, paginationNextPage } = 22 | translationsData.translation.data.attributes; 23 | 24 | return ( 25 | <> 26 | 32 |
    33 |
    34 | 40 |
    41 |
    42 | 43 | ); 44 | } 45 | 46 | export async function getStaticProps() { 47 | const homePageData: FetchHomepageQuery = ( 48 | await client.query({ 49 | query: FetchHomepageDocument, 50 | }) 51 | ).data; 52 | 53 | const articlesData: FetchBlogEntriesQuery = ( 54 | await client.query({ 55 | query: FetchBlogEntriesDocument, 56 | variables: { 57 | articlesPerPage: 58 | homePageData.homepage.data.attributes.articlesSection.entriesPerPage, 59 | }, 60 | }) 61 | ).data; 62 | 63 | const translationsData: GetTranslationsQuery = ( 64 | await client.query({ 65 | query: GetTranslationsDocument, 66 | }) 67 | ).data; 68 | 69 | return { 70 | props: { 71 | homePageData, 72 | articlesData, 73 | translationsData, 74 | }, 75 | revalidate: 1, 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /apps/frontend/pages/info/[slug].tsx: -------------------------------------------------------------------------------- 1 | import client from 'lib/apolloClient'; 2 | import DOMPurify from 'isomorphic-dompurify'; 3 | import { Seo } from 'features/seo/components/Seo'; 4 | import { EntryTitle } from 'components/entry/Title'; 5 | import { EntryContent } from 'components/entry/Content'; 6 | import { EntryDetails } from 'components/entry/Details'; 7 | import { FetchInfoDocument, FetchInfoQuery, FetchInfoSlugsQuery, FetchInfoSlugsDocument } from "generated/graphql"; 8 | import { GetStaticPaths, InferGetStaticPropsType } from 'next/types'; 9 | 10 | export default function InfoPage({ page }: InferGetStaticPropsType) { 11 | return ( 12 | <> 13 | 14 |
    15 |
    16 | 17 | 18 |
    19 | 20 |
    25 | 26 |
    27 | 28 | ); 29 | } 30 | 31 | export const getStaticPaths: GetStaticPaths = async () => { 32 | const data: FetchInfoSlugsQuery = ( 33 | await client.query({ 34 | query: FetchInfoSlugsDocument, 35 | }) 36 | ).data; 37 | 38 | return { 39 | paths: data.pages.data.map((page) => ({ 40 | params: { slug: page.attributes.slug }, 41 | })), 42 | fallback: 'blocking', 43 | }; 44 | }; 45 | 46 | export const getStaticProps = async ({ params }) => { 47 | const data: FetchInfoQuery = ( 48 | await client.query({ 49 | variables: { 50 | slug: params.slug, 51 | }, 52 | query: FetchInfoDocument, 53 | }) 54 | ).data; 55 | 56 | const page = data.pages.data.map((page) => ({ 57 | ...page.attributes, 58 | content: DOMPurify.sanitize(page.attributes.content), 59 | }))[0]; 60 | 61 | return { 62 | props: { 63 | page, 64 | }, 65 | revalidate: 1, 66 | }; 67 | }; 68 | -------------------------------------------------------------------------------- /apps/frontend/pages/tag/[slug].tsx: -------------------------------------------------------------------------------- 1 | import { NextSeo } from 'next-seo'; 2 | import ArticlesGrid from 'components/article/Grid'; 3 | 4 | import { GetStaticPaths, InferGetStaticPropsType } from 'next'; 5 | import { 6 | GetTranslationsDocument, 7 | GetTranslationsQuery, 8 | } from 'generated/graphql'; 9 | import client from 'lib/apolloClient'; 10 | import { FetchBlogPageQuery, FetchBlogPageDocument, FetchBlogEntriesOfCategoryQuery, FetchBlogEntriesOfCategoryDocument, FetchCategoriesSlugsQuery, FetchCategoriesSlugsDocument } from "../../generated/graphql"; 11 | 12 | function Category({ 13 | categoryArticlesData, 14 | blogPageData, 15 | translationsData, 16 | }: InferGetStaticPropsType) { 17 | const { name, articles, seo } = 18 | categoryArticlesData.categories.data[0].attributes; 19 | const { previewMaxCharacters } = 20 | blogPageData.blog.data.attributes.categorySection; 21 | const { articleReadMore } = translationsData.translation.data.attributes; 22 | 23 | return ( 24 | <> 25 | 30 |
    31 |
    32 | 38 |
    39 |
    40 | 41 | ); 42 | } 43 | 44 | export const getStaticPaths: GetStaticPaths = async () => { 45 | const categoriesSlugsData: FetchCategoriesSlugsQuery = ( 46 | await client.query({ query: FetchCategoriesSlugsDocument }) 47 | ).data; 48 | 49 | return { 50 | paths: categoriesSlugsData.categories.data.map((category) => ({ 51 | params: { 52 | slug: category.attributes.slug, 53 | }, 54 | })), 55 | fallback: false, 56 | }; 57 | }; 58 | 59 | export const getStaticProps = async ({ params }) => { 60 | const blogPageData: FetchBlogPageQuery = ( 61 | await client.query({ 62 | query: FetchBlogPageDocument, 63 | }) 64 | ).data; 65 | 66 | const categoryArticlesData: FetchBlogEntriesOfCategoryQuery = ( 67 | await client.query({ 68 | query: FetchBlogEntriesOfCategoryDocument, 69 | variables: { 70 | slug: params.slug, 71 | entriesPerPage: 72 | blogPageData.blog.data.attributes.categorySection.entriesPerPage, 73 | }, 74 | }) 75 | ).data; 76 | 77 | const translationsData: GetTranslationsQuery = ( 78 | await client.query({ 79 | query: GetTranslationsDocument, 80 | }) 81 | ).data; 82 | 83 | return { 84 | props: { 85 | blogPageData, 86 | categoryArticlesData, 87 | translationsData, 88 | }, 89 | revalidate: 1, 90 | }; 91 | }; 92 | 93 | export default Category; 94 | -------------------------------------------------------------------------------- /apps/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElektronPlus/school-website/c1e15485c34d8ce88d46e5f5f7269baf78c740cb/apps/frontend/public/favicon.ico -------------------------------------------------------------------------------- /apps/frontend/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /apps/frontend/queries/getGlobal.graphql: -------------------------------------------------------------------------------- 1 | query getGlobal { 2 | global { 3 | data { 4 | id 5 | attributes { 6 | siteName 7 | createdAt 8 | updatedAt 9 | background { 10 | data { 11 | attributes { 12 | ...Image 13 | } 14 | } 15 | } 16 | favicon { 17 | data { 18 | attributes { 19 | ...Image 20 | } 21 | } 22 | } 23 | logo { 24 | data { 25 | attributes { 26 | ...Image 27 | } 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/frontend/queries/getTranslations.graphql: -------------------------------------------------------------------------------- 1 | query getTranslations { 2 | translation { 3 | data { 4 | attributes { 5 | articleReadMore 6 | paginationNextPage 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/sentry.client.config.js: -------------------------------------------------------------------------------- 1 | // This file configures the initialization of Sentry on the browser. 2 | // The config you add here will be used whenever a page is visited. 3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/ 4 | 5 | import * as Sentry from '@sentry/nextjs'; 6 | 7 | const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; 8 | 9 | Sentry.init({ 10 | dsn: 11 | SENTRY_DSN || 12 | 'https://93d1ca7d72e54f05af207dab352a95aa@o1206761.ingest.sentry.io/6533213', 13 | // Adjust this value in production, or use tracesSampler for greater control 14 | tracesSampleRate: 1.0, 15 | // ... 16 | // Note: if you want to override the automatic release value, do not set a 17 | // `release` value here - use the environment variable `SENTRY_RELEASE`, so 18 | // that it will also get attached to your source maps 19 | }); 20 | -------------------------------------------------------------------------------- /apps/frontend/sentry.properties: -------------------------------------------------------------------------------- 1 | defaults.url=https://sentry.io/ 2 | defaults.org=konhi 3 | defaults.project=zseis-zgora 4 | cli.executable=../../../../home/node/.npm/_npx/a8388072043b4cbc/node_modules/@sentry/cli/bin/sentry-cli 5 | -------------------------------------------------------------------------------- /apps/frontend/sentry.server.config.js: -------------------------------------------------------------------------------- 1 | // This file configures the initialization of Sentry on the server. 2 | // The config you add here will be used whenever the server handles a request. 3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/ 4 | 5 | import * as Sentry from '@sentry/nextjs'; 6 | 7 | const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; 8 | 9 | Sentry.init({ 10 | dsn: 11 | SENTRY_DSN || 12 | 'https://93d1ca7d72e54f05af207dab352a95aa@o1206761.ingest.sentry.io/6533213', 13 | // Adjust this value in production, or use tracesSampler for greater control 14 | tracesSampleRate: 1.0, 15 | // ... 16 | // Note: if you want to override the automatic release value, do not set a 17 | // `release` value here - use the environment variable `SENTRY_RELEASE`, so 18 | // that it will also get attached to your source maps 19 | }); 20 | -------------------------------------------------------------------------------- /apps/frontend/services/api.ts: -------------------------------------------------------------------------------- 1 | import qs from 'qs'; 2 | 3 | export function getStrapiURL(path = '') { 4 | return `https://${ 5 | process.env.NEXT_PUBLIC_STRAPI_API_URL || 'https://strapi.elektronplus.pl' 6 | }${path}`; 7 | } 8 | 9 | export async function fetchAPI( 10 | path: string, 11 | urlParamsObject = {}, 12 | options = {} 13 | ) { 14 | const mergedOptions = { 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | }, 18 | ...options, 19 | }; 20 | 21 | const queryString = qs.stringify(urlParamsObject); 22 | const requestUrl = `${getStrapiURL( 23 | `/api${path}${queryString ? `?${queryString}` : ''}` 24 | )}`; 25 | 26 | const response = await fetch(requestUrl, mergedOptions); 27 | 28 | if (!response.ok) { 29 | console.error(response.statusText); 30 | console.log(requestUrl); 31 | throw new Error(`An error occured please try again`); 32 | } 33 | const data = await response.json(); 34 | return data; 35 | } 36 | -------------------------------------------------------------------------------- /apps/frontend/services/media.ts: -------------------------------------------------------------------------------- 1 | import { UploadFileEntityResponse } from 'generated/graphql'; 2 | import { PartialDeep } from 'type-fest'; 3 | import { getStrapiURL } from './api'; 4 | 5 | export function getStrapiMedia(media: PartialDeep) { 6 | if (media === undefined || media.data === null) { 7 | return null; 8 | } 9 | 10 | const { url } = media.data.attributes; 11 | const imageUrl = url.startsWith('/') ? getStrapiURL(url) : url; 12 | 13 | // adding updatedAt to the image object will allow us to cache the image and revalidate it only when it's updated 14 | return `${imageUrl}?hash=${media.data.attributes.hash}`; 15 | } 16 | -------------------------------------------------------------------------------- /apps/frontend/services/utils.ts: -------------------------------------------------------------------------------- 1 | const ARTICLES_PATH = 'blog'; 2 | const CATEGORY_PATH = 'tag'; 3 | 4 | export function getArticlePathBySlug(slug: string) { 5 | return `/${ARTICLES_PATH}/${slug}`; 6 | } 7 | 8 | export function getCategoryPathBySlug(slug: string) { 9 | return `/${CATEGORY_PATH}/${slug}`; 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /apps/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "baseUrl": ".", 11 | "incremental": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve", 18 | "jsxImportSource": "@emotion/react" 19 | }, 20 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "pages/_error.js"], 21 | "exclude": ["node_modules", ".cache"] 22 | } 23 | -------------------------------------------------------------------------------- /apps/frontend/types/graphql.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetArticlesQuery, 3 | GetCategoriesBySlugQuery, 4 | } from '../generated/graphql'; 5 | 6 | export type Articles = 7 | | GetArticlesQuery['articles'] 8 | | GetCategoriesBySlugQuery['categories']['data']['0']['attributes']['articles']; 9 | -------------------------------------------------------------------------------- /apps/frontend/utils/index.ts: -------------------------------------------------------------------------------- 1 | import parse from "node-html-parser"; 2 | import { META_DESCRIPTION_MAX_LENGTH } from "constants/index"; 3 | 4 | 5 | export function truncate(string: string, maxLength: number) { 6 | return (string.length > maxLength) ? string.slice(0, maxLength - 1) + '...' : string; 7 | }; 8 | export const getMetaDescriptionFromContent = (content: string) => { 9 | return truncate(parse(content).textContent, META_DESCRIPTION_MAX_LENGTH); 10 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | strapi: 4 | container_name: school-website-starter-strapi 5 | build: ./apps/backend 6 | image: school-website-starter-strapi:latest 7 | restart: always 8 | environment: 9 | - ADMIN_JWT_SECRET 10 | - JWT_SECRET 11 | - API_TOKEN_SALT 12 | - APP_KEYS 13 | - DATABASE_CLIENT=mysql 14 | - DATABASE_HOST=database # docker service name instead of localhost - https://stackoverflow.com/questions/67457694/mysql-docker-error-connect-econnrefused-127-0-0-17201-to-mysql-container 15 | - DATABASE_NAME=${MYSQL_DATABASE} 16 | - DATABASE_PASSWORD=${MYSQL_PASSWORD} 17 | - DATABASE_PORT=${MYSQL_PORT} 18 | - DATABASE_USERNAME=${MYSQL_USER} 19 | - HOST=${STRAPI_HOST} 20 | - NODE_ENV=development 21 | - PORT=${STRAPI_PORT} 22 | - MEILI_HOST 23 | - MEILI_MASTER_KEY 24 | volumes: 25 | - ./apps/backend/config:/opt/app/config 26 | - ./apps/backend/src:/opt/app/src 27 | - ./apps/backend/package.json:/opt/package.json 28 | - ./apps/backend/package-lock.json:/opt/package-lock.json 29 | - ./apps/backend/.env:/opt/app/.env 30 | ports: 31 | - "${STRAPI_PORT}:${STRAPI_PORT}" 32 | networks: 33 | - strapi 34 | depends_on: 35 | - database 36 | 37 | database: 38 | image: mysql:8 39 | container_name: school-website-starter-database 40 | platform: linux/amd64 #for platform error on Apple M1 chips 41 | command: --default-authentication-plugin=mysql_native_password # Old way of storing passwords, required because strapi uses old mysql client - https://stackoverflow.com/questions/50093144/mysql-8-0-client-does-not-support-authentication-protocol-requested-by-server 42 | environment: 43 | - MYSQL_DATABASE 44 | - MYSQL_PASSWORD 45 | - MYSQL_USER 46 | - MYSQL_PORT 47 | - MYSQL_ROOT_PASSWORD 48 | restart: always 49 | volumes: 50 | - strapi-data:/var/lib/mysql 51 | expose: 52 | - ${MYSQL_PORT} 53 | # uncomment if you want to have a port exposed on the host machine - usable for debugging 54 | # ports: 55 | # - "${MYSQL_PORT}:${MYSQL_PORT}" 56 | networks: 57 | - strapi 58 | 59 | volumes: 60 | strapi-data: 61 | 62 | 63 | networks: 64 | strapi: 65 | name: Strapi 66 | driver: bridge 67 | -------------------------------------------------------------------------------- /libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElektronPlus/school-website/c1e15485c34d8ce88d46e5f5f7269baf78c740cb/libs/.gitkeep -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmScope": "school-website", 3 | "affected": { 4 | "defaultBase": "main" 5 | }, 6 | "cli": { 7 | "defaultCollection": "@nrwl/workspace" 8 | }, 9 | "implicitDependencies": { 10 | "package.json": { 11 | "dependencies": "*", 12 | "devDependencies": "*" 13 | }, 14 | ".eslintrc.json": "*" 15 | }, 16 | "tasksRunnerOptions": { 17 | "default": { 18 | "runner": "@nrwl/nx-cloud", 19 | "options": { 20 | "cacheableOperations": [ 21 | "build", 22 | "lint", 23 | "test", 24 | "e2e" 25 | ], 26 | "accessToken": "NDFlODdmNDEtOTYyOC00OTFlLWIwOWQtYjQ0Njk2MGQ5Yzg5fHJlYWQtd3JpdGU=" 27 | } 28 | } 29 | }, 30 | "targetDependencies": { 31 | "build": [ 32 | { 33 | "target": "build", 34 | "projects": "dependencies" 35 | } 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "school-website", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "nx serve", 7 | "build": "nx build", 8 | "test": "nx test", 9 | "build-storybook": "build-storybook --config-dir apps/frontend/.storybook" 10 | }, 11 | "private": true, 12 | "devDependencies": { 13 | "@nrwl/cli": "14.1.4", 14 | "@nrwl/nx-cloud": "latest", 15 | "@nrwl/workspace": "14.1.4", 16 | "nx": "14.1.4", 17 | "prettier": "^2.5.1", 18 | "typescript": "~4.6.2", 19 | "webpack": "^5.72.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tools/generators/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElektronPlus/school-website/c1e15485c34d8ce88d46e5f5f7269baf78c740cb/tools/generators/.gitkeep -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": {} 18 | }, 19 | "exclude": ["node_modules", "tmp"] 20 | } 21 | -------------------------------------------------------------------------------- /workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "projects": {} 4 | } 5 | --------------------------------------------------------------------------------