Add a method to get all web pages
This commit is contained in:
318
astro/src/content/pages/pages.ts
Normal file
318
astro/src/content/pages/pages.ts
Normal file
@@ -0,0 +1,318 @@
|
||||
import { createDirectusConnection } from "@/lib/directus";
|
||||
import { print } from 'graphql';
|
||||
import { formatDate } from "@/lib/dates";
|
||||
import getAllPages from "@/graphql/pages/getAllPages.graphql";
|
||||
|
||||
export async function getAllWebpages(): Promise<WebPage[]> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getAllPages), {
|
||||
date: formatDate(new Date(), "%Y-%M-%D")
|
||||
});
|
||||
|
||||
let pages: WebPage[] = [];
|
||||
|
||||
result["Pages"].forEach((pageRecord: any) => {
|
||||
let dates: string[] = [
|
||||
pageRecord["date_created"],
|
||||
pageRecord["date_updated"],
|
||||
pageRecord["search_engine"][0]["date_created"],
|
||||
pageRecord["search_engine"][0]["date_updated"]
|
||||
];
|
||||
|
||||
const searchEngine = pageRecord["search_engine"][0];
|
||||
|
||||
let components: WebpageComponent[] = [];
|
||||
|
||||
pageRecord["components"].forEach((componentRecord: any) => {
|
||||
const component = componentRecord["item"];
|
||||
|
||||
switch (componentRecord["item"]["__typename"]) {
|
||||
case "Hero":
|
||||
let heroComponent: HeroComponent = {
|
||||
component: "Hero",
|
||||
id: component["hero_id"],
|
||||
title: component["hero_title"],
|
||||
text: component["hero_text"],
|
||||
backgroundImage: {
|
||||
url: component["background_image"]["filename_disk"],
|
||||
width: component["background_image"]["width"],
|
||||
height: component["background_image"]["height"]
|
||||
}
|
||||
};
|
||||
|
||||
components.push(heroComponent);
|
||||
dates.push(component["hero_created"]);
|
||||
dates.push(component["hero_updated"]);
|
||||
dates.push(component["background_image"]["created_on"]);
|
||||
|
||||
break;
|
||||
case "Text_With_Side_Image":
|
||||
let textWithImageComponent: TextWithImageComponent = {
|
||||
component: "TextWithImage",
|
||||
id: component["twsi_id"],
|
||||
title: component["twsi_title"],
|
||||
text: component["twsi_text"],
|
||||
imageSide: component["twsi_image_side"],
|
||||
image: {
|
||||
url: component["image"]["filename_disk"],
|
||||
width: component["image"]["width"],
|
||||
height: component["image"]["height"]
|
||||
}
|
||||
};
|
||||
|
||||
components.push(textWithImageComponent);
|
||||
dates.push(component["twsi_created"]);
|
||||
dates.push(component["twsi_updated"]);
|
||||
dates.push(component["image"]["created_on"]);
|
||||
|
||||
break;
|
||||
case "Wall_Of_Text":
|
||||
let wallOfTextComponent: WallOfTextComponent = {
|
||||
component: "WallOfText",
|
||||
id: component["wot_id"],
|
||||
title: component["wot_title"],
|
||||
text: component["wot_text"]
|
||||
};
|
||||
|
||||
components.push(wallOfTextComponent);
|
||||
dates.push(component["wot_created"]);
|
||||
dates.push(component["wot_updated"]);
|
||||
|
||||
break;
|
||||
case "Frequently_Asked_Questions":
|
||||
let faqComponent: FrequentlyAskedQuestionsComponent = {
|
||||
component: "FrequentlyAskedQuestions",
|
||||
id: component["faq_id"],
|
||||
title: component["faq_title"],
|
||||
text: component["faq_text"],
|
||||
questions: []
|
||||
};
|
||||
|
||||
component["questions"].forEach((faqQuestionRecord: any) => {
|
||||
faqComponent.questions.push({
|
||||
id: faqQuestionRecord["id"],
|
||||
question: faqQuestionRecord["question"],
|
||||
answer: faqQuestionRecord["answer"]
|
||||
});
|
||||
|
||||
dates.push(faqQuestionRecord["date_created"]);
|
||||
dates.push(faqQuestionRecord["date_updated"]);
|
||||
});
|
||||
|
||||
components.push(faqComponent);
|
||||
dates.push(component["faq_created"]);
|
||||
dates.push(component["faq_updated"]);
|
||||
|
||||
break;
|
||||
case "Upcoming_Events":
|
||||
let upcomingEventsComponent: UpcomingEventsComponent = {
|
||||
component: "UpcomingEvents",
|
||||
id: component["ue_id"],
|
||||
title: component["ue_title"],
|
||||
text: component["ue_text"],
|
||||
events: []
|
||||
};
|
||||
|
||||
component["events"].forEach((eventRecord: any) => {
|
||||
upcomingEventsComponent.events.push({
|
||||
id: eventRecord["id"],
|
||||
title: eventRecord["title"],
|
||||
description: eventRecord["description"],
|
||||
location: eventRecord["location"],
|
||||
mapLocation: [
|
||||
eventRecord["map_location"]["coordinates"][1],
|
||||
eventRecord["map_location"]["coordinates"][0]
|
||||
],
|
||||
startDate: eventRecord["start_date"],
|
||||
endDate: eventRecord["end_date"]
|
||||
});
|
||||
|
||||
dates.push(eventRecord["date_created"]);
|
||||
dates.push(eventRecord["date_updated"]);
|
||||
});
|
||||
|
||||
components.push(upcomingEventsComponent);
|
||||
dates.push(component["ue_created"]);
|
||||
dates.push(component["ue_updated"]);
|
||||
|
||||
break;
|
||||
case "Equipment_Table":
|
||||
let equipmentTableComponent: EquipmentTableComponent = {
|
||||
component: "EquipmentTable",
|
||||
id: component["et_id"],
|
||||
title: component["et_title"],
|
||||
text: component["et_text"],
|
||||
items: []
|
||||
};
|
||||
|
||||
component["items"].forEach((itemRecord: any) => {
|
||||
equipmentTableComponent.items.push({
|
||||
id: itemRecord["id"],
|
||||
title: itemRecord["title"],
|
||||
text: itemRecord["text"],
|
||||
icon: {
|
||||
url: itemRecord["icon"]["filename_disk"],
|
||||
width: itemRecord["icon"]["width"],
|
||||
height: itemRecord["icon"]["height"]
|
||||
}
|
||||
});
|
||||
|
||||
dates.push(itemRecord["date_created"]);
|
||||
dates.push(itemRecord["date_updated"]);
|
||||
dates.push(itemRecord["icon"]["created_on"]);
|
||||
});
|
||||
|
||||
components.push(equipmentTableComponent);
|
||||
dates.push(component["et_created"]);
|
||||
dates.push(component["et_updated"]);
|
||||
|
||||
break;
|
||||
case "Review_List":
|
||||
let reviewsComponent: ReviewListComponent = {
|
||||
component: "Reviews",
|
||||
id: component["rl_id"],
|
||||
title: component["rl_title"],
|
||||
text: component["rl_text"],
|
||||
reviews: []
|
||||
};
|
||||
|
||||
component["reviews"].forEach((reviewRecord: any) => {
|
||||
reviewsComponent.reviews.push({
|
||||
id: reviewRecord["id"],
|
||||
name: reviewRecord["name"],
|
||||
review: reviewRecord["review"],
|
||||
stars: reviewRecord["stars"],
|
||||
date: reviewRecord["date"],
|
||||
thumbnail: {
|
||||
url: reviewRecord["thumbnail"]["filename_disk"],
|
||||
width: reviewRecord["thumbnail"]["width"],
|
||||
height: reviewRecord["thumbnail"]["height"]
|
||||
}
|
||||
});
|
||||
|
||||
dates.push(reviewRecord["date_created"]);
|
||||
dates.push(reviewRecord["date_updated"]);
|
||||
dates.push(reviewRecord["thumbnail"]["created_on"]);
|
||||
});
|
||||
|
||||
components.push(reviewsComponent);
|
||||
dates.push(component["rl_created"]);
|
||||
dates.push(component["rl_updated"]);
|
||||
|
||||
break;
|
||||
case "Contact":
|
||||
let contactComponent: ContactComponent = {
|
||||
component: "Contact",
|
||||
id: component["c_id"],
|
||||
title: component["c_title"],
|
||||
text: component["c_text"],
|
||||
methods: []
|
||||
};
|
||||
|
||||
component["methods"].forEach((contactMethodRecord: any) => {
|
||||
contactComponent.methods.push({
|
||||
id: contactMethodRecord["id"],
|
||||
title: contactMethodRecord["title"],
|
||||
text: contactMethodRecord["text"],
|
||||
color: contactMethodRecord["color"],
|
||||
icon: {
|
||||
url: contactMethodRecord["icon"]["filename_disk"],
|
||||
width: contactMethodRecord["icon"]["width"],
|
||||
height: contactMethodRecord["icon"]["height"]
|
||||
}
|
||||
});
|
||||
|
||||
dates.push(contactMethodRecord["date_created"]);
|
||||
dates.push(contactMethodRecord["date_updated"]);
|
||||
dates.push(contactMethodRecord["icon"]["created_on"]);
|
||||
});
|
||||
|
||||
components.push(contactComponent);
|
||||
dates.push(component["c_created"]);
|
||||
dates.push(component["c_updated"]);
|
||||
|
||||
break;
|
||||
case "Last_Blogs":
|
||||
let lastBlogsComponent: LastBlogsComponent = {
|
||||
component: "LastBlogs",
|
||||
id: component["lb_id"],
|
||||
title: component["lb_title"],
|
||||
readMoreButtonText: component["lb_read_more_button_text"],
|
||||
amount: component["lb_amount"]
|
||||
};
|
||||
|
||||
components.push(lastBlogsComponent);
|
||||
dates.push(component["lb_created"]);
|
||||
dates.push(component["lb_updated"]);
|
||||
|
||||
break;
|
||||
case "Last_Projects":
|
||||
let lastProjectsComponent: LastProjectsComponent = {
|
||||
component: "LastProjects",
|
||||
id: component["lp_id"],
|
||||
title: component["lp_title"],
|
||||
readMoreButtonText: component["lp_read_more_button_text"],
|
||||
amount: component["lp_amount"]
|
||||
};
|
||||
|
||||
components.push(lastProjectsComponent);
|
||||
dates.push(component["lp_created"]);
|
||||
dates.push(component["lp_updated"]);
|
||||
|
||||
break;
|
||||
case "Last_Galleries":
|
||||
let lastGalleriesComponent: LastGalleriesComponent = {
|
||||
component: "LastGalleries",
|
||||
id: component["lg_id"],
|
||||
title: component["lg_title"],
|
||||
readMoreButtonText: component["lg_read_more_button_text"],
|
||||
amount: component["lg_amount"]
|
||||
};
|
||||
|
||||
components.push(lastGalleriesComponent);
|
||||
dates.push(component["lg_created"]);
|
||||
dates.push(component["lg_updated"]);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
let lastModified: Date;
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
let page: WebPage = {
|
||||
id: pageRecord["id"],
|
||||
lastModified: lastModified,
|
||||
url: pageRecord["url"],
|
||||
searchEngine: {
|
||||
title: searchEngine["title"],
|
||||
description: searchEngine["description"],
|
||||
canonical: searchEngine["canonical"],
|
||||
allowCrawlers: searchEngine["allow_crawler"],
|
||||
priority: searchEngine["priority"],
|
||||
thumbnail: {
|
||||
url: searchEngine["thumbnail"]["filename_disk"],
|
||||
height: searchEngine["thumbnail"]["height"],
|
||||
width: searchEngine["thumbnail"]["width"]
|
||||
}
|
||||
},
|
||||
components: components
|
||||
}
|
||||
|
||||
pages.push(page);
|
||||
});
|
||||
|
||||
return pages;
|
||||
}
|
||||
216
astro/src/graphql/pages/getAllPages.graphql
Normal file
216
astro/src/graphql/pages/getAllPages.graphql
Normal file
@@ -0,0 +1,216 @@
|
||||
query getAllPages($date: String!) {
|
||||
Pages(filter: { status: { _eq: "published" } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
url,
|
||||
search_engine {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
canonical,
|
||||
allow_crawler,
|
||||
priority
|
||||
},
|
||||
components {
|
||||
id,
|
||||
__typename,
|
||||
item {
|
||||
__typename,
|
||||
|
||||
...on Hero {
|
||||
hero_id: id,
|
||||
hero_created: date_created,
|
||||
hero_updated: date_updated,
|
||||
hero_title: title,
|
||||
hero_text: subtext,
|
||||
background_image {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
hero_image: background_image {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
background_image {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
...on Text_With_Side_Image {
|
||||
twsi_id: id,
|
||||
twsi_created: date_created,
|
||||
twsi_updated: date_updated,
|
||||
twsi_title: title,
|
||||
twsi_text: text,
|
||||
image {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
twsi_image_side: image_side
|
||||
}
|
||||
|
||||
...on Wall_Of_Text {
|
||||
wot_id: id,
|
||||
wot_created: date_created,
|
||||
wot_updated: date_updated,
|
||||
wot_title: title,
|
||||
wot_text: text
|
||||
}
|
||||
|
||||
...on Frequently_Asked_Questions {
|
||||
faq_id: id,
|
||||
faq_created: date_created,
|
||||
faq_updated: date_updated,
|
||||
faq_title: title,
|
||||
faq_text: text,
|
||||
questions {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
question,
|
||||
answer
|
||||
}
|
||||
}
|
||||
|
||||
...on Upcoming_Events {
|
||||
ue_id: id,
|
||||
ue_created: date_created,
|
||||
ue_updated: date_updated,
|
||||
ue_title: title,
|
||||
ue_text: text,
|
||||
events(filter: { start_date: { _gte: $date } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
location,
|
||||
map_location,
|
||||
start_date,
|
||||
end_date
|
||||
}
|
||||
}
|
||||
|
||||
...on Equipment_Table {
|
||||
et_id: id,
|
||||
et_created: date_created,
|
||||
et_updated: date_updated,
|
||||
et_title: title,
|
||||
et_text: text,
|
||||
items {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
text,
|
||||
icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
...on Review_List {
|
||||
rl_id: id,
|
||||
rl_created: date_created,
|
||||
rl_updated: date_updated,
|
||||
rl_title: title,
|
||||
rl_text: text,
|
||||
reviews(sort: ["date"], filter: { status: { _eq: "published" } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
name,
|
||||
review,
|
||||
date,
|
||||
stars,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
...on Contact {
|
||||
c_id: id,
|
||||
c_created: date_created,
|
||||
c_updated: date_updated,
|
||||
c_title: title,
|
||||
c_text: text,
|
||||
methods {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
text,
|
||||
icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
...on Last_Blogs {
|
||||
lb_id: id,
|
||||
lb_created: date_created,
|
||||
lb_updated: date_updated,
|
||||
lb_title: title,
|
||||
lb_read_more_button_text: read_more_button_text,
|
||||
lb_amount: amount
|
||||
}
|
||||
|
||||
...on Last_Projects {
|
||||
lp_id: id,
|
||||
lp_created: date_created,
|
||||
lp_updated: date_updated,
|
||||
lp_title: title,
|
||||
lp_read_more_button_text: read_more_button_text,
|
||||
lp_amount: amount
|
||||
}
|
||||
|
||||
...on Last_Galleries {
|
||||
lg_id: id,
|
||||
lg_created: date_created,
|
||||
lg_updated: date_updated,
|
||||
lg_title: title,
|
||||
lg_read_more_button_text: read_more_button_text,
|
||||
lg_amount: amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
---
|
||||
import { getAllWebpages } from "@/content/pages/pages";
|
||||
import { getAllAlbums } from "@/content/photos/albums";
|
||||
import { getSettings } from "@/content/settings/settings"
|
||||
import WebpageLayout from "@/layouts/WebpageLayout.astro";
|
||||
|
||||
const settings = await getSettings();
|
||||
const albums = await getAllAlbums(settings);
|
||||
const webpages = await getAllWebpages();
|
||||
---
|
||||
|
||||
<WebpageLayout>
|
||||
|
||||
16
astro/src/types/components/contact.d.ts
vendored
Normal file
16
astro/src/types/components/contact.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
type ContactComponent = {
|
||||
component: "Contact";
|
||||
|
||||
id: string;
|
||||
title: string;
|
||||
text: string;
|
||||
methods: ContactMethod[];
|
||||
}
|
||||
|
||||
type ContactMethod = {
|
||||
id: string;
|
||||
title: string;
|
||||
text: string;
|
||||
color: string;
|
||||
icon: PhotoProps;
|
||||
}
|
||||
15
astro/src/types/components/equipment.d.ts
vendored
Normal file
15
astro/src/types/components/equipment.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
type EquipmentTableComponent = {
|
||||
component: "EquipmentTable";
|
||||
|
||||
id: string;
|
||||
title: string;
|
||||
text: string;
|
||||
items: EquipmentTableItem[];
|
||||
}
|
||||
|
||||
type EquipmentTableItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
text: string;
|
||||
icon: PhotoProps;
|
||||
}
|
||||
18
astro/src/types/components/events.d.ts
vendored
Normal file
18
astro/src/types/components/events.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
type UpcomingEventsComponent = {
|
||||
component: "UpcomingEvents";
|
||||
|
||||
id: string;
|
||||
title: string;
|
||||
text: string;
|
||||
events: UpcomingEvent[];
|
||||
}
|
||||
|
||||
type UpcomingEvent = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
location: string;
|
||||
mapLocation: [number, number];
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
}
|
||||
14
astro/src/types/components/faq.d.ts
vendored
Normal file
14
astro/src/types/components/faq.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
type FrequentlyAskedQuestionsComponent = {
|
||||
component: "FrequentlyAskedQuestions";
|
||||
|
||||
id: string;
|
||||
title: string;
|
||||
text: string;
|
||||
questions: FrequentlyAskedQuestion[];
|
||||
}
|
||||
|
||||
type FrequentlyAskedQuestion = {
|
||||
id: string;
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
8
astro/src/types/components/hero.d.ts
vendored
Normal file
8
astro/src/types/components/hero.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
type HeroComponent = {
|
||||
component: "Hero";
|
||||
|
||||
id: string;
|
||||
title: string;
|
||||
text: string;
|
||||
backgroundImage: PhotoProps;
|
||||
}
|
||||
26
astro/src/types/components/lastContent.d.ts
vendored
Normal file
26
astro/src/types/components/lastContent.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
type LastBlogsComponent = {
|
||||
component: "LastBlogs";
|
||||
|
||||
id: string;
|
||||
title: string;
|
||||
readMoreButtonText: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
type LastProjectsComponent = {
|
||||
component: "LastProjects";
|
||||
|
||||
id: string;
|
||||
title: string;
|
||||
readMoreButtonText: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
type LastGalleriesComponent = {
|
||||
component: "LastGalleries";
|
||||
|
||||
id: string;
|
||||
title: string;
|
||||
readMoreButtonText: string;
|
||||
amount: number;
|
||||
}
|
||||
17
astro/src/types/components/reviews.d.ts
vendored
Normal file
17
astro/src/types/components/reviews.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
type ReviewListComponent = {
|
||||
component: "Reviews";
|
||||
|
||||
id: string;
|
||||
title: string;
|
||||
text: string;
|
||||
reviews: Review[];
|
||||
}
|
||||
|
||||
type Review = {
|
||||
id: string;
|
||||
name: string;
|
||||
review: string;
|
||||
date: string;
|
||||
stars: number;
|
||||
thumbnail: PhotoProps;
|
||||
}
|
||||
9
astro/src/types/components/textWithImage.d.ts
vendored
Normal file
9
astro/src/types/components/textWithImage.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
type TextWithImageComponent = {
|
||||
component: "TextWithImage";
|
||||
|
||||
id: string;
|
||||
title: string;
|
||||
text: string;
|
||||
image: PhotoProps;
|
||||
imageSide: "left" | "right";
|
||||
}
|
||||
7
astro/src/types/components/wallOfText.d.ts
vendored
Normal file
7
astro/src/types/components/wallOfText.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
type WallOfTextComponent = {
|
||||
component: "WallOfText";
|
||||
|
||||
id: string;
|
||||
title: string;
|
||||
text: string;
|
||||
}
|
||||
21
astro/src/types/pages/page.d.ts
vendored
Normal file
21
astro/src/types/pages/page.d.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
type WebPage = {
|
||||
id: string;
|
||||
lastModified: Date;
|
||||
|
||||
url: string;
|
||||
searchEngine: SearchEngine;
|
||||
components: WebpageComponent[];
|
||||
}
|
||||
|
||||
type WebpageComponent =
|
||||
ContactComponent |
|
||||
EquipmentTableComponent |
|
||||
UpcomingEventsComponent |
|
||||
FrequentlyAskedQuestionsComponent |
|
||||
HeroComponent |
|
||||
LastBlogsComponent |
|
||||
LastProjectsComponent |
|
||||
LastGalleriesComponent |
|
||||
ReviewListComponent |
|
||||
TextWithImageComponent |
|
||||
WallOfTextComponent;
|
||||
Reference in New Issue
Block a user