diff --git a/apps/backend/config/database.ts b/apps/backend/config/database.ts
index a162e94..e838feb 100644
--- a/apps/backend/config/database.ts
+++ b/apps/backend/config/database.ts
@@ -8,9 +8,7 @@ export default ({env}) => ({
user: env('DATABASE_USERNAME', 'postgres'),
password: env('DATABASE_PASSWORD', '0000'),
schema: env('DATABASE_SCHEMA', 'public'), // Not required
- ssl: {
- rejectUnauthorized: env.bool('DATABASE_SSL_SELF', false),
- },
+ ssl: false,
},
debug: false,
},
diff --git a/apps/backend/package-lock.json b/apps/backend/package-lock.json
index 8cd8e45..c7ed4ef 100644
--- a/apps/backend/package-lock.json
+++ b/apps/backend/package-lock.json
@@ -14,7 +14,9 @@
"@strapi/strapi": "4.5.4",
"@strapi/utils": "^4.5.6",
"better-sqlite3": "7.4.6",
- "strapi-plugin-menus": "^1.2.1"
+ "pg": "^8.8.0",
+ "strapi-plugin-menus": "^1.2.1",
+ "strapi-plugin-populate-deep": "^1.1.2"
},
"engines": {
"node": ">=14.19.1 <=18.x.x",
@@ -5926,6 +5928,14 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
+ "node_modules/buffer-writer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
+ "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/buffer-xor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
@@ -12328,6 +12338,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/packet-reader": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
+ "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
+ },
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@@ -12616,11 +12631,80 @@
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
},
+ "node_modules/pg": {
+ "version": "8.8.0",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz",
+ "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==",
+ "dependencies": {
+ "buffer-writer": "2.0.0",
+ "packet-reader": "1.0.0",
+ "pg-connection-string": "^2.5.0",
+ "pg-pool": "^3.5.2",
+ "pg-protocol": "^1.5.0",
+ "pg-types": "^2.1.0",
+ "pgpass": "1.x"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "peerDependencies": {
+ "pg-native": ">=3.0.1"
+ },
+ "peerDependenciesMeta": {
+ "pg-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/pg-connection-string": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
},
+ "node_modules/pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/pg-pool": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz",
+ "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==",
+ "peerDependencies": {
+ "pg": ">=8.0"
+ }
+ },
+ "node_modules/pg-protocol": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
+ "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
+ },
+ "node_modules/pg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+ "dependencies": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.4",
+ "postgres-interval": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pgpass": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+ "dependencies": {
+ "split2": "^4.1.0"
+ }
+ },
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -13081,6 +13165,41 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
+ "node_modules/postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postgres-bytea": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+ "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/prebuild-install": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
@@ -15110,6 +15229,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/split2": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
+ "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -15362,6 +15489,15 @@
"react": ">=16.8"
}
},
+ "node_modules/strapi-plugin-populate-deep": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/strapi-plugin-populate-deep/-/strapi-plugin-populate-deep-1.1.2.tgz",
+ "integrity": "sha512-fKw1BMRpdWbi1D+E0Yq08lZFxrTHgdPQ2P5jKEuXEE5YHvHiepSHVQtymDJGQ3WRWierem8ZBpnz2nl6iRx8Gw==",
+ "engines": {
+ "node": ">=12.x.x <=16.x.x",
+ "npm": ">=6.0.0"
+ }
+ },
"node_modules/stream-browserify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
diff --git a/apps/backend/package.json b/apps/backend/package.json
index 6b151d6..748e862 100644
--- a/apps/backend/package.json
+++ b/apps/backend/package.json
@@ -18,7 +18,9 @@
"@strapi/strapi": "4.5.4",
"@strapi/utils": "^4.5.6",
"better-sqlite3": "7.4.6",
- "strapi-plugin-menus": "^1.2.1"
+ "pg": "^8.8.0",
+ "strapi-plugin-menus": "^1.2.1",
+ "strapi-plugin-populate-deep": "^1.1.2"
},
"author": {
"name": "A Strapi developer"
diff --git a/apps/backend/src/api/page/content-types/page/schema.json b/apps/backend/src/api/page/content-types/page/schema.json
new file mode 100644
index 0000000..5100f7e
--- /dev/null
+++ b/apps/backend/src/api/page/content-types/page/schema.json
@@ -0,0 +1,28 @@
+{
+ "kind": "collectionType",
+ "collectionName": "pages",
+ "info": {
+ "singularName": "page",
+ "pluralName": "pages",
+ "displayName": "Pages",
+ "description": ""
+ },
+ "options": {
+ "draftAndPublish": true
+ },
+ "pluginOptions": {},
+ "attributes": {
+ "label": {
+ "type": "string"
+ },
+ "slug": {
+ "type": "uid",
+ "targetField": "label"
+ },
+ "seo": {
+ "type": "component",
+ "repeatable": false,
+ "component": "shared.seo"
+ }
+ }
+}
diff --git a/apps/backend/src/api/page/controllers/page.ts b/apps/backend/src/api/page/controllers/page.ts
new file mode 100644
index 0000000..f513618
--- /dev/null
+++ b/apps/backend/src/api/page/controllers/page.ts
@@ -0,0 +1,7 @@
+/**
+ * page controller
+ */
+
+import { factories } from '@strapi/strapi'
+
+export default factories.createCoreController('api::page.page');
diff --git a/apps/backend/src/api/page/routes/page.ts b/apps/backend/src/api/page/routes/page.ts
new file mode 100644
index 0000000..8e5ddfc
--- /dev/null
+++ b/apps/backend/src/api/page/routes/page.ts
@@ -0,0 +1,7 @@
+/**
+ * page router
+ */
+
+import { factories } from '@strapi/strapi';
+
+export default factories.createCoreRouter('api::page.page');
diff --git a/apps/backend/src/api/page/services/page.ts b/apps/backend/src/api/page/services/page.ts
new file mode 100644
index 0000000..eaf07ff
--- /dev/null
+++ b/apps/backend/src/api/page/services/page.ts
@@ -0,0 +1,7 @@
+/**
+ * page service
+ */
+
+import { factories } from '@strapi/strapi';
+
+export default factories.createCoreService('api::page.page');
diff --git a/apps/backend/src/components/meta/meta.json b/apps/backend/src/components/meta/meta.json
new file mode 100644
index 0000000..cb9ba70
--- /dev/null
+++ b/apps/backend/src/components/meta/meta.json
@@ -0,0 +1,16 @@
+{
+ "collectionName": "components_meta_metas",
+ "info": {
+ "displayName": "meta",
+ "icon": "network-wired"
+ },
+ "options": {},
+ "attributes": {
+ "property": {
+ "type": "string"
+ },
+ "content": {
+ "type": "string"
+ }
+ }
+}
diff --git a/apps/backend/src/components/shared/seo.json b/apps/backend/src/components/shared/seo.json
new file mode 100644
index 0000000..c9f433e
--- /dev/null
+++ b/apps/backend/src/components/shared/seo.json
@@ -0,0 +1,36 @@
+{
+ "collectionName": "components_shared_seos",
+ "info": {
+ "displayName": "Seo",
+ "icon": "book",
+ "description": ""
+ },
+ "options": {},
+ "attributes": {
+ "metaTitle": {
+ "type": "string"
+ },
+ "metaDescription": {
+ "type": "string"
+ },
+ "SharedImage": {
+ "displayName": "SharedImage",
+ "type": "component",
+ "repeatable": false,
+ "component": "shared.shared-image"
+ },
+ "Meta": {
+ "displayName": "meta",
+ "type": "component",
+ "repeatable": true,
+ "component": "meta.meta"
+ },
+ "preventIndexing": {
+ "type": "boolean",
+ "default": false
+ },
+ "structuredData": {
+ "type": "json"
+ }
+ }
+}
diff --git a/apps/backend/src/components/shared/shared-image.json b/apps/backend/src/components/shared/shared-image.json
new file mode 100644
index 0000000..4424287
--- /dev/null
+++ b/apps/backend/src/components/shared/shared-image.json
@@ -0,0 +1,24 @@
+{
+ "collectionName": "components_shared_shared_images",
+ "info": {
+ "displayName": "SharedImage",
+ "icon": "file-image",
+ "description": ""
+ },
+ "options": {},
+ "attributes": {
+ "alt": {
+ "type": "string"
+ },
+ "media": {
+ "allowedTypes": [
+ "images",
+ "files",
+ "videos",
+ "audios"
+ ],
+ "type": "media",
+ "multiple": false
+ }
+ }
+}
diff --git a/apps/client/.env b/apps/client/.env
new file mode 100644
index 0000000..172e20c
--- /dev/null
+++ b/apps/client/.env
@@ -0,0 +1 @@
+STRAPI_URL=http://127.0.0.1:1337/api
diff --git a/apps/client/components/carousel/carousel.module.scss b/apps/client/components/carousel/carousel.module.scss
new file mode 100644
index 0000000..45c2aa4
--- /dev/null
+++ b/apps/client/components/carousel/carousel.module.scss
@@ -0,0 +1,7 @@
+/*
+ * Replace this with your own classes
+ *
+ * e.g.
+ * .container {
+ * }
+*/
diff --git a/apps/client/components/carousel/carousel.spec.tsx b/apps/client/components/carousel/carousel.spec.tsx
new file mode 100644
index 0000000..4201b81
--- /dev/null
+++ b/apps/client/components/carousel/carousel.spec.tsx
@@ -0,0 +1,10 @@
+import { render } from '@testing-library/react';
+
+import Carousel from './carousel';
+
+describe('Carousel', () => {
+ it('should render successfully', () => {
+ const { baseElement } = render();
+ expect(baseElement).toBeTruthy();
+ });
+});
diff --git a/apps/client/components/carousel/carousel.tsx b/apps/client/components/carousel/carousel.tsx
new file mode 100644
index 0000000..42fe9e1
--- /dev/null
+++ b/apps/client/components/carousel/carousel.tsx
@@ -0,0 +1,14 @@
+import styles from './carousel.module.scss';
+
+/* eslint-disable-next-line */
+export interface CarouselProps {}
+
+export function Carousel(props: CarouselProps) {
+ return (
+
+
Welcome to Carousel!
+
+ );
+}
+
+export default Carousel;
diff --git a/apps/client/components/footer/footer.module.scss b/apps/client/components/footer/footer.module.scss
new file mode 100644
index 0000000..45c2aa4
--- /dev/null
+++ b/apps/client/components/footer/footer.module.scss
@@ -0,0 +1,7 @@
+/*
+ * Replace this with your own classes
+ *
+ * e.g.
+ * .container {
+ * }
+*/
diff --git a/apps/client/components/footer/footer.spec.tsx b/apps/client/components/footer/footer.spec.tsx
new file mode 100644
index 0000000..97c820b
--- /dev/null
+++ b/apps/client/components/footer/footer.spec.tsx
@@ -0,0 +1,10 @@
+import { render } from '@testing-library/react';
+
+import Footer from './footer';
+
+describe('Footer', () => {
+ it('should render successfully', () => {
+ const { baseElement } = render();
+ expect(baseElement).toBeTruthy();
+ });
+});
diff --git a/apps/client/components/footer/footer.tsx b/apps/client/components/footer/footer.tsx
new file mode 100644
index 0000000..8820f3c
--- /dev/null
+++ b/apps/client/components/footer/footer.tsx
@@ -0,0 +1,36 @@
+import delve from "dlv";
+
+export function Footer({items = []}) {
+
+ const generateItem = (item, index) => {
+ const value = delve(item, 'attributes', {});
+
+ return (
+
+
+ {value.title}
+
+
+ );
+ }
+
+ return (
+
+ );
+}
+
+export default Footer;
diff --git a/apps/client/components/header/header.module.scss b/apps/client/components/header/header.module.scss
new file mode 100644
index 0000000..45c2aa4
--- /dev/null
+++ b/apps/client/components/header/header.module.scss
@@ -0,0 +1,7 @@
+/*
+ * Replace this with your own classes
+ *
+ * e.g.
+ * .container {
+ * }
+*/
diff --git a/apps/client/components/header/header.spec.tsx b/apps/client/components/header/header.spec.tsx
new file mode 100644
index 0000000..3fd19ff
--- /dev/null
+++ b/apps/client/components/header/header.spec.tsx
@@ -0,0 +1,10 @@
+import { render } from '@testing-library/react';
+
+import Header from './header';
+
+describe('Header', () => {
+ it('should render successfully', () => {
+ const { baseElement } = render();
+ expect(baseElement).toBeTruthy();
+ });
+});
diff --git a/apps/client/components/header/header.tsx b/apps/client/components/header/header.tsx
new file mode 100644
index 0000000..6a0eee3
--- /dev/null
+++ b/apps/client/components/header/header.tsx
@@ -0,0 +1,152 @@
+import {Collapse, Dropdown} from "flowbite";
+import {useEffect} from "react";
+import delve from "dlv";
+
+/* eslint-disable-next-line */
+export interface AppHeaderProps {
+}
+
+export function Header({items = []}) {
+ useEffect(function mount() {
+ const burgerButton = document.getElementById('mega-menu-button');
+ const megaZone = document.getElementById('mega-menu');
+ new Collapse(megaZone, burgerButton);
+
+ const userButton = document.getElementById('user-menu-button');
+ const userZone = document.getElementById('user-dropdown');
+ new Dropdown(userZone, userButton);
+ });
+
+ const generateItem = (item, index, isActive = false) => {
+ const value = delve(item, 'attributes', {});
+ return (
+
+ {value.children.data.length > 0 ? dropdownItem(value, isActive) : regularItem(value, isActive)}
+
+ );
+ }
+
+ const regularItem = (item, isActive = false) => {
+ return (
+ {item.title}
+ )
+ }
+
+ const regularDropdownItem = (item, index) => {
+ const value = delve(item, 'attributes', {});
+ return (
+
+
+ {value.title}
+
+
+ );
+ }
+
+ const dropdownItem = (item, isActive = false) => {
+ return (
+ <>
+
+
+ >
+ )
+ }
+
+ return (
+
+ );
+}
+
+export default Header;
diff --git a/apps/client/components/layout/layout.module.scss b/apps/client/components/layout/layout.module.scss
new file mode 100644
index 0000000..1e26662
--- /dev/null
+++ b/apps/client/components/layout/layout.module.scss
@@ -0,0 +1,12 @@
+/*
+ * Replace this with your own classes
+ *
+ * e.g.
+ * .container {
+ * }
+*/
+.app-container {
+ position: relative;
+ padding-top: 70px;
+ height: calc(100% - 181px);
+}
diff --git a/apps/client/components/layout/layout.spec.tsx b/apps/client/components/layout/layout.spec.tsx
new file mode 100644
index 0000000..56e3b81
--- /dev/null
+++ b/apps/client/components/layout/layout.spec.tsx
@@ -0,0 +1,10 @@
+import { render } from '@testing-library/react';
+
+import Layout from './layout';
+
+describe('Layout', () => {
+ it('should render successfully', () => {
+ const { baseElement } = render();
+ expect(baseElement).toBeTruthy();
+ });
+});
diff --git a/apps/client/components/layout/layout.tsx b/apps/client/components/layout/layout.tsx
new file mode 100644
index 0000000..8dbb3f7
--- /dev/null
+++ b/apps/client/components/layout/layout.tsx
@@ -0,0 +1,18 @@
+import Header from "../header/header";
+import Footer from "../footer/footer";
+
+import styles from './layout.module.scss';
+
+export function Layout({menuHeader = [], menuFooter = [], children}) {
+ return (
+ <>
+
+
+ {children}
+
+