commit
681a559641
@ -1,7 +1,8 @@
|
||||
import { withPayload } from "@payloadcms/next/withPayload";
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
export default withPayload(nextConfig);
|
||||
|
14
package.json
14
package.json
@ -2,14 +2,26 @@
|
||||
"name": "dynamic-realty-next",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"payload:generate:types": "payload generate:types",
|
||||
"payload:generate:importMap": "payload generate:importMap"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/db-postgres": "^3.35.1",
|
||||
"@payloadcms/next": "^3.35.1",
|
||||
"@payloadcms/payload-cloud": "^3.35.1",
|
||||
"@payloadcms/richtext-lexical": "^3.35.1",
|
||||
"@payloadcms/storage-s3": "^3.35.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"graphql": "^16.8.1",
|
||||
"next": "15.3.0",
|
||||
"payload": "^3.35.1",
|
||||
"qs-esm": "^7.0.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"swiper": "^11.2.6"
|
||||
|
@ -3751,8 +3751,7 @@ html .page .divider-secondary::after {
|
||||
|
||||
.button-primary:hover, .button-primary:active {
|
||||
color: #151515;
|
||||
background-color: #fdde52;
|
||||
border-color: #fdde52;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.button-primary.button-ujarak::before {
|
||||
@ -3789,8 +3788,7 @@ html .page .divider-secondary::after {
|
||||
|
||||
.button-primary:hover, .button-primary:active {
|
||||
color: #151515;
|
||||
background-color: #fdde52;
|
||||
border-color: #fdde52;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.button-primary.button-ujarak::before {
|
||||
@ -4159,7 +4157,8 @@ input:-webkit-autofill ~ .form-validation {
|
||||
-webkit-appearance: none;
|
||||
transition: .3s ease-in-out;
|
||||
letter-spacing: 0;
|
||||
/* border: 1px solid #f3f4f9; */
|
||||
border: 1px solid #f3f4f9;
|
||||
border-color: #f3f4f9!;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 275 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 542 KiB |
Binary file not shown.
Before Width: | Height: | Size: 103 KiB |
@ -1084,91 +1084,91 @@ $(function () {
|
||||
}
|
||||
|
||||
// RD Search
|
||||
if (plugins.search.length || plugins.searchResults) {
|
||||
var handler = "bat/rd-search.php";
|
||||
var defaultTemplate = '<h5 class="search-title"><a target="_top" href="#{href}" class="search-link">#{title}</a></h5>' +
|
||||
'<p>...#{token}...</p>' +
|
||||
'<p class="match"><em>Terms matched: #{count} - URL: #{href}</em></p>';
|
||||
var defaultFilter = '*.html';
|
||||
// if (plugins.search.length || plugins.searchResults) {
|
||||
// var handler = "bat/rd-search.php";
|
||||
// var defaultTemplate = '<h5 class="search-title"><a target="_top" href="#{href}" class="search-link">#{title}</a></h5>' +
|
||||
// '<p>...#{token}...</p>' +
|
||||
// '<p class="match"><em>Terms matched: #{count} - URL: #{href}</em></p>';
|
||||
// var defaultFilter = '*.html';
|
||||
|
||||
if (plugins.search.length) {
|
||||
for (var i = 0; i < plugins.search.length; i++) {
|
||||
var searchItem = $(plugins.search[i]),
|
||||
options = {
|
||||
element: searchItem,
|
||||
filter: (searchItem.attr('data-search-filter')) ? searchItem.attr('data-search-filter') : defaultFilter,
|
||||
template: (searchItem.attr('data-search-template')) ? searchItem.attr('data-search-template') : defaultTemplate,
|
||||
live: (searchItem.attr('data-search-live')) ? searchItem.attr('data-search-live') : false,
|
||||
liveCount: (searchItem.attr('data-search-live-count')) ? parseInt(searchItem.attr('data-search-live'), 10) : 4,
|
||||
current: 0, processed: 0, timer: {}
|
||||
};
|
||||
// if (plugins.search.length) {
|
||||
// for (var i = 0; i < plugins.search.length; i++) {
|
||||
// var searchItem = $(plugins.search[i]),
|
||||
// options = {
|
||||
// element: searchItem,
|
||||
// filter: (searchItem.attr('data-search-filter')) ? searchItem.attr('data-search-filter') : defaultFilter,
|
||||
// template: (searchItem.attr('data-search-template')) ? searchItem.attr('data-search-template') : defaultTemplate,
|
||||
// live: (searchItem.attr('data-search-live')) ? searchItem.attr('data-search-live') : false,
|
||||
// liveCount: (searchItem.attr('data-search-live-count')) ? parseInt(searchItem.attr('data-search-live'), 10) : 4,
|
||||
// current: 0, processed: 0, timer: {}
|
||||
// };
|
||||
|
||||
var $toggle = $('.rd-navbar-search-toggle');
|
||||
if ($toggle.length) {
|
||||
$toggle.on('click', (function (searchItem) {
|
||||
return function () {
|
||||
if (!($(this).hasClass('active'))) {
|
||||
searchItem.find('input').val('').trigger('propertychange');
|
||||
}
|
||||
}
|
||||
})(searchItem));
|
||||
}
|
||||
// var $toggle = $('.rd-navbar-search-toggle');
|
||||
// if ($toggle.length) {
|
||||
// $toggle.on('click', (function (searchItem) {
|
||||
// return function () {
|
||||
// if (!($(this).hasClass('active'))) {
|
||||
// searchItem.find('input').val('').trigger('propertychange');
|
||||
// }
|
||||
// }
|
||||
// })(searchItem));
|
||||
// }
|
||||
|
||||
if (options.live) {
|
||||
var clearHandler = false;
|
||||
// if (options.live) {
|
||||
// var clearHandler = false;
|
||||
|
||||
searchItem.find('input').on("input propertychange", $.proxy(function () {
|
||||
this.term = this.element.find('input').val().trim();
|
||||
this.spin = this.element.find('.input-group-addon');
|
||||
// searchItem.find('input').on("input propertychange", $.proxy(function () {
|
||||
// this.term = this.element.find('input').val().trim();
|
||||
// this.spin = this.element.find('.input-group-addon');
|
||||
|
||||
clearTimeout(this.timer);
|
||||
// clearTimeout(this.timer);
|
||||
|
||||
if (this.term.length > 2) {
|
||||
this.timer = setTimeout(liveSearch(this), 200);
|
||||
// if (this.term.length > 2) {
|
||||
// this.timer = setTimeout(liveSearch(this), 200);
|
||||
|
||||
if (clearHandler === false) {
|
||||
clearHandler = true;
|
||||
// if (clearHandler === false) {
|
||||
// clearHandler = true;
|
||||
|
||||
$body.on("click", function (e) {
|
||||
if ($(e.toElement).parents('.rd-search').length === 0) {
|
||||
$('#rd-search-results-live').addClass('cleared').html('');
|
||||
}
|
||||
})
|
||||
}
|
||||
// $body.on("click", function (e) {
|
||||
// if ($(e.toElement).parents('.rd-search').length === 0) {
|
||||
// $('#rd-search-results-live').addClass('cleared').html('');
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
} else if (this.term.length === 0) {
|
||||
$('#' + this.live).addClass('cleared').html('');
|
||||
}
|
||||
}, options, this));
|
||||
}
|
||||
// } else if (this.term.length === 0) {
|
||||
// $('#' + this.live).addClass('cleared').html('');
|
||||
// }
|
||||
// }, options, this));
|
||||
// }
|
||||
|
||||
searchItem.submit($.proxy(function () {
|
||||
$('<input />').attr('type', 'hidden')
|
||||
.attr('name', "filter")
|
||||
.attr('value', this.filter)
|
||||
.appendTo(this.element);
|
||||
return true;
|
||||
}, options, this))
|
||||
}
|
||||
}
|
||||
// searchItem.submit($.proxy(function () {
|
||||
// $('<input />').attr('type', 'hidden')
|
||||
// .attr('name', "filter")
|
||||
// .attr('value', this.filter)
|
||||
// .appendTo(this.element);
|
||||
// return true;
|
||||
// }, options, this))
|
||||
// }
|
||||
// }
|
||||
|
||||
if (plugins.searchResults.length) {
|
||||
var regExp = /\?.*s=([^&]+)\&filter=([^&]+)/g;
|
||||
var match = regExp.exec(location.search);
|
||||
// if (plugins.searchResults.length) {
|
||||
// var regExp = /\?.*s=([^&]+)\&filter=([^&]+)/g;
|
||||
// var match = regExp.exec(location.search);
|
||||
|
||||
if (match !== null) {
|
||||
$.get(handler, {
|
||||
s: decodeURI(match[1]),
|
||||
dataType: "html",
|
||||
filter: match[2],
|
||||
template: defaultTemplate,
|
||||
live: ''
|
||||
}, function (data) {
|
||||
plugins.searchResults.html(data);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (match !== null) {
|
||||
// $.get(handler, {
|
||||
// s: decodeURI(match[1]),
|
||||
// dataType: "html",
|
||||
// filter: match[2],
|
||||
// template: defaultTemplate,
|
||||
// live: ''
|
||||
// }, function (data) {
|
||||
// plugins.searchResults.html(data);
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Swiper
|
||||
function makeInterLeaveEffectOptions(interleaveOffset) {
|
||||
|
190
src/app/(main)/blog/[slug]/page.tsx
Normal file
190
src/app/(main)/blog/[slug]/page.tsx
Normal file
@ -0,0 +1,190 @@
|
||||
import HeroImage from "@/components/HeroImage";
|
||||
import { getDefaultMetadata } from "@/utils/metadata";
|
||||
import { Metadata } from "next";
|
||||
|
||||
const metaDesc =
|
||||
"Explore the latest insights, news, and resources on the Dynamic Realty blog. Read our articles today.";
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const metadata = await getDefaultMetadata();
|
||||
metadata.title = `Blog - ${metadata.openGraph?.siteName}`;
|
||||
metadata.description = metaDesc;
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export default function BlogDetail() {
|
||||
return (
|
||||
<>
|
||||
<HeroImage />
|
||||
|
||||
<section className="section section-md bg-colorSection1">
|
||||
<div className="container">
|
||||
<div className="row justify-content-lg-center">
|
||||
<div className="col-lg-8">
|
||||
<article className="blog-post-solo">
|
||||
<div className="blog-post-solo-part">
|
||||
<p>
|
||||
Showcasing a warm, traditional-style exterior and the highest caliber of contemporary European
|
||||
finishes throughout, towering glass doors open to grand-scale living spaces.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="blog-post-solo-footer">
|
||||
<div className="blog-post-solo-footer-left">
|
||||
<ul className="blog-post-solo-footer-list">
|
||||
<li>
|
||||
<span className="icon mdi mdi-clock"></span>
|
||||
<a href="#">February 10, 2021</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="blog-post-solo-footer-right">
|
||||
<ul className="blog-post-solo-footer-list-1">
|
||||
<li>
|
||||
<span>Share this post</span>
|
||||
</li>
|
||||
<li>
|
||||
<a className="icon icon-circle icon-rounded icon-5 fa-facebook" href="#"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="icon icon-circle icon-rounded icon-4 fa-google-plus" href="#"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="icon icon-circle icon-rounded icon-6 fa-twitter" href="#"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="icon icon-circle icon-rounded icon-6 fa-pinterest-p" href="#"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="post-simple-group">
|
||||
<div className="post-simple-group-title">
|
||||
<h6>Recent Posts</h6>
|
||||
</div>
|
||||
<div className="post-simple-group-divider"></div>
|
||||
<div className="row row-30">
|
||||
<div className="col-sm-6">
|
||||
<article className="post-simple">
|
||||
<div className="post-simple-img">
|
||||
<img src="/images/blog-post-03-736x540.jpg" alt="" width="736" height="540" />
|
||||
</div>
|
||||
<div className="post-simple-body">
|
||||
<div className="post-simple-title">
|
||||
<h4>
|
||||
<a href="#">Turks and Caicos Villa to be Sold for Record $7.6M</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div className="post-simple-time">
|
||||
<span className="icon mdi mdi-clock"></span>
|
||||
<a className="time" href="#">
|
||||
March 15, 2021
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<article className="post-simple">
|
||||
<div className="post-simple-img">
|
||||
<img src="/images/blog-post-04-736x540.jpg" alt="" width="736" height="540" />
|
||||
</div>
|
||||
<div className="post-simple-body">
|
||||
<div className="post-simple-title">
|
||||
<h4>
|
||||
<a href="#">How We Build a Better LA for Fifth Year in a Row</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div className="post-simple-time">
|
||||
<span className="icon mdi mdi-clock"></span>
|
||||
<a className="time" href="#">
|
||||
March 15, 2021
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="col-lg-4">
|
||||
<div className="pdl-xl-40">
|
||||
<div className="row row-60">
|
||||
<div className="col-md-6 col-lg-12">
|
||||
<form className="form-lg rd-search rd-search-classic">
|
||||
<div className="form-wrap">
|
||||
<label className="form-label" htmlFor="rd-search-form-input">
|
||||
Search the blog...
|
||||
</label>
|
||||
<input
|
||||
className="form-input"
|
||||
id="rd-search-form-input"
|
||||
type="text"
|
||||
name="s"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<button className="rd-search-submit" type="submit">
|
||||
{" "}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="col-md-6 col-lg-12">
|
||||
<div className="block-info-2">
|
||||
<div className="block-info-2-title">
|
||||
<h3>Latest Listings</h3>
|
||||
</div>
|
||||
<a className="post-minimal-1" href="#">
|
||||
<div className="post-minimal-1-left">
|
||||
<img src="/images/post-agent-01-212x208.jpg" alt="" width="212" height="208" />
|
||||
</div>
|
||||
<div className="post-minimal-1-body">
|
||||
<div className="post-minimal-1-title">
|
||||
<span>401 Biscayne Blvd</span>
|
||||
</div>
|
||||
<div className="post-minimal-1-text">
|
||||
<span>$5000\mo</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a className="post-minimal-1" href="#">
|
||||
<div className="post-minimal-1-left">
|
||||
<img src="/images/post-agent-02-212x208.jpg" alt="" width="212" height="208" />
|
||||
</div>
|
||||
<div className="post-minimal-1-body">
|
||||
<div className="post-minimal-1-title">
|
||||
<span>35 Pond St, New York</span>
|
||||
</div>
|
||||
<div className="post-minimal-1-text">
|
||||
<span>$5550\mo</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a className="post-minimal-1" href="#">
|
||||
<div className="post-minimal-1-left">
|
||||
<img src="/images/post-agent-03-212x208.jpg" alt="" width="212" height="208" />
|
||||
</div>
|
||||
<div className="post-minimal-1-body">
|
||||
<div className="post-minimal-1-title">
|
||||
<span>182 3rd St, Seattle</span>
|
||||
</div>
|
||||
<div className="post-minimal-1-text">
|
||||
<span>$2520\mo</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
47
src/app/(main)/blog/page.tsx
Normal file
47
src/app/(main)/blog/page.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import ListOfBlog from "@/components/blogs/ListOfBlog";
|
||||
import HeroImage from "@/components/HeroImage";
|
||||
import { getDefaultMetadata } from "@/utils/metadata";
|
||||
import { Metadata } from "next";
|
||||
|
||||
const metaDesc =
|
||||
"Explore the latest insights, news, and resources on the Dynamic Realty blog. Read our articles today.";
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const metadata = await getDefaultMetadata();
|
||||
metadata.title = `Blog - ${metadata.openGraph?.siteName}`;
|
||||
metadata.description = metaDesc;
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
export default async function Blog({ searchParams }: { searchParams?: Promise<{ s?: string }> }) {
|
||||
const params = await searchParams;
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeroImage />
|
||||
<section className="section section-md bg-colorSection2">
|
||||
<div className="container">
|
||||
<div>
|
||||
<form action="/blog" method="GET" className="form-lg rd-search rd-search-classic">
|
||||
<div className="form-wrap">
|
||||
<input
|
||||
placeholder="Search blog..."
|
||||
className="form-input"
|
||||
id="rd-search-form-input"
|
||||
type="text"
|
||||
name="s"
|
||||
autoComplete="off"
|
||||
defaultValue={params?.s}
|
||||
/>
|
||||
</div>
|
||||
<button className="rd-search-submit" type="submit"></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<ListOfBlog searchKeyword={params?.s} />
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
@ -12,22 +12,26 @@
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--font-montserrat: var(--font-montserrat);
|
||||
--font-playfairdisplay: var(--font-playfairdisplay);
|
||||
--color-colorExt1: #0a0a0a;
|
||||
--color-colorExt2: #ffffff;
|
||||
--color-colorExt3: #967244;
|
||||
--color-colorext4: #bc986b;
|
||||
--color-colorExt5: #1d1d1d;
|
||||
--color-colorHeader: var(--color-colorExt1);
|
||||
--color-colorHeaderText: var(--color-colorExt2);
|
||||
--color-colorHeaderTextHover: var(--color-colorext4);
|
||||
--color-colorFooter: var(--color-colorExt1);
|
||||
--color-colorFooter2: var(--color-colorExt5);
|
||||
--color-colorFooterText: var(--color-colorExt2);
|
||||
--color-colorFooterTextHover: var(--color-colorExt3);
|
||||
--color-colorFormInput: var(--color-colorExt2);
|
||||
--color-colorContactForm: var(--color-colorExt5);
|
||||
--color-colorText1: var(--color-colorExt1);
|
||||
--color-colorText2: var(--color-colorExt2);
|
||||
--color-colorExt10: #0a0a0a;
|
||||
--color-colorExt20: #ffffff;
|
||||
--color-colorExt21: #f3f4f9;
|
||||
--color-colorExt30: #967244;
|
||||
--color-colorext40: #bc986b;
|
||||
--color-colorExt50: #1d1d1d;
|
||||
--color-colorSection1: var(--color-colorExt20);
|
||||
--color-colorSection2: var(--color-colorExt21);
|
||||
--color-colorHeader: var(--color-colorExt10);
|
||||
--color-colorHeaderText: var(--color-colorExt20);
|
||||
--color-colorHeaderTextHover: var(--color-colorext40);
|
||||
--color-colorHeroOverlay: var(--color-colorExt50);
|
||||
--color-colorFooter: var(--color-colorExt10);
|
||||
--color-colorFooter2: var(--color-colorExt50);
|
||||
--color-colorFooterText: var(--color-colorExt20);
|
||||
--color-colorFooterTextHover: var(--color-colorExt30);
|
||||
--color-colorFormInput: var(--color-colorExt20);
|
||||
--color-colorContactForm: var(--color-colorExt50);
|
||||
--color-colorText1: var(--color-colorExt10);
|
||||
--color-colorText2: var(--color-colorExt20);
|
||||
}
|
||||
|
||||
@layer components {
|
@ -43,15 +43,14 @@ export default function RootLayout({
|
||||
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta charSet="utf-8" />
|
||||
<link rel="icon" href="images/favicon.ico" type="image/x-icon" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700,800,900%7CRoboto:300,400,500,700,900"
|
||||
/>
|
||||
<link rel="stylesheet" href="css/bootstrap.css" />
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
<link rel="stylesheet" href="css/fonts.css" />
|
||||
<link rel="stylesheet" href="/css/bootstrap.css" />
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<link rel="stylesheet" href="/css/fonts.css" />
|
||||
</head>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} ${montserrat.variable} ${playfairDisplay.variable} antialiased`}
|
||||
@ -59,7 +58,7 @@ export default function RootLayout({
|
||||
<div className="ie-panel">
|
||||
<a href="http://windows.microsoft.com/en-US/internet-explorer/">
|
||||
<img
|
||||
src="images/ie8-panel/warning_bar_0000_us.jpg"
|
||||
src="/images/ie8-panel/warning_bar_0000_us.jpg"
|
||||
height="42"
|
||||
width="820"
|
||||
alt="You are using an outdated browser. For a faster, safer browsing experience, upgrade for free today."
|
@ -23,7 +23,7 @@ export default function Home() {
|
||||
<ContactFormSection />
|
||||
</div>
|
||||
|
||||
<section className="section section-lg bg-default">
|
||||
<section className="section section-lg bg-colorSection1">
|
||||
<div className="container">
|
||||
<div className="row row-30">
|
||||
<div className="col-md-6 col-lg-3">
|
||||
@ -122,7 +122,7 @@ export default function Home() {
|
||||
|
||||
<GoogleReviewBox />
|
||||
|
||||
<section className="section section-lg bg-gray-12">
|
||||
<section className="section section-lg bg-colorSection2">
|
||||
<div className="container">
|
||||
<div className="layout-4">
|
||||
<h2 className="heading-decoration-1">
|
||||
@ -200,7 +200,7 @@ export default function Home() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section section-lg bg-default">
|
||||
<section className="section section-lg bg-colorSection1">
|
||||
<div className="container">
|
||||
<h2 className="heading-decoration-1">
|
||||
<span className="heading-inner">Find Us</span>
|
24
src/app/(payload)/admin/[[...segments]]/not-found.tsx
Normal file
24
src/app/(payload)/admin/[[...segments]]/not-found.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
import config from '@payload-config'
|
||||
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'
|
||||
import { importMap } from '../importMap'
|
||||
|
||||
type Args = {
|
||||
params: Promise<{
|
||||
segments: string[]
|
||||
}>
|
||||
searchParams: Promise<{
|
||||
[key: string]: string | string[]
|
||||
}>
|
||||
}
|
||||
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
|
||||
const NotFound = ({ params, searchParams }: Args) =>
|
||||
NotFoundPage({ config, params, searchParams, importMap })
|
||||
|
||||
export default NotFound
|
24
src/app/(payload)/admin/[[...segments]]/page.tsx
Normal file
24
src/app/(payload)/admin/[[...segments]]/page.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
import config from '@payload-config'
|
||||
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
|
||||
import { importMap } from '../importMap'
|
||||
|
||||
type Args = {
|
||||
params: Promise<{
|
||||
segments: string[]
|
||||
}>
|
||||
searchParams: Promise<{
|
||||
[key: string]: string | string[]
|
||||
}>
|
||||
}
|
||||
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
|
||||
const Page = ({ params, searchParams }: Args) =>
|
||||
RootPage({ config, params, searchParams, importMap })
|
||||
|
||||
export default Page
|
53
src/app/(payload)/admin/importMap.js
Normal file
53
src/app/(payload)/admin/importMap.js
Normal file
@ -0,0 +1,53 @@
|
||||
import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||
import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||
import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||
import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { UploadFeatureClient as UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { RelationshipFeatureClient as RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { ChecklistFeatureClient as ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { SuperscriptFeatureClient as SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { SubscriptFeatureClient as SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { default as default_aa89fa9464216e16b81a3e716c94a23a } from '../../../components/LogoAdmin'
|
||||
import { S3ClientUploadHandler as S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24 } from '@payloadcms/storage-s3/client'
|
||||
|
||||
export const importMap = {
|
||||
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||
"@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||
"@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#UploadFeatureClient": UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#BlockquoteFeatureClient": BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#RelationshipFeatureClient": RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#ChecklistFeatureClient": ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#OrderedListFeatureClient": OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#UnorderedListFeatureClient": UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#IndentFeatureClient": IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#AlignFeatureClient": AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#InlineCodeFeatureClient": InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#SuperscriptFeatureClient": SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#SubscriptFeatureClient": SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#StrikethroughFeatureClient": StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"/components/LogoAdmin#default": default_aa89fa9464216e16b81a3e716c94a23a,
|
||||
"@payloadcms/storage-s3/client#S3ClientUploadHandler": S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24
|
||||
}
|
19
src/app/(payload)/api/[...slug]/route.ts
Normal file
19
src/app/(payload)/api/[...slug]/route.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import config from '@payload-config'
|
||||
import '@payloadcms/next/css'
|
||||
import {
|
||||
REST_DELETE,
|
||||
REST_GET,
|
||||
REST_OPTIONS,
|
||||
REST_PATCH,
|
||||
REST_POST,
|
||||
REST_PUT,
|
||||
} from '@payloadcms/next/routes'
|
||||
|
||||
export const GET = REST_GET(config)
|
||||
export const POST = REST_POST(config)
|
||||
export const DELETE = REST_DELETE(config)
|
||||
export const PATCH = REST_PATCH(config)
|
||||
export const PUT = REST_PUT(config)
|
||||
export const OPTIONS = REST_OPTIONS(config)
|
7
src/app/(payload)/api/graphql-playground/route.ts
Normal file
7
src/app/(payload)/api/graphql-playground/route.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import config from '@payload-config'
|
||||
import '@payloadcms/next/css'
|
||||
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
|
||||
|
||||
export const GET = GRAPHQL_PLAYGROUND_GET(config)
|
8
src/app/(payload)/api/graphql/route.ts
Normal file
8
src/app/(payload)/api/graphql/route.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import config from '@payload-config'
|
||||
import { GRAPHQL_POST, REST_OPTIONS } from '@payloadcms/next/routes'
|
||||
|
||||
export const POST = GRAPHQL_POST(config)
|
||||
|
||||
export const OPTIONS = REST_OPTIONS(config)
|
0
src/app/(payload)/custom.scss
Normal file
0
src/app/(payload)/custom.scss
Normal file
31
src/app/(payload)/layout.tsx
Normal file
31
src/app/(payload)/layout.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import config from '@payload-config'
|
||||
import '@payloadcms/next/css'
|
||||
import type { ServerFunctionClient } from 'payload'
|
||||
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
|
||||
import React from 'react'
|
||||
|
||||
import { importMap } from './admin/importMap.js'
|
||||
import './custom.scss'
|
||||
|
||||
type Args = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const serverFunction: ServerFunctionClient = async function (args) {
|
||||
'use server'
|
||||
return handleServerFunctions({
|
||||
...args,
|
||||
config,
|
||||
importMap,
|
||||
})
|
||||
}
|
||||
|
||||
const Layout = ({ children }: Args) => (
|
||||
<RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
|
||||
{children}
|
||||
</RootLayout>
|
||||
)
|
||||
|
||||
export default Layout
|
@ -1,3 +0,0 @@
|
||||
export default function Blog() {
|
||||
return <></>;
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li className="tracking-[-.01em]">
|
||||
Save and see your changes instantly.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
39
src/collections/BlogCategories.ts
Normal file
39
src/collections/BlogCategories.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import formatSlug from "@/utils/payload/formatSlug";
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const BlogCategories: CollectionConfig = {
|
||||
slug: "blogCategories",
|
||||
labels: { plural: "Categories", singular: "Category" },
|
||||
fields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "slug",
|
||||
type: "text",
|
||||
admin: {
|
||||
position: "sidebar",
|
||||
},
|
||||
hooks: {
|
||||
beforeValidate: [formatSlug("name")],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "parent_category",
|
||||
label: "Parent Category",
|
||||
type: "relationship",
|
||||
relationTo: "blogCategories",
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
type: "textarea",
|
||||
},
|
||||
],
|
||||
admin: {
|
||||
hideAPIURL: true,
|
||||
group: "Blogs",
|
||||
useAsTitle: "name",
|
||||
},
|
||||
};
|
32
src/collections/BlogTags.ts
Normal file
32
src/collections/BlogTags.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import formatSlug from "@/utils/payload/formatSlug";
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const BlogTags: CollectionConfig = {
|
||||
slug: "blogTags",
|
||||
labels: { plural: "Tags", singular: "Tag" },
|
||||
fields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "slug",
|
||||
type: "text",
|
||||
admin: {
|
||||
position: "sidebar",
|
||||
},
|
||||
hooks: {
|
||||
beforeValidate: [formatSlug("name")],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
type: "textarea",
|
||||
},
|
||||
],
|
||||
admin: {
|
||||
hideAPIURL: true,
|
||||
group: "Blogs",
|
||||
},
|
||||
};
|
107
src/collections/Blogs.ts
Normal file
107
src/collections/Blogs.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import formatSlug from "@/utils/payload/formatSlug";
|
||||
import setAuthor from "@/utils/payload/setAuthor";
|
||||
import { lexicalEditor } from "@payloadcms/richtext-lexical";
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const Blogs: CollectionConfig = {
|
||||
slug: "blogs",
|
||||
labels: { plural: "Posts", singular: "Post" },
|
||||
versions: {
|
||||
drafts: {
|
||||
validate: true,
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "title",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "slug",
|
||||
type: "text",
|
||||
hooks: {
|
||||
beforeValidate: [formatSlug("title")],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "img",
|
||||
label: "Image",
|
||||
type: "upload",
|
||||
relationTo: "media",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "content",
|
||||
type: "richText",
|
||||
required: true,
|
||||
editor: lexicalEditor({}),
|
||||
},
|
||||
{
|
||||
name: "categories",
|
||||
type: "relationship",
|
||||
relationTo: "blogCategories",
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: "tags",
|
||||
type: "relationship",
|
||||
relationTo: "blogTags",
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: "meta",
|
||||
label: "Page Meta",
|
||||
type: "group",
|
||||
fields: [
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
label: "Description",
|
||||
type: "textarea",
|
||||
},
|
||||
{
|
||||
name: "canonical_url",
|
||||
label: "Canonical Url",
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "createdBy",
|
||||
type: "relationship",
|
||||
relationTo: "users",
|
||||
hooks: {
|
||||
beforeChange: [setAuthor],
|
||||
},
|
||||
admin: {
|
||||
hidden: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "updatedBy",
|
||||
type: "relationship",
|
||||
relationTo: "users",
|
||||
hooks: {
|
||||
beforeChange: [setAuthor],
|
||||
},
|
||||
admin: {
|
||||
hidden: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
admin: {
|
||||
hideAPIURL: true,
|
||||
group: "Blogs",
|
||||
useAsTitle: "title",
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
20
src/collections/Media.ts
Normal file
20
src/collections/Media.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const Media: CollectionConfig = {
|
||||
slug: "media",
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "alt",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
upload: true,
|
||||
admin: {
|
||||
hideAPIURL: true,
|
||||
group: "General",
|
||||
},
|
||||
};
|
20
src/collections/Users.ts
Normal file
20
src/collections/Users.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const Users: CollectionConfig = {
|
||||
slug: "users",
|
||||
admin: {
|
||||
useAsTitle: "email",
|
||||
hideAPIURL: true,
|
||||
group: "Users",
|
||||
},
|
||||
auth: true,
|
||||
fields: [
|
||||
// Email added by default
|
||||
// Add more fields as needed
|
||||
{
|
||||
name: "name",
|
||||
label: "Name",
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
export default function GoogleReviewBox() {
|
||||
return (
|
||||
<section className="section section-lg bg-gray-12">
|
||||
<section className="section section-lg bg-colorSection2">
|
||||
<div className="container">
|
||||
<h2 className="heading-decoration-1">
|
||||
<span className="heading-inner">Making Moves, Building Trust</span>
|
||||
|
26
src/components/HeroImage.tsx
Normal file
26
src/components/HeroImage.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import Image from "next/image";
|
||||
|
||||
type HeroImageProps = {
|
||||
imgSrc?: string;
|
||||
};
|
||||
|
||||
export default function HeroImage({ imgSrc = "/images/breadcrumbs-bg-05-1922x441.jpg" }: HeroImageProps) {
|
||||
return (
|
||||
<section className="breadcrumbs-custom bg-image context-dark">
|
||||
<Image
|
||||
alt="Blog"
|
||||
src={imgSrc}
|
||||
quality={100}
|
||||
fill
|
||||
sizes="100vw"
|
||||
style={{
|
||||
objectFit: "cover",
|
||||
}}
|
||||
/>
|
||||
<div className="bg-colorHeroOverlay/50 w-full h-full absolute top-0 left-0" />
|
||||
<div className="container relative">
|
||||
<h2 className="breadcrumbs-custom-title">Blog</h2>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
@ -52,7 +52,7 @@ export default function HeroSlider({ onClickBook }: HeroSliderProps) {
|
||||
objectFit: "cover",
|
||||
}}
|
||||
/>
|
||||
<section className="section section-lg text-colorText2! bg-colorExt5/50 w-full h-full z-20">
|
||||
<section className="section section-lg text-colorText2! bg-colorHeroOverlay/50 w-full h-full z-20">
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-12 col-lg-8 text-wrap space-y-5 md:space-y-8 lg:space-y-12">
|
||||
|
17
src/components/Loader.tsx
Normal file
17
src/components/Loader.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
export default function Loader() {
|
||||
return (
|
||||
<div className="flex flex-row justify-center">
|
||||
<div className="w-[72px] h-[72px]">
|
||||
<div className="banter-loader__box"></div>
|
||||
<div className="banter-loader__box"></div>
|
||||
<div className="banter-loader__box"></div>
|
||||
<div className="banter-loader__box"></div>
|
||||
<div className="banter-loader__box"></div>
|
||||
<div className="banter-loader__box"></div>
|
||||
<div className="banter-loader__box"></div>
|
||||
<div className="banter-loader__box"></div>
|
||||
<div className="banter-loader__box"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
5
src/components/LogoAdmin.tsx
Normal file
5
src/components/LogoAdmin.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function LogoAdmin() {
|
||||
return <Image src="/images/logo2.png" alt="Logo" width={205} height={134} />;
|
||||
}
|
36
src/components/blogs/CardBlog.tsx
Normal file
36
src/components/blogs/CardBlog.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { BlogData } from "@/schema/blog";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
type CardBlogProps = {
|
||||
data: BlogData;
|
||||
};
|
||||
|
||||
export default function CardBlog({ data }: CardBlogProps) {
|
||||
return (
|
||||
<div>
|
||||
<article className="post-default">
|
||||
<div className="h-64 relative">
|
||||
<Link href="#">
|
||||
<Image src={data.img?.url ?? ""} alt={data.img?.alt ?? ""} fill />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="post-default-body">
|
||||
<div className="post-default-title">
|
||||
<h4>
|
||||
<a href="blog-post.html">{data.title}</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div className="post-default-divider"></div>
|
||||
<div className="post-default-text">
|
||||
<p>{data.description}</p>
|
||||
</div>
|
||||
<div className="post-default-time">
|
||||
<span className="icon mdi mdi-clock"></span>
|
||||
<a href="#">{data.posted_at}</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
47
src/components/blogs/ListOfBlog.tsx
Normal file
47
src/components/blogs/ListOfBlog.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import Loader from "@/components/Loader";
|
||||
import { useBlogQuery } from "@/services/hooks/blog";
|
||||
import { useEffect, useRef } from "react";
|
||||
import CardBlog from "./CardBlog";
|
||||
|
||||
type ListOfBlogProps = {
|
||||
searchKeyword?: string;
|
||||
};
|
||||
|
||||
export default function ListOfBlog({ searchKeyword }: ListOfBlogProps) {
|
||||
const pageRef = useRef(1);
|
||||
const blogQuery = useBlogQuery();
|
||||
|
||||
useEffect(() => {
|
||||
blogQuery._fetch({
|
||||
search: searchKeyword,
|
||||
page: pageRef.current,
|
||||
});
|
||||
}, []);
|
||||
|
||||
function fetchMore() {
|
||||
blogQuery._fetch({
|
||||
search: searchKeyword,
|
||||
page: ++pageRef.current,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mt-5">
|
||||
{blogQuery.data.map((blog) => (
|
||||
<CardBlog key={blog.slug} data={blog} />
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
{blogQuery.isFetching && <Loader />}
|
||||
{blogQuery.hasNext && (
|
||||
<button onClick={fetchMore} className="button button-primary">
|
||||
LOAD MORE...
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@ -10,7 +10,7 @@ export default function Footer() {
|
||||
</h3>
|
||||
<a className="post-minimal" href="single-property.html">
|
||||
<div className="post-minimal-image">
|
||||
<img src="images/featured-properties-17-480x287.jpg" alt="" width="161" height="136" />
|
||||
<img src="/images/featured-properties-17-480x287.jpg" alt="" width="161" height="136" />
|
||||
</div>
|
||||
<div className="post-minimal-body">
|
||||
<div className="post-minimal-title">
|
||||
@ -25,7 +25,7 @@ export default function Footer() {
|
||||
</a>
|
||||
<a className="post-minimal" href="single-property.html">
|
||||
<div className="post-minimal-image">
|
||||
<img src="images/featured-properties-17-480x287.jpg" alt="" width="161" height="136" />
|
||||
<img src="/images/featured-properties-17-480x287.jpg" alt="" width="161" height="136" />
|
||||
</div>
|
||||
<div className="post-minimal-body">
|
||||
<div className="post-minimal-title">
|
||||
|
@ -75,20 +75,20 @@ export default function Header() {
|
||||
<img
|
||||
className="brand-logo-dark"
|
||||
// src="images/logo-default-142x41.png"
|
||||
src="images/logo2.png"
|
||||
src="/images/logo2.png"
|
||||
alt=""
|
||||
width="142"
|
||||
height="41"
|
||||
srcSet="images/logo2.png 2x"
|
||||
srcSet="/images/logo2.png 2x"
|
||||
/>
|
||||
<img
|
||||
className="brand-logo-light"
|
||||
// src="images/logo-inverse-121x61.png"\
|
||||
src="images/logo2.png"
|
||||
src="/images/logo2.png"
|
||||
alt=""
|
||||
width="121"
|
||||
height="61"
|
||||
srcSet="images/logo2.png 2x"
|
||||
srcSet="/images/logo2.png 2x"
|
||||
/>
|
||||
</a>
|
||||
{/* <h3 className="text-colorHeaderText! font-montserrat! font-semibold! hidden lg:inline">
|
||||
@ -123,7 +123,7 @@ export default function Header() {
|
||||
</a>
|
||||
</li>
|
||||
<li className="rd-nav-item">
|
||||
<a className="rd-nav-link rd-nav-link-custom" href="/">
|
||||
<a className="rd-nav-link rd-nav-link-custom" href="/blog">
|
||||
BLOGS
|
||||
</a>
|
||||
</li>
|
||||
|
@ -5,8 +5,8 @@ import Script from "next/script";
|
||||
export default function InitialScript() {
|
||||
return (
|
||||
<>
|
||||
<Script src="js/core.min.js" strategy="beforeInteractive" />
|
||||
<Script src="js/script.js" strategy="lazyOnload" />
|
||||
<Script src="/js/core.min.js" strategy="beforeInteractive" />
|
||||
<Script src="/js/script.js" strategy="lazyOnload" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -20,17 +20,17 @@ export function middleware(request: NextRequest) {
|
||||
request.headers.set("x-meta-desc", metaDesc);
|
||||
|
||||
// redirect for /blog/<slug>/ path
|
||||
const blogPathRegex = /^\/blog\/([^\/]+)(\/[^\/]*)*\/?$/;
|
||||
if (blogPathRegex.test(path) && !path.includes("/blog/?")) {
|
||||
const newBlogPath = path.replace(/^\/blog/, "") || "/";
|
||||
return NextResponse.redirect(`${mainUrl}${newBlogPath}`, 301);
|
||||
}
|
||||
// const blogPathRegex = /^\/blog\/([^\/]+)(\/[^\/]*)*\/?$/;
|
||||
// if (blogPathRegex.test(path) && !path.includes("/blog/?")) {
|
||||
// const newBlogPath = path.replace(/^\/blog/, "") || "/";
|
||||
// return NextResponse.redirect(`${mainUrl}${newBlogPath}`, 301);
|
||||
// }
|
||||
// redirect for /about/<slug>/ path
|
||||
const aboutPathRegex = /^\/about\/([^\/]+)(\/[^\/]*)*\/?$/;
|
||||
if (aboutPathRegex.test(path)) {
|
||||
const newAboutPath = path.replace(/^\/about/, "") || "/";
|
||||
return NextResponse.redirect(`${mainUrl}${newAboutPath}`, 301);
|
||||
}
|
||||
// const aboutPathRegex = /^\/about\/([^\/]+)(\/[^\/]*)*\/?$/;
|
||||
// if (aboutPathRegex.test(path)) {
|
||||
// const newAboutPath = path.replace(/^\/about/, "") || "/";
|
||||
// return NextResponse.redirect(`${mainUrl}${newAboutPath}`, 301);
|
||||
// }
|
||||
|
||||
return NextResponse.next({
|
||||
request: {
|
||||
|
416
src/payload-types.ts
Normal file
416
src/payload-types.ts
Normal file
@ -0,0 +1,416 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* This file was automatically generated by Payload.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Supported timezones in IANA format.
|
||||
*
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "supportedTimezones".
|
||||
*/
|
||||
export type SupportedTimezones =
|
||||
| 'Pacific/Midway'
|
||||
| 'Pacific/Niue'
|
||||
| 'Pacific/Honolulu'
|
||||
| 'Pacific/Rarotonga'
|
||||
| 'America/Anchorage'
|
||||
| 'Pacific/Gambier'
|
||||
| 'America/Los_Angeles'
|
||||
| 'America/Tijuana'
|
||||
| 'America/Denver'
|
||||
| 'America/Phoenix'
|
||||
| 'America/Chicago'
|
||||
| 'America/Guatemala'
|
||||
| 'America/New_York'
|
||||
| 'America/Bogota'
|
||||
| 'America/Caracas'
|
||||
| 'America/Santiago'
|
||||
| 'America/Buenos_Aires'
|
||||
| 'America/Sao_Paulo'
|
||||
| 'Atlantic/South_Georgia'
|
||||
| 'Atlantic/Azores'
|
||||
| 'Atlantic/Cape_Verde'
|
||||
| 'Europe/London'
|
||||
| 'Europe/Berlin'
|
||||
| 'Africa/Lagos'
|
||||
| 'Europe/Athens'
|
||||
| 'Africa/Cairo'
|
||||
| 'Europe/Moscow'
|
||||
| 'Asia/Riyadh'
|
||||
| 'Asia/Dubai'
|
||||
| 'Asia/Baku'
|
||||
| 'Asia/Karachi'
|
||||
| 'Asia/Tashkent'
|
||||
| 'Asia/Calcutta'
|
||||
| 'Asia/Dhaka'
|
||||
| 'Asia/Almaty'
|
||||
| 'Asia/Jakarta'
|
||||
| 'Asia/Bangkok'
|
||||
| 'Asia/Shanghai'
|
||||
| 'Asia/Singapore'
|
||||
| 'Asia/Tokyo'
|
||||
| 'Asia/Seoul'
|
||||
| 'Australia/Brisbane'
|
||||
| 'Australia/Sydney'
|
||||
| 'Pacific/Guam'
|
||||
| 'Pacific/Noumea'
|
||||
| 'Pacific/Auckland'
|
||||
| 'Pacific/Fiji';
|
||||
|
||||
export interface Config {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
};
|
||||
blocks: {};
|
||||
collections: {
|
||||
users: User;
|
||||
media: Media;
|
||||
blogTags: BlogTag;
|
||||
blogCategories: BlogCategory;
|
||||
blogs: Blog;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
media: MediaSelect<false> | MediaSelect<true>;
|
||||
blogTags: BlogTagsSelect<false> | BlogTagsSelect<true>;
|
||||
blogCategories: BlogCategoriesSelect<false> | BlogCategoriesSelect<true>;
|
||||
blogs: BlogsSelect<false> | BlogsSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: number;
|
||||
};
|
||||
globals: {};
|
||||
globalsSelect: {};
|
||||
locale: null;
|
||||
user: User & {
|
||||
collection: 'users';
|
||||
};
|
||||
jobs: {
|
||||
tasks: unknown;
|
||||
workflows: unknown;
|
||||
};
|
||||
}
|
||||
export interface UserAuthOperations {
|
||||
forgotPassword: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
login: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
registerFirstUser: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
unlock: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: number;
|
||||
name?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string | null;
|
||||
resetPasswordExpiration?: string | null;
|
||||
salt?: string | null;
|
||||
hash?: string | null;
|
||||
loginAttempts?: number | null;
|
||||
lockUntil?: string | null;
|
||||
password?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media".
|
||||
*/
|
||||
export interface Media {
|
||||
id: number;
|
||||
alt: string;
|
||||
prefix?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
thumbnailURL?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "blogTags".
|
||||
*/
|
||||
export interface BlogTag {
|
||||
id: number;
|
||||
name: string;
|
||||
slug?: string | null;
|
||||
description?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "blogCategories".
|
||||
*/
|
||||
export interface BlogCategory {
|
||||
id: number;
|
||||
name: string;
|
||||
slug?: string | null;
|
||||
parent_category?: (number | null) | BlogCategory;
|
||||
description?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "blogs".
|
||||
*/
|
||||
export interface Blog {
|
||||
id: number;
|
||||
title: string;
|
||||
slug?: string | null;
|
||||
img: number | Media;
|
||||
content: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
categories?: (number | BlogCategory)[] | null;
|
||||
tags?: (number | BlogTag)[] | null;
|
||||
meta?: {
|
||||
title?: string | null;
|
||||
description?: string | null;
|
||||
canonical_url?: string | null;
|
||||
};
|
||||
createdBy?: (number | null) | User;
|
||||
updatedBy?: (number | null) | User;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents".
|
||||
*/
|
||||
export interface PayloadLockedDocument {
|
||||
id: number;
|
||||
document?:
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'media';
|
||||
value: number | Media;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'blogTags';
|
||||
value: number | BlogTag;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'blogCategories';
|
||||
value: number | BlogCategory;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'blogs';
|
||||
value: number | Blog;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-preferences".
|
||||
*/
|
||||
export interface PayloadPreference {
|
||||
id: number;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
};
|
||||
key?: string | null;
|
||||
value?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-migrations".
|
||||
*/
|
||||
export interface PayloadMigration {
|
||||
id: number;
|
||||
name?: string | null;
|
||||
batch?: number | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
*/
|
||||
export interface UsersSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
email?: T;
|
||||
resetPasswordToken?: T;
|
||||
resetPasswordExpiration?: T;
|
||||
salt?: T;
|
||||
hash?: T;
|
||||
loginAttempts?: T;
|
||||
lockUntil?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media_select".
|
||||
*/
|
||||
export interface MediaSelect<T extends boolean = true> {
|
||||
alt?: T;
|
||||
prefix?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
url?: T;
|
||||
thumbnailURL?: T;
|
||||
filename?: T;
|
||||
mimeType?: T;
|
||||
filesize?: T;
|
||||
width?: T;
|
||||
height?: T;
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "blogTags_select".
|
||||
*/
|
||||
export interface BlogTagsSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
slug?: T;
|
||||
description?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "blogCategories_select".
|
||||
*/
|
||||
export interface BlogCategoriesSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
slug?: T;
|
||||
parent_category?: T;
|
||||
description?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "blogs_select".
|
||||
*/
|
||||
export interface BlogsSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
slug?: T;
|
||||
img?: T;
|
||||
content?: T;
|
||||
categories?: T;
|
||||
tags?: T;
|
||||
meta?:
|
||||
| T
|
||||
| {
|
||||
title?: T;
|
||||
description?: T;
|
||||
canonical_url?: T;
|
||||
};
|
||||
createdBy?: T;
|
||||
updatedBy?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents_select".
|
||||
*/
|
||||
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
|
||||
document?: T;
|
||||
globalSlug?: T;
|
||||
user?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-preferences_select".
|
||||
*/
|
||||
export interface PayloadPreferencesSelect<T extends boolean = true> {
|
||||
user?: T;
|
||||
key?: T;
|
||||
value?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-migrations_select".
|
||||
*/
|
||||
export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
batch?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "auth".
|
||||
*/
|
||||
export interface Auth {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
|
||||
|
||||
declare module 'payload' {
|
||||
export interface GeneratedTypes extends Config {}
|
||||
}
|
75
src/payload.config.ts
Normal file
75
src/payload.config.ts
Normal file
@ -0,0 +1,75 @@
|
||||
// storage-adapter-import-placeholder
|
||||
import { postgresAdapter } from "@payloadcms/db-postgres";
|
||||
import { payloadCloudPlugin } from "@payloadcms/payload-cloud";
|
||||
import { lexicalEditor } from "@payloadcms/richtext-lexical";
|
||||
import path from "path";
|
||||
import { buildConfig } from "payload";
|
||||
import { fileURLToPath } from "url";
|
||||
import sharp from "sharp";
|
||||
|
||||
import { s3Storage } from "@payloadcms/storage-s3";
|
||||
import { Users } from "@/collections/Users";
|
||||
import { Media } from "@/collections/Media";
|
||||
import { BlogTags } from "@/collections/BlogTags";
|
||||
import { BlogCategories } from "@/collections/BlogCategories";
|
||||
import { Blogs } from "@/collections/Blogs";
|
||||
|
||||
const filename = fileURLToPath(import.meta.url);
|
||||
const dirname = path.dirname(filename);
|
||||
|
||||
export default buildConfig({
|
||||
cors: [process.env.SITE_URL || ""],
|
||||
csrf: [process.env.SITE_URL || ""],
|
||||
admin: {
|
||||
user: Users.slug,
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
},
|
||||
meta: {
|
||||
titleSuffix: "- Admin - Dynamic Realty",
|
||||
description: "Dynamic Realty",
|
||||
icons: [{ url: "/images/logo2.png" }],
|
||||
},
|
||||
components: {
|
||||
graphics: {
|
||||
Logo: {
|
||||
path: "/components/LogoAdmin",
|
||||
},
|
||||
},
|
||||
},
|
||||
theme: "dark",
|
||||
},
|
||||
collections: [Users, Media, BlogTags, BlogCategories, Blogs],
|
||||
editor: lexicalEditor(),
|
||||
secret: process.env.PAYLOAD_SECRET || "",
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, "payload-types.ts"),
|
||||
},
|
||||
db: postgresAdapter({
|
||||
pool: {
|
||||
connectionString: process.env.DATABASE_URI || "",
|
||||
},
|
||||
}),
|
||||
sharp,
|
||||
plugins: [
|
||||
payloadCloudPlugin(),
|
||||
// storage-adapter-placeholder,
|
||||
s3Storage({
|
||||
collections: {
|
||||
media: {
|
||||
prefix: "media",
|
||||
},
|
||||
},
|
||||
bucket: process.env.S3_BUCKET || "",
|
||||
config: {
|
||||
forcePathStyle: true, // Important for using Supabase
|
||||
credentials: {
|
||||
accessKeyId: process.env.S3_ACCESS_KEY_ID || "",
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || "",
|
||||
},
|
||||
region: process.env.S3_REGION,
|
||||
endpoint: process.env.S3_ENDPOINT,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
7
src/schema/blog.ts
Normal file
7
src/schema/blog.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export type BlogData = {
|
||||
slug?: string | null;
|
||||
title: string;
|
||||
description: string;
|
||||
img?: { url: string; alt?: string };
|
||||
posted_at: string;
|
||||
};
|
6
src/schema/services/blog.ts
Normal file
6
src/schema/services/blog.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export type FetchBlogParams = {
|
||||
page?: number;
|
||||
search?: string;
|
||||
categoryId?: number;
|
||||
tagId?: number;
|
||||
};
|
28
src/services/hooks/blog.ts
Normal file
28
src/services/hooks/blog.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { BlogData } from "@/schema/blog";
|
||||
import { FetchBlogParams } from "@/schema/services/blog";
|
||||
import { useState } from "react";
|
||||
import { fetchBlogREST } from "../rest/blog";
|
||||
|
||||
export function useBlogQuery() {
|
||||
const [data, setData] = useState<BlogData[]>([]);
|
||||
const [isFetching, setFetching] = useState(false);
|
||||
const [hasNext, setHasNext] = useState(false);
|
||||
|
||||
async function _fetch(params: FetchBlogParams = {}) {
|
||||
setFetching(true);
|
||||
const res = await fetchBlogREST(params);
|
||||
setFetching(false);
|
||||
|
||||
if (Array.isArray(res?.formattedData)) {
|
||||
setData(res.formattedData);
|
||||
}
|
||||
setHasNext(res?.hasNextPage ?? false);
|
||||
}
|
||||
|
||||
return {
|
||||
_fetch,
|
||||
data,
|
||||
isFetching,
|
||||
hasNext,
|
||||
};
|
||||
}
|
147
src/services/payload/blog.ts
Normal file
147
src/services/payload/blog.ts
Normal file
@ -0,0 +1,147 @@
|
||||
import payloadConfig from "@/payload.config";
|
||||
import { FetchBlogParams } from "@/schema/services/blog";
|
||||
import { formatDate } from "@/utils/datetime";
|
||||
import { getRandomNumber } from "@/utils/general";
|
||||
import { getPayload, Where } from "payload";
|
||||
|
||||
export async function fetchBlog({ page, search = "", categoryId, tagId }: FetchBlogParams = {}) {
|
||||
const payload = await getPayload({ config: payloadConfig });
|
||||
|
||||
const queryCondition: Where = {
|
||||
_status: { equals: "published" },
|
||||
};
|
||||
|
||||
if (!!search) {
|
||||
queryCondition["title"] = {
|
||||
contains: search,
|
||||
};
|
||||
}
|
||||
if (!!categoryId) {
|
||||
queryCondition["categories"] = {
|
||||
equals: categoryId,
|
||||
};
|
||||
}
|
||||
if (!!tagId) {
|
||||
queryCondition["tags"] = {
|
||||
equals: tagId,
|
||||
};
|
||||
}
|
||||
|
||||
const blogDataQuery = await payload.find({
|
||||
collection: "blogs",
|
||||
page,
|
||||
pagination: true,
|
||||
limit: 9,
|
||||
where: queryCondition,
|
||||
});
|
||||
|
||||
const formattedData = blogDataQuery.docs.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
imgFormatted: typeof item.img !== "number" ? { url: item?.img?.url ?? "", alt: item.img.alt } : undefined,
|
||||
createdAtFormatted: formatDate(item.createdAt),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...blogDataQuery,
|
||||
formattedData,
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchBlogSuggestion() {
|
||||
const payload = await getPayload({ config: payloadConfig });
|
||||
const limitPerPage = 2;
|
||||
const blogCountQuery = await payload.count({
|
||||
collection: "blogs",
|
||||
where: { _status: { equals: "published" } },
|
||||
});
|
||||
|
||||
// randomize page
|
||||
let page = 1;
|
||||
const totalDocs = blogCountQuery.totalDocs;
|
||||
if (totalDocs > limitPerPage) {
|
||||
const totalPage = Math.ceil(totalDocs / limitPerPage);
|
||||
page = getRandomNumber(totalPage);
|
||||
}
|
||||
|
||||
const blogDataQuery = await payload.find({
|
||||
collection: "blogs",
|
||||
page,
|
||||
limit: limitPerPage,
|
||||
});
|
||||
|
||||
const formattedData = blogDataQuery.docs.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
imgFormatted: typeof item.img !== "number" ? { url: item?.img?.url ?? "", alt: item.img.alt } : undefined,
|
||||
createdAtFormatted: formatDate(item.createdAt),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...blogDataQuery,
|
||||
formattedData,
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchBlogDetail(slug: string | undefined) {
|
||||
const payload = await getPayload({ config: payloadConfig });
|
||||
const blogDataQuery = await payload.find({
|
||||
collection: "blogs",
|
||||
where: {
|
||||
_status: { equals: "published" },
|
||||
slug: { equals: slug },
|
||||
},
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
});
|
||||
|
||||
if (!blogDataQuery?.docs?.[0]) return null;
|
||||
|
||||
const data = blogDataQuery?.docs?.[0];
|
||||
const createdAt = formatDate(data.createdAt);
|
||||
const updatedAt = formatDate(data.updatedAt);
|
||||
const imgUrl = typeof data.img !== "number" ? (data?.img?.url ?? "") : "";
|
||||
|
||||
return {
|
||||
data,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
imgUrl,
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchBlogCategoryBySlug(slug: string) {
|
||||
const payload = await getPayload({ config: payloadConfig });
|
||||
const category = await payload.find({
|
||||
collection: "blogCategories",
|
||||
where: {
|
||||
_status: { equals: "published" },
|
||||
slug: { equals: slug },
|
||||
},
|
||||
});
|
||||
|
||||
if (!category?.docs?.[0]) return null;
|
||||
|
||||
return {
|
||||
data: category.docs[0],
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchBlogTagBySlug(slug: string) {
|
||||
const payload = await getPayload({ config: payloadConfig });
|
||||
const tag = await payload.find({
|
||||
collection: "blogTags",
|
||||
where: {
|
||||
_status: { equals: "published" },
|
||||
slug: { equals: slug },
|
||||
},
|
||||
});
|
||||
|
||||
if (!tag?.docs?.[0]) return null;
|
||||
|
||||
return {
|
||||
data: tag.docs[0],
|
||||
};
|
||||
}
|
61
src/services/rest/blog.ts
Normal file
61
src/services/rest/blog.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { Blog } from "@/payload-types";
|
||||
import { BlogData } from "@/schema/blog";
|
||||
import { FetchBlogParams } from "@/schema/services/blog";
|
||||
import { formatDate } from "@/utils/datetime";
|
||||
import { sanitizeBlogContentIntoStringPreview } from "@/utils/sanitize";
|
||||
import { PaginatedDocs, Where } from "payload";
|
||||
import { stringify } from "qs-esm";
|
||||
|
||||
export async function fetchBlogREST({ page, search = "", categoryId, tagId }: FetchBlogParams = {}) {
|
||||
const queryCondition: Where = {
|
||||
_status: { equals: "published" },
|
||||
};
|
||||
|
||||
if (!!search) {
|
||||
queryCondition["title"] = {
|
||||
contains: search,
|
||||
};
|
||||
}
|
||||
if (!!categoryId) {
|
||||
queryCondition["categories"] = {
|
||||
equals: categoryId,
|
||||
};
|
||||
}
|
||||
if (!!tagId) {
|
||||
queryCondition["tags"] = {
|
||||
equals: tagId,
|
||||
};
|
||||
}
|
||||
|
||||
const queryParams = stringify(
|
||||
{
|
||||
page,
|
||||
pagination: true,
|
||||
limit: 9,
|
||||
where: queryCondition,
|
||||
},
|
||||
{ addQueryPrefix: true }
|
||||
);
|
||||
|
||||
const blogRequest = await fetch(`/api/blogs${queryParams}`);
|
||||
|
||||
if (blogRequest.ok) {
|
||||
const resData = (await blogRequest.json()) as PaginatedDocs<Blog>;
|
||||
const formattedData: BlogData[] = resData.docs.map((item) => {
|
||||
return {
|
||||
slug: item.slug,
|
||||
title: item.title,
|
||||
description: sanitizeBlogContentIntoStringPreview(item.content),
|
||||
img: typeof item.img !== "number" ? { url: item?.img?.url ?? "", alt: item.img.alt } : undefined,
|
||||
posted_at: formatDate(item.createdAt),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...resData,
|
||||
formattedData,
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
5
src/utils/datetime.ts
Normal file
5
src/utils/datetime.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export function formatDate(iso: string, format: string = "MMM, D YYYY") {
|
||||
return dayjs(iso).format(format);
|
||||
}
|
7
src/utils/general.ts
Normal file
7
src/utils/general.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export function limitString(text: string) {
|
||||
return `${text.length > 100 ? `${text.slice(0, 100)}...` : text}`;
|
||||
}
|
||||
|
||||
export function getRandomNumber(range: number): number {
|
||||
return Math.floor(Math.random() * range) + 1;
|
||||
}
|
24
src/utils/payload/formatSlug.ts
Normal file
24
src/utils/payload/formatSlug.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { FieldHook } from "payload";
|
||||
|
||||
const format = (val: string): string =>
|
||||
val
|
||||
.replace(/ /g, "-")
|
||||
.replace(/[^\w-/]+/g, "")
|
||||
.toLowerCase();
|
||||
|
||||
const formatSlug =
|
||||
(fallback: string): FieldHook =>
|
||||
({ value, originalDoc, data }) => {
|
||||
if (typeof value === "string") {
|
||||
return format(value);
|
||||
}
|
||||
const fallbackData = (data && data[fallback]) || (originalDoc && originalDoc[fallback]);
|
||||
|
||||
if (fallbackData && typeof fallbackData === "string") {
|
||||
return format(fallbackData);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export default formatSlug;
|
17
src/utils/payload/setAuthor.ts
Normal file
17
src/utils/payload/setAuthor.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { FieldHook } from "payload";
|
||||
|
||||
const setAuthor: FieldHook = ({ value, req, data, field, operation }) => {
|
||||
if (req.user && !!data) {
|
||||
if (operation === "create") {
|
||||
return req.user.id;
|
||||
} else {
|
||||
if (field.name === "updatedBy") {
|
||||
return req.user.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export default setAuthor;
|
32
src/utils/sanitize.ts
Normal file
32
src/utils/sanitize.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Blog } from "@/payload-types";
|
||||
|
||||
export function sanitizePageNumber(page: any, defaultPage = 1): number {
|
||||
const parsedPage = Number(page);
|
||||
|
||||
if (isNaN(parsedPage) || parsedPage < 1 || !Number.isInteger(parsedPage)) {
|
||||
return defaultPage;
|
||||
}
|
||||
|
||||
return parsedPage;
|
||||
}
|
||||
|
||||
export function sanitizeBlogContentIntoStringPreview(data: Blog["content"]) {
|
||||
// Find the first paragraph that has children with text
|
||||
const firstParagraph = data.root.children.find(
|
||||
(node) =>
|
||||
node.type === "paragraph" &&
|
||||
Array.isArray(node.children) &&
|
||||
node.children.length > 0 &&
|
||||
!!node.children?.[0]?.text
|
||||
);
|
||||
|
||||
if (!firstParagraph) {
|
||||
return "...";
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const text = firstParagraph.children?.[0]?.text ?? "";
|
||||
|
||||
// Limit to 100 characters
|
||||
return `${text.length > 100 ? text.slice(0, 100) : text}...`;
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@ -19,9 +23,21 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"@payload-config": [
|
||||
"./src/payload.config.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user