From e8c3c3cfb8d0edf50b0ba86ebcf9d758138bda40 Mon Sep 17 00:00:00 2001
From: Radoslav Zeman <19418224+radoslavzeman@users.noreply.github.com>
Date: Mon, 23 Sep 2024 10:43:06 +0200
Subject: [PATCH] #1323 Add Faqs as content type (#1346)
* #1323 Add Faq and Faq Category content types to Strapi
* #1323 Implement FE sections for Faqs and Faq Categories
* #1323 Fix PR comments
---
.../components/common/FaqsGroup/FaqsGroup.tsx | 31 ++
next/components/layouts/Sections.tsx | 8 +
.../sections/FaqCategoriesSection.tsx | 44 ++
next/components/sections/FaqsSection.tsx | 31 ++
next/services/graphql/index.ts | 493 ++++++++++++++++++
next/services/graphql/queries/Faqs.graphql | 20 +
.../services/graphql/queries/Sections.graphql | 28 +
strapi/schema.graphql | 164 +++++-
.../content-types/faq-category/schema.json | 44 ++
.../faq-category/controllers/faq-category.ts | 7 +
.../api/faq-category/routes/faq-category.ts | 7 +
.../api/faq-category/services/faq-category.ts | 7 +
.../src/api/faq/content-types/faq/schema.json | 43 ++
strapi/src/api/faq/controllers/faq.ts | 7 +
strapi/src/api/faq/routes/faq.ts | 7 +
strapi/src/api/faq/services/faq.ts | 7 +
.../api/page/content-types/page/schema.json | 2 +
.../components/sections/faq-categories.json | 20 +
strapi/src/components/sections/faqs.json | 21 +
19 files changed, 989 insertions(+), 2 deletions(-)
create mode 100644 next/components/common/FaqsGroup/FaqsGroup.tsx
create mode 100644 next/components/sections/FaqCategoriesSection.tsx
create mode 100644 next/components/sections/FaqsSection.tsx
create mode 100644 next/services/graphql/queries/Faqs.graphql
create mode 100644 strapi/src/api/faq-category/content-types/faq-category/schema.json
create mode 100644 strapi/src/api/faq-category/controllers/faq-category.ts
create mode 100644 strapi/src/api/faq-category/routes/faq-category.ts
create mode 100644 strapi/src/api/faq-category/services/faq-category.ts
create mode 100644 strapi/src/api/faq/content-types/faq/schema.json
create mode 100644 strapi/src/api/faq/controllers/faq.ts
create mode 100644 strapi/src/api/faq/routes/faq.ts
create mode 100644 strapi/src/api/faq/services/faq.ts
create mode 100644 strapi/src/components/sections/faq-categories.json
create mode 100644 strapi/src/components/sections/faqs.json
diff --git a/next/components/common/FaqsGroup/FaqsGroup.tsx b/next/components/common/FaqsGroup/FaqsGroup.tsx
new file mode 100644
index 000000000..0d5c867ab
--- /dev/null
+++ b/next/components/common/FaqsGroup/FaqsGroup.tsx
@@ -0,0 +1,31 @@
+import React from 'react'
+
+import Accordion from '@/components/common/Accordion/Accordion'
+import Markdown from '@/components/formatting/Markdown/Markdown'
+import { FaqEntityFragment } from '@/services/graphql'
+import { isDefined } from '@/utils/isDefined'
+
+export type FaqsGroupProps = {
+ faqs: FaqEntityFragment[]
+}
+
+const FaqsGroup = ({ faqs }: FaqsGroupProps) => {
+ return (
+
+ {faqs
+ .map((faq, index) => {
+ if (!faq.attributes) return null
+
+ return (
+ // eslint-disable-next-line react/no-array-index-key
+
+
+
+ )
+ })
+ .filter(isDefined)}
+
+ )
+}
+
+export default FaqsGroup
diff --git a/next/components/layouts/Sections.tsx b/next/components/layouts/Sections.tsx
index 1612bccfe..7794dcbc1 100644
--- a/next/components/layouts/Sections.tsx
+++ b/next/components/layouts/Sections.tsx
@@ -13,6 +13,8 @@ import ColumnedTextSection from '@/components/sections/ColumnedTextSection'
import ComparisonSection from '@/components/sections/ComparisonSection'
import ContactsSection from '@/components/sections/ContactsSection'
import DividerSection from '@/components/sections/DividerSection'
+import FaqCategoriesSection from '@/components/sections/FaqCategoriesSection'
+import FaqsSection from '@/components/sections/FaqsSection'
import FeaturedBlogPostsSection from '@/components/sections/FeaturedBlogPostsSection'
import FileListSection from '@/components/sections/FileListSection'
import GallerySection from '@/components/sections/GallerySection'
@@ -120,6 +122,12 @@ const SectionContent = ({ section }: { section: SectionsFragment }) => {
case 'ComponentSectionsTestimonials':
return
+ case 'ComponentSectionsFaqs':
+ return
+
+ case 'ComponentSectionsFaqCategories':
+ return
+
default:
return null
}
diff --git a/next/components/sections/FaqCategoriesSection.tsx b/next/components/sections/FaqCategoriesSection.tsx
new file mode 100644
index 000000000..02254c783
--- /dev/null
+++ b/next/components/sections/FaqCategoriesSection.tsx
@@ -0,0 +1,44 @@
+import { Typography } from '@bratislava/component-library'
+import React from 'react'
+
+import FaqsGroup from '@/components/common/FaqsGroup/FaqsGroup'
+import { FaqCategoriesSectionFragment } from '@/services/graphql'
+import { isDefined } from '@/utils/isDefined'
+
+type Props = {
+ section: FaqCategoriesSectionFragment
+}
+
+const FaqCategoriesSection = ({ section }: Props) => {
+ const { title, text, faqCategories } = section ?? {}
+
+ const filteredFaqCategories = faqCategories?.data.filter(isDefined) ?? []
+
+ return (
+
+ {title || text ? (
+
+ {title ? {title} : null}
+ {text ? {text} : null}
+
+ ) : null}
+ {filteredFaqCategories
+ .map((faqCategory) => {
+ if (!faqCategory.attributes) return null
+
+ const { title: categoryTitle, faqs, slug } = faqCategory.attributes
+ const filteredFaqs = faqs?.data.filter(isDefined) ?? []
+
+ return (
+
+ {categoryTitle}
+
+
+ )
+ })
+ .filter(isDefined)}
+
+ )
+}
+
+export default FaqCategoriesSection
diff --git a/next/components/sections/FaqsSection.tsx b/next/components/sections/FaqsSection.tsx
new file mode 100644
index 000000000..62910652a
--- /dev/null
+++ b/next/components/sections/FaqsSection.tsx
@@ -0,0 +1,31 @@
+import { Typography } from '@bratislava/component-library'
+import React from 'react'
+
+import FaqsGroup from '@/components/common/FaqsGroup/FaqsGroup'
+import { FaqsSectionFragment } from '@/services/graphql'
+import { isDefined } from '@/utils/isDefined'
+
+type Props = {
+ section: FaqsSectionFragment
+}
+
+const FaqsSection = ({ section }: Props) => {
+ const { title, text, faqs } = section ?? {}
+
+ const filteredFaqs = faqs?.data.filter(isDefined) ?? []
+
+ return (
+
+ {title || text ? (
+
+ {title ? {title} : null}
+ {text ? {text} : null}
+
+ ) : null}
+
+
+
+ )
+}
+
+export default FaqsSection
diff --git a/next/services/graphql/index.ts b/next/services/graphql/index.ts
index f509c35cb..72dffdaa9 100644
--- a/next/services/graphql/index.ts
+++ b/next/services/graphql/index.ts
@@ -1130,6 +1130,36 @@ export type ComponentSectionsDivider = {
style?: Maybe
}
+export type ComponentSectionsFaqCategories = {
+ __typename?: 'ComponentSectionsFaqCategories'
+ faqCategories?: Maybe
+ id: Scalars['ID']['output']
+ text?: Maybe
+ title?: Maybe
+}
+
+export type ComponentSectionsFaqCategoriesFaqCategoriesArgs = {
+ filters?: InputMaybe
+ pagination?: InputMaybe
+ publicationState?: InputMaybe
+ sort?: InputMaybe>>
+}
+
+export type ComponentSectionsFaqs = {
+ __typename?: 'ComponentSectionsFaqs'
+ faqs?: Maybe
+ id: Scalars['ID']['output']
+ text?: Maybe
+ title?: Maybe
+}
+
+export type ComponentSectionsFaqsFaqsArgs = {
+ filters?: InputMaybe
+ pagination?: InputMaybe
+ publicationState?: InputMaybe
+ sort?: InputMaybe>>
+}
+
export type ComponentSectionsFeaturedBlogPosts = {
__typename?: 'ComponentSectionsFeaturedBlogPosts'
first_blog?: Maybe
@@ -1838,6 +1868,139 @@ export type Error = {
message?: Maybe
}
+export type Faq = {
+ __typename?: 'Faq'
+ body?: Maybe
+ createdAt?: Maybe
+ faqCategory?: Maybe
+ locale?: Maybe
+ localizations?: Maybe
+ publishedAt?: Maybe
+ title: Scalars['String']['output']
+ updatedAt?: Maybe
+}
+
+export type FaqLocalizationsArgs = {
+ filters?: InputMaybe
+ pagination?: InputMaybe
+ publicationState?: InputMaybe
+ sort?: InputMaybe>>
+}
+
+export type FaqCategory = {
+ __typename?: 'FaqCategory'
+ createdAt?: Maybe
+ faqs?: Maybe
+ locale?: Maybe
+ localizations?: Maybe
+ publishedAt?: Maybe
+ slug: Scalars['String']['output']
+ title: Scalars['String']['output']
+ updatedAt?: Maybe
+}
+
+export type FaqCategoryFaqsArgs = {
+ filters?: InputMaybe
+ pagination?: InputMaybe
+ publicationState?: InputMaybe
+ sort?: InputMaybe>>
+}
+
+export type FaqCategoryLocalizationsArgs = {
+ filters?: InputMaybe
+ pagination?: InputMaybe
+ publicationState?: InputMaybe
+ sort?: InputMaybe>>
+}
+
+export type FaqCategoryEntity = {
+ __typename?: 'FaqCategoryEntity'
+ attributes?: Maybe
+ id?: Maybe
+}
+
+export type FaqCategoryEntityResponse = {
+ __typename?: 'FaqCategoryEntityResponse'
+ data?: Maybe
+}
+
+export type FaqCategoryEntityResponseCollection = {
+ __typename?: 'FaqCategoryEntityResponseCollection'
+ data: Array
+ meta: ResponseCollectionMeta
+}
+
+export type FaqCategoryFiltersInput = {
+ and?: InputMaybe>>
+ createdAt?: InputMaybe
+ faqs?: InputMaybe
+ id?: InputMaybe
+ locale?: InputMaybe
+ localizations?: InputMaybe
+ not?: InputMaybe
+ or?: InputMaybe>>
+ publishedAt?: InputMaybe
+ slug?: InputMaybe
+ title?: InputMaybe
+ updatedAt?: InputMaybe
+}
+
+export type FaqCategoryInput = {
+ faqs?: InputMaybe>>
+ publishedAt?: InputMaybe
+ slug?: InputMaybe
+ title?: InputMaybe
+}
+
+export type FaqCategoryRelationResponseCollection = {
+ __typename?: 'FaqCategoryRelationResponseCollection'
+ data: Array
+}
+
+export type FaqEntity = {
+ __typename?: 'FaqEntity'
+ attributes?: Maybe
+ id?: Maybe
+}
+
+export type FaqEntityResponse = {
+ __typename?: 'FaqEntityResponse'
+ data?: Maybe
+}
+
+export type FaqEntityResponseCollection = {
+ __typename?: 'FaqEntityResponseCollection'
+ data: Array
+ meta: ResponseCollectionMeta
+}
+
+export type FaqFiltersInput = {
+ and?: InputMaybe>>
+ body?: InputMaybe
+ createdAt?: InputMaybe
+ faqCategory?: InputMaybe
+ id?: InputMaybe
+ locale?: InputMaybe
+ localizations?: InputMaybe
+ not?: InputMaybe
+ or?: InputMaybe>>
+ publishedAt?: InputMaybe
+ title?: InputMaybe
+ updatedAt?: InputMaybe
+}
+
+export type FaqInput = {
+ body?: InputMaybe
+ faqCategory?: InputMaybe
+ publishedAt?: InputMaybe
+ title?: InputMaybe
+}
+
+export type FaqRelationResponseCollection = {
+ __typename?: 'FaqRelationResponseCollection'
+ data: Array
+}
+
export type FileInfoInput = {
alternativeText?: InputMaybe
caption?: InputMaybe
@@ -2019,6 +2182,8 @@ export type GenericMorph =
| ComponentSectionsComparisonSection
| ComponentSectionsContactsSection
| ComponentSectionsDivider
+ | ComponentSectionsFaqCategories
+ | ComponentSectionsFaqs
| ComponentSectionsFeaturedBlogPosts
| ComponentSectionsFileList
| ComponentSectionsGallery
@@ -2047,6 +2212,8 @@ export type GenericMorph =
| ComponentSectionsVideos
| ComponentSectionsWaves
| ComponentTaxAdministratorsTaxAdministrator
+ | Faq
+ | FaqCategory
| Footer
| General
| Homepage
@@ -2523,6 +2690,10 @@ export type Mutation = {
createAlertLocalization?: Maybe
createBlogPost?: Maybe
createBlogPostLocalization?: Maybe
+ createFaq?: Maybe
+ createFaqCategory?: Maybe
+ createFaqCategoryLocalization?: Maybe
+ createFaqLocalization?: Maybe
createFooterLocalization?: Maybe
createGeneralLocalization?: Maybe
createHomepageLocalization?: Maybe
@@ -2550,6 +2721,8 @@ export type Mutation = {
createVzn?: Maybe
deleteAlert?: Maybe
deleteBlogPost?: Maybe
+ deleteFaq?: Maybe
+ deleteFaqCategory?: Maybe
deleteFooter?: Maybe
deleteGeneral?: Maybe
deleteHomepage?: Maybe
@@ -2583,6 +2756,8 @@ export type Mutation = {
resetPassword?: Maybe
updateAlert?: Maybe
updateBlogPost?: Maybe
+ updateFaq?: Maybe
+ updateFaqCategory?: Maybe
updateFileInfo: UploadFileEntityResponse
updateFooter?: Maybe
updateGeneral?: Maybe
@@ -2630,6 +2805,28 @@ export type MutationCreateBlogPostLocalizationArgs = {
locale?: InputMaybe
}
+export type MutationCreateFaqArgs = {
+ data: FaqInput
+ locale?: InputMaybe
+}
+
+export type MutationCreateFaqCategoryArgs = {
+ data: FaqCategoryInput
+ locale?: InputMaybe
+}
+
+export type MutationCreateFaqCategoryLocalizationArgs = {
+ data?: InputMaybe
+ id?: InputMaybe
+ locale?: InputMaybe
+}
+
+export type MutationCreateFaqLocalizationArgs = {
+ data?: InputMaybe
+ id?: InputMaybe
+ locale?: InputMaybe
+}
+
export type MutationCreateFooterLocalizationArgs = {
data?: InputMaybe
id?: InputMaybe
@@ -2757,6 +2954,16 @@ export type MutationDeleteBlogPostArgs = {
locale?: InputMaybe
}
+export type MutationDeleteFaqArgs = {
+ id: Scalars['ID']['input']
+ locale?: InputMaybe
+}
+
+export type MutationDeleteFaqCategoryArgs = {
+ id: Scalars['ID']['input']
+ locale?: InputMaybe
+}
+
export type MutationDeleteFooterArgs = {
locale?: InputMaybe
}
@@ -2875,6 +3082,18 @@ export type MutationUpdateBlogPostArgs = {
locale?: InputMaybe
}
+export type MutationUpdateFaqArgs = {
+ data: FaqInput
+ id: Scalars['ID']['input']
+ locale?: InputMaybe
+}
+
+export type MutationUpdateFaqCategoryArgs = {
+ data: FaqCategoryInput
+ id: Scalars['ID']['input']
+ locale?: InputMaybe
+}
+
export type MutationUpdateFileInfoArgs = {
id: Scalars['ID']['input']
info?: InputMaybe
@@ -3194,6 +3413,8 @@ export type PageSectionsDynamicZone =
| ComponentSectionsComparisonSection
| ComponentSectionsContactsSection
| ComponentSectionsDivider
+ | ComponentSectionsFaqCategories
+ | ComponentSectionsFaqs
| ComponentSectionsFeaturedBlogPosts
| ComponentSectionsFileList
| ComponentSectionsGallery
@@ -3314,6 +3535,10 @@ export type Query = {
alert?: Maybe
blogPost?: Maybe
blogPosts?: Maybe
+ faq?: Maybe
+ faqCategories?: Maybe
+ faqCategory?: Maybe
+ faqs?: Maybe
footer?: Maybe
general?: Maybe
homepage?: Maybe
@@ -3367,6 +3592,32 @@ export type QueryBlogPostsArgs = {
sort?: InputMaybe>>
}
+export type QueryFaqArgs = {
+ id?: InputMaybe
+ locale?: InputMaybe
+}
+
+export type QueryFaqCategoriesArgs = {
+ filters?: InputMaybe
+ locale?: InputMaybe
+ pagination?: InputMaybe
+ publicationState?: InputMaybe
+ sort?: InputMaybe>>
+}
+
+export type QueryFaqCategoryArgs = {
+ id?: InputMaybe
+ locale?: InputMaybe
+}
+
+export type QueryFaqsArgs = {
+ filters?: InputMaybe
+ locale?: InputMaybe
+ pagination?: InputMaybe
+ publicationState?: InputMaybe
+ sort?: InputMaybe>>
+}
+
export type QueryFooterArgs = {
locale?: InputMaybe
publicationState?: InputMaybe
@@ -5617,6 +5868,30 @@ export type PageCategoryEntityFragment = {
} | null
}
+export type FaqCategoryEntityFragment = {
+ __typename?: 'FaqCategoryEntity'
+ id?: string | null
+ attributes?: {
+ __typename?: 'FaqCategory'
+ title: string
+ slug: string
+ faqs?: {
+ __typename?: 'FaqRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqEntity'
+ id?: string | null
+ attributes?: { __typename?: 'Faq'; title: string; body?: string | null } | null
+ }>
+ } | null
+ } | null
+}
+
+export type FaqEntityFragment = {
+ __typename?: 'FaqEntity'
+ id?: string | null
+ attributes?: { __typename?: 'Faq'; title: string; body?: string | null } | null
+}
+
export type UploadImageSrcEntityFragment = {
__typename?: 'UploadFileEntity'
id?: string | null
@@ -8398,6 +8673,48 @@ export type PageBySlugQuery = {
hasBackground?: boolean | null
style?: Enum_Componentsectionsdivider_Style | null
}
+ | {
+ __typename: 'ComponentSectionsFaqCategories'
+ title?: string | null
+ text?: string | null
+ faqCategories?: {
+ __typename?: 'FaqCategoryRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqCategoryEntity'
+ id?: string | null
+ attributes?: {
+ __typename?: 'FaqCategory'
+ title: string
+ slug: string
+ faqs?: {
+ __typename?: 'FaqRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqEntity'
+ id?: string | null
+ attributes?: {
+ __typename?: 'Faq'
+ title: string
+ body?: string | null
+ } | null
+ }>
+ } | null
+ } | null
+ }>
+ } | null
+ }
+ | {
+ __typename: 'ComponentSectionsFaqs'
+ title?: string | null
+ text?: string | null
+ faqs?: {
+ __typename?: 'FaqRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqEntity'
+ id?: string | null
+ attributes?: { __typename?: 'Faq'; title: string; body?: string | null } | null
+ }>
+ } | null
+ }
| {
__typename: 'ComponentSectionsFeaturedBlogPosts'
id: string
@@ -9462,6 +9779,44 @@ export type PageEntityFragment = {
hasBackground?: boolean | null
style?: Enum_Componentsectionsdivider_Style | null
}
+ | {
+ __typename: 'ComponentSectionsFaqCategories'
+ title?: string | null
+ text?: string | null
+ faqCategories?: {
+ __typename?: 'FaqCategoryRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqCategoryEntity'
+ id?: string | null
+ attributes?: {
+ __typename?: 'FaqCategory'
+ title: string
+ slug: string
+ faqs?: {
+ __typename?: 'FaqRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqEntity'
+ id?: string | null
+ attributes?: { __typename?: 'Faq'; title: string; body?: string | null } | null
+ }>
+ } | null
+ } | null
+ }>
+ } | null
+ }
+ | {
+ __typename: 'ComponentSectionsFaqs'
+ title?: string | null
+ text?: string | null
+ faqs?: {
+ __typename?: 'FaqRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqEntity'
+ id?: string | null
+ attributes?: { __typename?: 'Faq'; title: string; body?: string | null } | null
+ }>
+ } | null
+ }
| {
__typename: 'ComponentSectionsFeaturedBlogPosts'
id: string
@@ -12115,6 +12470,46 @@ export type RegulationsSectionFragment = {
} | null
}
+export type FaqsSectionFragment = {
+ __typename?: 'ComponentSectionsFaqs'
+ title?: string | null
+ text?: string | null
+ faqs?: {
+ __typename?: 'FaqRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqEntity'
+ id?: string | null
+ attributes?: { __typename?: 'Faq'; title: string; body?: string | null } | null
+ }>
+ } | null
+}
+
+export type FaqCategoriesSectionFragment = {
+ __typename?: 'ComponentSectionsFaqCategories'
+ title?: string | null
+ text?: string | null
+ faqCategories?: {
+ __typename?: 'FaqCategoryRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqCategoryEntity'
+ id?: string | null
+ attributes?: {
+ __typename?: 'FaqCategory'
+ title: string
+ slug: string
+ faqs?: {
+ __typename?: 'FaqRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqEntity'
+ id?: string | null
+ attributes?: { __typename?: 'Faq'; title: string; body?: string | null } | null
+ }>
+ } | null
+ } | null
+ }>
+ } | null
+}
+
type Sections_ComponentSectionsAccordion_Fragment = {
__typename: 'ComponentSectionsAccordion'
title?: string | null
@@ -12361,6 +12756,46 @@ type Sections_ComponentSectionsDivider_Fragment = {
style?: Enum_Componentsectionsdivider_Style | null
}
+type Sections_ComponentSectionsFaqCategories_Fragment = {
+ __typename: 'ComponentSectionsFaqCategories'
+ title?: string | null
+ text?: string | null
+ faqCategories?: {
+ __typename?: 'FaqCategoryRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqCategoryEntity'
+ id?: string | null
+ attributes?: {
+ __typename?: 'FaqCategory'
+ title: string
+ slug: string
+ faqs?: {
+ __typename?: 'FaqRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqEntity'
+ id?: string | null
+ attributes?: { __typename?: 'Faq'; title: string; body?: string | null } | null
+ }>
+ } | null
+ } | null
+ }>
+ } | null
+}
+
+type Sections_ComponentSectionsFaqs_Fragment = {
+ __typename: 'ComponentSectionsFaqs'
+ title?: string | null
+ text?: string | null
+ faqs?: {
+ __typename?: 'FaqRelationResponseCollection'
+ data: Array<{
+ __typename?: 'FaqEntity'
+ id?: string | null
+ attributes?: { __typename?: 'Faq'; title: string; body?: string | null } | null
+ }>
+ } | null
+}
+
type Sections_ComponentSectionsFeaturedBlogPosts_Fragment = {
__typename: 'ComponentSectionsFeaturedBlogPosts'
id: string
@@ -12971,6 +13406,8 @@ export type SectionsFragment =
| Sections_ComponentSectionsComparisonSection_Fragment
| Sections_ComponentSectionsContactsSection_Fragment
| Sections_ComponentSectionsDivider_Fragment
+ | Sections_ComponentSectionsFaqCategories_Fragment
+ | Sections_ComponentSectionsFaqs_Fragment
| Sections_ComponentSectionsFeaturedBlogPosts_Fragment
| Sections_ComponentSectionsFileList_Fragment
| Sections_ComponentSectionsGallery_Fragment
@@ -13882,6 +14319,54 @@ export const TestimonialsSectionFragmentDoc = gql`
}
${TestimonialItemBlockFragmentDoc}
`
+export const FaqEntityFragmentDoc = gql`
+ fragment FaqEntity on FaqEntity {
+ id
+ attributes {
+ title
+ body
+ }
+ }
+`
+export const FaqsSectionFragmentDoc = gql`
+ fragment FaqsSection on ComponentSectionsFaqs {
+ title
+ text
+ faqs {
+ data {
+ ...FaqEntity
+ }
+ }
+ }
+ ${FaqEntityFragmentDoc}
+`
+export const FaqCategoryEntityFragmentDoc = gql`
+ fragment FaqCategoryEntity on FaqCategoryEntity {
+ id
+ attributes {
+ title
+ slug
+ faqs {
+ data {
+ ...FaqEntity
+ }
+ }
+ }
+ }
+ ${FaqEntityFragmentDoc}
+`
+export const FaqCategoriesSectionFragmentDoc = gql`
+ fragment FaqCategoriesSection on ComponentSectionsFaqCategories {
+ title
+ text
+ faqCategories {
+ data {
+ ...FaqCategoryEntity
+ }
+ }
+ }
+ ${FaqCategoryEntityFragmentDoc}
+`
export const SectionsFragmentDoc = gql`
fragment Sections on PageSectionsDynamicZone {
__typename
@@ -13972,6 +14457,12 @@ export const SectionsFragmentDoc = gql`
... on ComponentSectionsTestimonials {
...TestimonialsSection
}
+ ... on ComponentSectionsFaqs {
+ ...FaqsSection
+ }
+ ... on ComponentSectionsFaqCategories {
+ ...FaqCategoriesSection
+ }
}
${IconTitleDescSectionFragmentDoc}
${DividerSectionFragmentDoc}
@@ -14002,6 +14493,8 @@ export const SectionsFragmentDoc = gql`
${RegulationsListSectionFragmentDoc}
${RegulationsSectionFragmentDoc}
${TestimonialsSectionFragmentDoc}
+ ${FaqsSectionFragmentDoc}
+ ${FaqCategoriesSectionFragmentDoc}
`
export const BlogPostEntityFragmentDoc = gql`
fragment BlogPostEntity on BlogPostEntity {
diff --git a/next/services/graphql/queries/Faqs.graphql b/next/services/graphql/queries/Faqs.graphql
new file mode 100644
index 000000000..c4e26e002
--- /dev/null
+++ b/next/services/graphql/queries/Faqs.graphql
@@ -0,0 +1,20 @@
+fragment FaqCategoryEntity on FaqCategoryEntity {
+ id
+ attributes {
+ title
+ slug
+ faqs {
+ data {
+ ...FaqEntity
+ }
+ }
+ }
+}
+
+fragment FaqEntity on FaqEntity {
+ id
+ attributes {
+ title
+ body
+ }
+}
diff --git a/next/services/graphql/queries/Sections.graphql b/next/services/graphql/queries/Sections.graphql
index 1e4012339..e1bafbe3f 100644
--- a/next/services/graphql/queries/Sections.graphql
+++ b/next/services/graphql/queries/Sections.graphql
@@ -446,6 +446,26 @@ fragment RegulationsSection on ComponentSectionsRegulations {
}
}
+fragment FaqsSection on ComponentSectionsFaqs {
+ title
+ text
+ faqs {
+ data {
+ ...FaqEntity
+ }
+ }
+}
+
+fragment FaqCategoriesSection on ComponentSectionsFaqCategories {
+ title
+ text
+ faqCategories {
+ data {
+ ...FaqCategoryEntity
+ }
+ }
+}
+
fragment Sections on PageSectionsDynamicZone {
__typename
@@ -566,6 +586,14 @@ fragment Sections on PageSectionsDynamicZone {
... on ComponentSectionsTestimonials {
...TestimonialsSection
}
+
+ ... on ComponentSectionsFaqs {
+ ...FaqsSection
+ }
+
+ ... on ComponentSectionsFaqCategories {
+ ...FaqCategoriesSection
+ }
}
fragment SubpageListPageHeaderSection on ComponentSectionsSubpageList {
diff --git a/strapi/schema.graphql b/strapi/schema.graphql
index be3289e4d..a6afb90f7 100644
--- a/strapi/schema.graphql
+++ b/strapi/schema.graphql
@@ -1303,6 +1303,52 @@ input ComponentSectionsDividerInput {
style: ENUM_COMPONENTSECTIONSDIVIDER_STYLE
}
+type ComponentSectionsFaqCategories {
+ faqCategories(filters: FaqCategoryFiltersInput, pagination: PaginationArg = {}, publicationState: PublicationState = LIVE, sort: [String] = []): FaqCategoryRelationResponseCollection
+ id: ID!
+ text: String
+ title: String
+}
+
+input ComponentSectionsFaqCategoriesFiltersInput {
+ and: [ComponentSectionsFaqCategoriesFiltersInput]
+ faqCategories: FaqCategoryFiltersInput
+ not: ComponentSectionsFaqCategoriesFiltersInput
+ or: [ComponentSectionsFaqCategoriesFiltersInput]
+ text: StringFilterInput
+ title: StringFilterInput
+}
+
+input ComponentSectionsFaqCategoriesInput {
+ faqCategories: [ID]
+ id: ID
+ text: String
+ title: String
+}
+
+type ComponentSectionsFaqs {
+ faqs(filters: FaqFiltersInput, pagination: PaginationArg = {}, publicationState: PublicationState = LIVE, sort: [String] = []): FaqRelationResponseCollection
+ id: ID!
+ text: String
+ title: String
+}
+
+input ComponentSectionsFaqsFiltersInput {
+ and: [ComponentSectionsFaqsFiltersInput]
+ faqs: FaqFiltersInput
+ not: ComponentSectionsFaqsFiltersInput
+ or: [ComponentSectionsFaqsFiltersInput]
+ text: StringFilterInput
+ title: StringFilterInput
+}
+
+input ComponentSectionsFaqsInput {
+ faqs: [ID]
+ id: ID
+ text: String
+ title: String
+}
+
type ComponentSectionsFeaturedBlogPosts {
first_blog: BlogPostEntityResponse
id: ID!
@@ -2293,6 +2339,108 @@ type Error {
message: String
}
+type Faq {
+ body: String
+ createdAt: DateTime
+ faqCategory: FaqCategoryEntityResponse
+ locale: String
+ localizations(filters: FaqFiltersInput, pagination: PaginationArg = {}, publicationState: PublicationState = LIVE, sort: [String] = []): FaqRelationResponseCollection
+ publishedAt: DateTime
+ title: String!
+ updatedAt: DateTime
+}
+
+type FaqCategory {
+ createdAt: DateTime
+ faqs(filters: FaqFiltersInput, pagination: PaginationArg = {}, publicationState: PublicationState = LIVE, sort: [String] = []): FaqRelationResponseCollection
+ locale: String
+ localizations(filters: FaqCategoryFiltersInput, pagination: PaginationArg = {}, publicationState: PublicationState = LIVE, sort: [String] = []): FaqCategoryRelationResponseCollection
+ publishedAt: DateTime
+ slug: String!
+ title: String!
+ updatedAt: DateTime
+}
+
+type FaqCategoryEntity {
+ attributes: FaqCategory
+ id: ID
+}
+
+type FaqCategoryEntityResponse {
+ data: FaqCategoryEntity
+}
+
+type FaqCategoryEntityResponseCollection {
+ data: [FaqCategoryEntity!]!
+ meta: ResponseCollectionMeta!
+}
+
+input FaqCategoryFiltersInput {
+ and: [FaqCategoryFiltersInput]
+ createdAt: DateTimeFilterInput
+ faqs: FaqFiltersInput
+ id: IDFilterInput
+ locale: StringFilterInput
+ localizations: FaqCategoryFiltersInput
+ not: FaqCategoryFiltersInput
+ or: [FaqCategoryFiltersInput]
+ publishedAt: DateTimeFilterInput
+ slug: StringFilterInput
+ title: StringFilterInput
+ updatedAt: DateTimeFilterInput
+}
+
+input FaqCategoryInput {
+ faqs: [ID]
+ publishedAt: DateTime
+ slug: String
+ title: String
+}
+
+type FaqCategoryRelationResponseCollection {
+ data: [FaqCategoryEntity!]!
+}
+
+type FaqEntity {
+ attributes: Faq
+ id: ID
+}
+
+type FaqEntityResponse {
+ data: FaqEntity
+}
+
+type FaqEntityResponseCollection {
+ data: [FaqEntity!]!
+ meta: ResponseCollectionMeta!
+}
+
+input FaqFiltersInput {
+ and: [FaqFiltersInput]
+ body: StringFilterInput
+ createdAt: DateTimeFilterInput
+ faqCategory: FaqCategoryFiltersInput
+ id: IDFilterInput
+ locale: StringFilterInput
+ localizations: FaqFiltersInput
+ not: FaqFiltersInput
+ or: [FaqFiltersInput]
+ publishedAt: DateTimeFilterInput
+ title: StringFilterInput
+ updatedAt: DateTimeFilterInput
+}
+
+input FaqInput {
+ body: String
+ faqCategory: ID
+ publishedAt: DateTime
+ title: String
+}
+
+type FaqRelationResponseCollection {
+ data: [FaqEntity!]!
+}
+
input FileInfoInput {
alternativeText: String
caption: String
@@ -2444,7 +2592,7 @@ type GeneralRelationResponseCollection {
data: [GeneralEntity!]!
}
-union GenericMorph = Alert | BlogPost | ComponentAccordionItemsFlatText | ComponentAccordionItemsInstitution | ComponentAccordionItemsInstitutionNarrow | ComponentBlocksBlogPostLink | ComponentBlocksBookmarkLink | ComponentBlocksCommonLink | ComponentBlocksComparisonCard | ComponentBlocksComparisonItem | ComponentBlocksContactCard | ComponentBlocksDocListExtensions | ComponentBlocksFile | ComponentBlocksFileItem | ComponentBlocksFooterColumn | ComponentBlocksFooterContactItem | ComponentBlocksFooterSection | ComponentBlocksGalleryItem | ComponentBlocksHomepageBookmark | ComponentBlocksHomepageHighlightsItem | ComponentBlocksIconWithTitleAndDescription | ComponentBlocksInBa | ComponentBlocksNumericalListItem | ComponentBlocksPageLink | ComponentBlocksProsAndConsCard | ComponentBlocksSpaceInfo | ComponentBlocksSubpage | ComponentBlocksTestimonialItem | ComponentBlocksTimelineItem | ComponentBlocksTopServicesItem | ComponentBlocksVideo | ComponentGeneralHeader | ComponentGeneralHeaderLink | ComponentMenuMenuItem | ComponentMenuMenuLink | ComponentMenuMenuSection | ComponentOsItemsAdvancedAccordionDepartment | ComponentOsItemsAdvancedAccordionItem | ComponentOsItemsAdvancedAccordionSubItem | ComponentOsItemsAdvancedAccordionSubSubItem | ComponentSectionsAccordion | ComponentSectionsBanner | ComponentSectionsBlogPostsByCategory | ComponentSectionsBlogPostsByTags | ComponentSectionsBlogPostsList | ComponentSectionsCalculator | ComponentSectionsColumnedText | ComponentSectionsComparisonSection | ComponentSectionsContactsSection | ComponentSectionsDivider | ComponentSectionsFeaturedBlogPosts | ComponentSectionsFileList | ComponentSectionsGallery | ComponentSectionsHomepageEvents | ComponentSectionsHomepageHighlights | ComponentSectionsHomepageMayorAndCouncil | ComponentSectionsHomepageTabs | ComponentSectionsIconTitleDesc | ComponentSectionsIframe | ComponentSectionsInbaArticlesList | ComponentSectionsInbaReleases | ComponentSectionsLinks | ComponentSectionsNarrowText | ComponentSectionsNumericalList | ComponentSectionsOfficialBoard | ComponentSectionsOrganizationalStructure | ComponentSectionsProsAndConsSection | ComponentSectionsRegulations | ComponentSectionsRegulationsList | ComponentSectionsSpace | ComponentSectionsSubpageList | ComponentSectionsTestimonials | ComponentSectionsTextWithImage | ComponentSectionsTimeline | ComponentSectionsTopServices | ComponentSectionsVideos | ComponentSectionsWaves | ComponentTaxAdministratorsTaxAdministrator | Footer | General | Homepage | I18NLocale | InbaArticle | InbaRelease | InbaTag | Menu | Page | PageCategory | PageSubcategory | Regulation | Tag | TaxAdministratorsList | UploadFile | UploadFolder | UsersPermissionsPermission | UsersPermissionsRole | UsersPermissionsUser | Vzn
+union GenericMorph = Alert | BlogPost | ComponentAccordionItemsFlatText | ComponentAccordionItemsInstitution | ComponentAccordionItemsInstitutionNarrow | ComponentBlocksBlogPostLink | ComponentBlocksBookmarkLink | ComponentBlocksCommonLink | ComponentBlocksComparisonCard | ComponentBlocksComparisonItem | ComponentBlocksContactCard | ComponentBlocksDocListExtensions | ComponentBlocksFile | ComponentBlocksFileItem | ComponentBlocksFooterColumn | ComponentBlocksFooterContactItem | ComponentBlocksFooterSection | ComponentBlocksGalleryItem | ComponentBlocksHomepageBookmark | ComponentBlocksHomepageHighlightsItem | ComponentBlocksIconWithTitleAndDescription | ComponentBlocksInBa | ComponentBlocksNumericalListItem | ComponentBlocksPageLink | ComponentBlocksProsAndConsCard | ComponentBlocksSpaceInfo | ComponentBlocksSubpage | ComponentBlocksTestimonialItem | ComponentBlocksTimelineItem | ComponentBlocksTopServicesItem | ComponentBlocksVideo | ComponentGeneralHeader | ComponentGeneralHeaderLink | ComponentMenuMenuItem | ComponentMenuMenuLink | ComponentMenuMenuSection | ComponentOsItemsAdvancedAccordionDepartment | ComponentOsItemsAdvancedAccordionItem | ComponentOsItemsAdvancedAccordionSubItem | ComponentOsItemsAdvancedAccordionSubSubItem | ComponentSectionsAccordion | ComponentSectionsBanner | ComponentSectionsBlogPostsByCategory | ComponentSectionsBlogPostsByTags | ComponentSectionsBlogPostsList | ComponentSectionsCalculator | ComponentSectionsColumnedText | ComponentSectionsComparisonSection | ComponentSectionsContactsSection | ComponentSectionsDivider | ComponentSectionsFaqCategories | ComponentSectionsFaqs | ComponentSectionsFeaturedBlogPosts | ComponentSectionsFileList | ComponentSectionsGallery | ComponentSectionsHomepageEvents | ComponentSectionsHomepageHighlights | ComponentSectionsHomepageMayorAndCouncil | ComponentSectionsHomepageTabs | ComponentSectionsIconTitleDesc | ComponentSectionsIframe | ComponentSectionsInbaArticlesList | ComponentSectionsInbaReleases | ComponentSectionsLinks | ComponentSectionsNarrowText | ComponentSectionsNumericalList | ComponentSectionsOfficialBoard | ComponentSectionsOrganizationalStructure | ComponentSectionsProsAndConsSection | ComponentSectionsRegulations | ComponentSectionsRegulationsList | ComponentSectionsSpace | ComponentSectionsSubpageList | ComponentSectionsTestimonials | ComponentSectionsTextWithImage | ComponentSectionsTimeline | ComponentSectionsTopServices | ComponentSectionsVideos | ComponentSectionsWaves | ComponentTaxAdministratorsTaxAdministrator | Faq | FaqCategory | Footer | General | Homepage | I18NLocale | InbaArticle | InbaRelease | InbaTag | Menu | Page | PageCategory | PageSubcategory | Regulation | Tag | TaxAdministratorsList | UploadFile | UploadFolder | UsersPermissionsPermission | UsersPermissionsRole | UsersPermissionsUser | Vzn
type Homepage {
bookmarkTourists: ComponentBlocksHomepageBookmark
@@ -2896,6 +3044,10 @@ type Mutation {
createAlertLocalization(data: AlertInput, id: ID, locale: I18NLocaleCode): AlertEntityResponse
createBlogPost(data: BlogPostInput!, locale: I18NLocaleCode): BlogPostEntityResponse
createBlogPostLocalization(data: BlogPostInput, id: ID, locale: I18NLocaleCode): BlogPostEntityResponse
+ createFaq(data: FaqInput!, locale: I18NLocaleCode): FaqEntityResponse
+ createFaqCategory(data: FaqCategoryInput!, locale: I18NLocaleCode): FaqCategoryEntityResponse
+ createFaqCategoryLocalization(data: FaqCategoryInput, id: ID, locale: I18NLocaleCode): FaqCategoryEntityResponse
+ createFaqLocalization(data: FaqInput, id: ID, locale: I18NLocaleCode): FaqEntityResponse
createFooterLocalization(data: FooterInput, id: ID, locale: I18NLocaleCode): FooterEntityResponse
createGeneralLocalization(data: GeneralInput, id: ID, locale: I18NLocaleCode): GeneralEntityResponse
createHomepageLocalization(data: HomepageInput, id: ID, locale: I18NLocaleCode): HomepageEntityResponse
@@ -2925,6 +3077,8 @@ type Mutation {
createVzn(data: VznInput!): VznEntityResponse
deleteAlert(locale: I18NLocaleCode): AlertEntityResponse
deleteBlogPost(id: ID!, locale: I18NLocaleCode): BlogPostEntityResponse
+ deleteFaq(id: ID!, locale: I18NLocaleCode): FaqEntityResponse
+ deleteFaqCategory(id: ID!, locale: I18NLocaleCode): FaqCategoryEntityResponse
deleteFooter(locale: I18NLocaleCode): FooterEntityResponse
deleteGeneral(locale: I18NLocaleCode): GeneralEntityResponse
deleteHomepage(locale: I18NLocaleCode): HomepageEntityResponse
@@ -2966,6 +3120,8 @@ type Mutation {
resetPassword(code: String!, password: String!, passwordConfirmation: String!): UsersPermissionsLoginPayload
updateAlert(data: AlertInput!, locale: I18NLocaleCode): AlertEntityResponse
updateBlogPost(data: BlogPostInput!, id: ID!, locale: I18NLocaleCode): BlogPostEntityResponse
+ updateFaq(data: FaqInput!, id: ID!, locale: I18NLocaleCode): FaqEntityResponse
+ updateFaqCategory(data: FaqCategoryInput!, id: ID!, locale: I18NLocaleCode): FaqCategoryEntityResponse
updateFileInfo(id: ID!, info: FileInfoInput): UploadFileEntityResponse!
updateFooter(data: FooterInput!, locale: I18NLocaleCode): FooterEntityResponse
updateGeneral(data: GeneralInput!, locale: I18NLocaleCode): GeneralEntityResponse
@@ -3137,7 +3293,7 @@ type PageRelationResponseCollection {
data: [PageEntity!]!
}
-union PageSectionsDynamicZone = ComponentSectionsAccordion | ComponentSectionsBanner | ComponentSectionsBlogPostsByCategory | ComponentSectionsBlogPostsByTags | ComponentSectionsBlogPostsList | ComponentSectionsCalculator | ComponentSectionsColumnedText | ComponentSectionsComparisonSection | ComponentSectionsContactsSection | ComponentSectionsDivider | ComponentSectionsFeaturedBlogPosts | ComponentSectionsFileList | ComponentSectionsGallery | ComponentSectionsIconTitleDesc | ComponentSectionsIframe | ComponentSectionsInbaArticlesList | ComponentSectionsInbaReleases | ComponentSectionsLinks | ComponentSectionsNarrowText | ComponentSectionsNumericalList | ComponentSectionsOfficialBoard | ComponentSectionsOrganizationalStructure | ComponentSectionsProsAndConsSection | ComponentSectionsRegulations | ComponentSectionsRegulationsList | ComponentSectionsSpace | ComponentSectionsTestimonials | ComponentSectionsTextWithImage | ComponentSectionsTimeline | ComponentSectionsVideos | ComponentSectionsWaves | Error
+union PageSectionsDynamicZone = ComponentSectionsAccordion | ComponentSectionsBanner | ComponentSectionsBlogPostsByCategory | ComponentSectionsBlogPostsByTags | ComponentSectionsBlogPostsList | ComponentSectionsCalculator | ComponentSectionsColumnedText | ComponentSectionsComparisonSection | ComponentSectionsContactsSection | ComponentSectionsDivider | ComponentSectionsFaqCategories | ComponentSectionsFaqs | ComponentSectionsFeaturedBlogPosts | ComponentSectionsFileList | ComponentSectionsGallery | ComponentSectionsIconTitleDesc | ComponentSectionsIframe | ComponentSectionsInbaArticlesList | ComponentSectionsInbaReleases | ComponentSectionsLinks | ComponentSectionsNarrowText | ComponentSectionsNumericalList | ComponentSectionsOfficialBoard | ComponentSectionsOrganizationalStructure | ComponentSectionsProsAndConsSection | ComponentSectionsRegulations | ComponentSectionsRegulationsList | ComponentSectionsSpace | ComponentSectionsTestimonials | ComponentSectionsTextWithImage | ComponentSectionsTimeline | ComponentSectionsVideos | ComponentSectionsWaves | Error
scalar PageSectionsDynamicZoneInput
@@ -3218,6 +3374,10 @@ type Query {
alert(locale: I18NLocaleCode): AlertEntityResponse
blogPost(id: ID, locale: I18NLocaleCode): BlogPostEntityResponse
blogPosts(filters: BlogPostFiltersInput, locale: I18NLocaleCode, pagination: PaginationArg = {}, publicationState: PublicationState = LIVE, sort: [String] = []): BlogPostEntityResponseCollection
+ faq(id: ID, locale: I18NLocaleCode): FaqEntityResponse
+ faqCategories(filters: FaqCategoryFiltersInput, locale: I18NLocaleCode, pagination: PaginationArg = {}, publicationState: PublicationState = LIVE, sort: [String] = []): FaqCategoryEntityResponseCollection
+ faqCategory(id: ID, locale: I18NLocaleCode): FaqCategoryEntityResponse
+ faqs(filters: FaqFiltersInput, locale: I18NLocaleCode, pagination: PaginationArg = {}, publicationState: PublicationState = LIVE, sort: [String] = []): FaqEntityResponseCollection
footer(locale: I18NLocaleCode, publicationState: PublicationState = LIVE): FooterEntityResponse
general(locale: I18NLocaleCode): GeneralEntityResponse
homepage(locale: I18NLocaleCode, publicationState: PublicationState = LIVE): HomepageEntityResponse
diff --git a/strapi/src/api/faq-category/content-types/faq-category/schema.json b/strapi/src/api/faq-category/content-types/faq-category/schema.json
new file mode 100644
index 000000000..619c14a80
--- /dev/null
+++ b/strapi/src/api/faq-category/content-types/faq-category/schema.json
@@ -0,0 +1,44 @@
+{
+ "kind": "collectionType",
+ "collectionName": "faq_categories",
+ "info": {
+ "singularName": "faq-category",
+ "pluralName": "faq-categories",
+ "displayName": "faq category"
+ },
+ "options": {
+ "draftAndPublish": true
+ },
+ "pluginOptions": {
+ "i18n": {
+ "localized": true
+ }
+ },
+ "attributes": {
+ "title": {
+ "pluginOptions": {
+ "i18n": {
+ "localized": true
+ }
+ },
+ "type": "string",
+ "required": true
+ },
+ "slug": {
+ "pluginOptions": {
+ "i18n": {
+ "localized": true
+ }
+ },
+ "type": "uid",
+ "targetField": "title",
+ "required": true
+ },
+ "faqs": {
+ "type": "relation",
+ "relation": "oneToMany",
+ "target": "api::faq.faq",
+ "mappedBy": "faqCategory"
+ }
+ }
+}
diff --git a/strapi/src/api/faq-category/controllers/faq-category.ts b/strapi/src/api/faq-category/controllers/faq-category.ts
new file mode 100644
index 000000000..b565fcd56
--- /dev/null
+++ b/strapi/src/api/faq-category/controllers/faq-category.ts
@@ -0,0 +1,7 @@
+/**
+ * faq-category controller
+ */
+
+import { factories } from '@strapi/strapi'
+
+export default factories.createCoreController('api::faq-category.faq-category');
diff --git a/strapi/src/api/faq-category/routes/faq-category.ts b/strapi/src/api/faq-category/routes/faq-category.ts
new file mode 100644
index 000000000..eb55fd466
--- /dev/null
+++ b/strapi/src/api/faq-category/routes/faq-category.ts
@@ -0,0 +1,7 @@
+/**
+ * faq-category router
+ */
+
+import { factories } from '@strapi/strapi';
+
+export default factories.createCoreRouter('api::faq-category.faq-category');
diff --git a/strapi/src/api/faq-category/services/faq-category.ts b/strapi/src/api/faq-category/services/faq-category.ts
new file mode 100644
index 000000000..1f05cbe70
--- /dev/null
+++ b/strapi/src/api/faq-category/services/faq-category.ts
@@ -0,0 +1,7 @@
+/**
+ * faq-category service
+ */
+
+import { factories } from '@strapi/strapi';
+
+export default factories.createCoreService('api::faq-category.faq-category');
diff --git a/strapi/src/api/faq/content-types/faq/schema.json b/strapi/src/api/faq/content-types/faq/schema.json
new file mode 100644
index 000000000..d578493a5
--- /dev/null
+++ b/strapi/src/api/faq/content-types/faq/schema.json
@@ -0,0 +1,43 @@
+{
+ "kind": "collectionType",
+ "collectionName": "faqs",
+ "info": {
+ "singularName": "faq",
+ "pluralName": "faqs",
+ "displayName": "faq"
+ },
+ "options": {
+ "draftAndPublish": true
+ },
+ "pluginOptions": {
+ "i18n": {
+ "localized": true
+ }
+ },
+ "attributes": {
+ "title": {
+ "pluginOptions": {
+ "i18n": {
+ "localized": true
+ }
+ },
+ "type": "string",
+ "required": true
+ },
+ "body": {
+ "pluginOptions": {
+ "i18n": {
+ "localized": true
+ }
+ },
+ "type": "richtext",
+ "required": false
+ },
+ "faqCategory": {
+ "type": "relation",
+ "relation": "manyToOne",
+ "target": "api::faq-category.faq-category",
+ "inversedBy": "faqs"
+ }
+ }
+}
diff --git a/strapi/src/api/faq/controllers/faq.ts b/strapi/src/api/faq/controllers/faq.ts
new file mode 100644
index 000000000..fa1905038
--- /dev/null
+++ b/strapi/src/api/faq/controllers/faq.ts
@@ -0,0 +1,7 @@
+/**
+ * faq controller
+ */
+
+import { factories } from '@strapi/strapi'
+
+export default factories.createCoreController('api::faq.faq');
diff --git a/strapi/src/api/faq/routes/faq.ts b/strapi/src/api/faq/routes/faq.ts
new file mode 100644
index 000000000..e9f783cb2
--- /dev/null
+++ b/strapi/src/api/faq/routes/faq.ts
@@ -0,0 +1,7 @@
+/**
+ * faq router
+ */
+
+import { factories } from '@strapi/strapi';
+
+export default factories.createCoreRouter('api::faq.faq');
diff --git a/strapi/src/api/faq/services/faq.ts b/strapi/src/api/faq/services/faq.ts
new file mode 100644
index 000000000..5578d01fb
--- /dev/null
+++ b/strapi/src/api/faq/services/faq.ts
@@ -0,0 +1,7 @@
+/**
+ * faq service
+ */
+
+import { factories } from '@strapi/strapi';
+
+export default factories.createCoreService('api::faq.faq');
diff --git a/strapi/src/api/page/content-types/page/schema.json b/strapi/src/api/page/content-types/page/schema.json
index dea01616a..4bba674a9 100644
--- a/strapi/src/api/page/content-types/page/schema.json
+++ b/strapi/src/api/page/content-types/page/schema.json
@@ -119,6 +119,8 @@
"sections.comparison-section",
"sections.contacts-section",
"sections.divider",
+ "sections.faqs",
+ "sections.faq-categories",
"sections.featured-blog-posts",
"sections.file-list",
"sections.gallery",
diff --git a/strapi/src/components/sections/faq-categories.json b/strapi/src/components/sections/faq-categories.json
new file mode 100644
index 000000000..8634ed60d
--- /dev/null
+++ b/strapi/src/components/sections/faq-categories.json
@@ -0,0 +1,20 @@
+{
+ "collectionName": "components_sections_faq_categories",
+ "info": {
+ "displayName": "faq categories"
+ },
+ "options": {},
+ "attributes": {
+ "title": {
+ "type": "string"
+ },
+ "text": {
+ "type": "text"
+ },
+ "faqCategories": {
+ "type": "relation",
+ "relation": "oneToMany",
+ "target": "api::faq-category.faq-category"
+ }
+ }
+}
diff --git a/strapi/src/components/sections/faqs.json b/strapi/src/components/sections/faqs.json
new file mode 100644
index 000000000..66d122ae1
--- /dev/null
+++ b/strapi/src/components/sections/faqs.json
@@ -0,0 +1,21 @@
+{
+ "collectionName": "components_sections_faqs",
+ "info": {
+ "displayName": "faqs",
+ "description": ""
+ },
+ "options": {},
+ "attributes": {
+ "title": {
+ "type": "string"
+ },
+ "text": {
+ "type": "text"
+ },
+ "faqs": {
+ "type": "relation",
+ "relation": "oneToMany",
+ "target": "api::faq.faq"
+ }
+ }
+}