Merge pull request 'dev' (#5) from dev into main

Reviewed-on: #5
This commit is contained in:
RizqiSyahrendra 2025-04-21 01:27:22 +00:00
commit 681a559641
53 changed files with 8134 additions and 264 deletions

View File

@ -1,7 +1,8 @@
import { withPayload } from "@payloadcms/next/withPayload";
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ /* config options here */
}; };
export default nextConfig; export default withPayload(nextConfig);

View File

@ -2,14 +2,26 @@
"name": "dynamic-realty-next", "name": "dynamic-realty-next",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"dev": "next dev --turbopack", "dev": "next dev --turbopack",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
"payload:generate:types": "payload generate:types",
"payload:generate:importMap": "payload generate:importMap"
}, },
"dependencies": { "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", "next": "15.3.0",
"payload": "^3.35.1",
"qs-esm": "^7.0.2",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"swiper": "^11.2.6" "swiper": "^11.2.6"

View File

@ -3751,8 +3751,7 @@ html .page .divider-secondary::after {
.button-primary:hover, .button-primary:active { .button-primary:hover, .button-primary:active {
color: #151515; color: #151515;
background-color: #fdde52; opacity: 0.8;
border-color: #fdde52;
} }
.button-primary.button-ujarak::before { .button-primary.button-ujarak::before {
@ -3789,8 +3788,7 @@ html .page .divider-secondary::after {
.button-primary:hover, .button-primary:active { .button-primary:hover, .button-primary:active {
color: #151515; color: #151515;
background-color: #fdde52; opacity: 0.8;
border-color: #fdde52;
} }
.button-primary.button-ujarak::before { .button-primary.button-ujarak::before {
@ -4159,7 +4157,8 @@ input:-webkit-autofill ~ .form-validation {
-webkit-appearance: none; -webkit-appearance: none;
transition: .3s ease-in-out; transition: .3s ease-in-out;
letter-spacing: 0; letter-spacing: 0;
/* border: 1px solid #f3f4f9; */ border: 1px solid #f3f4f9;
border-color: #f3f4f9!;
} }
.form-input:focus { .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

View File

@ -1084,91 +1084,91 @@ $(function () {
} }
// RD Search // RD Search
if (plugins.search.length || plugins.searchResults) { // if (plugins.search.length || plugins.searchResults) {
var handler = "bat/rd-search.php"; // var handler = "bat/rd-search.php";
var defaultTemplate = '<h5 class="search-title"><a target="_top" href="#{href}" class="search-link">#{title}</a></h5>' + // var defaultTemplate = '<h5 class="search-title"><a target="_top" href="#{href}" class="search-link">#{title}</a></h5>' +
'<p>...#{token}...</p>' + // '<p>...#{token}...</p>' +
'<p class="match"><em>Terms matched: #{count} - URL: #{href}</em></p>'; // '<p class="match"><em>Terms matched: #{count} - URL: #{href}</em></p>';
var defaultFilter = '*.html'; // var defaultFilter = '*.html';
if (plugins.search.length) { // if (plugins.search.length) {
for (var i = 0; i < plugins.search.length; i++) { // for (var i = 0; i < plugins.search.length; i++) {
var searchItem = $(plugins.search[i]), // var searchItem = $(plugins.search[i]),
options = { // options = {
element: searchItem, // element: searchItem,
filter: (searchItem.attr('data-search-filter')) ? searchItem.attr('data-search-filter') : defaultFilter, // filter: (searchItem.attr('data-search-filter')) ? searchItem.attr('data-search-filter') : defaultFilter,
template: (searchItem.attr('data-search-template')) ? searchItem.attr('data-search-template') : defaultTemplate, // template: (searchItem.attr('data-search-template')) ? searchItem.attr('data-search-template') : defaultTemplate,
live: (searchItem.attr('data-search-live')) ? searchItem.attr('data-search-live') : false, // 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, // liveCount: (searchItem.attr('data-search-live-count')) ? parseInt(searchItem.attr('data-search-live'), 10) : 4,
current: 0, processed: 0, timer: {} // current: 0, processed: 0, timer: {}
}; // };
var $toggle = $('.rd-navbar-search-toggle'); // var $toggle = $('.rd-navbar-search-toggle');
if ($toggle.length) { // if ($toggle.length) {
$toggle.on('click', (function (searchItem) { // $toggle.on('click', (function (searchItem) {
return function () { // return function () {
if (!($(this).hasClass('active'))) { // if (!($(this).hasClass('active'))) {
searchItem.find('input').val('').trigger('propertychange'); // searchItem.find('input').val('').trigger('propertychange');
} // }
} // }
})(searchItem)); // })(searchItem));
} // }
if (options.live) { // if (options.live) {
var clearHandler = false; // var clearHandler = false;
searchItem.find('input').on("input propertychange", $.proxy(function () { // searchItem.find('input').on("input propertychange", $.proxy(function () {
this.term = this.element.find('input').val().trim(); // this.term = this.element.find('input').val().trim();
this.spin = this.element.find('.input-group-addon'); // this.spin = this.element.find('.input-group-addon');
clearTimeout(this.timer); // clearTimeout(this.timer);
if (this.term.length > 2) { // if (this.term.length > 2) {
this.timer = setTimeout(liveSearch(this), 200); // this.timer = setTimeout(liveSearch(this), 200);
if (clearHandler === false) { // if (clearHandler === false) {
clearHandler = true; // clearHandler = true;
$body.on("click", function (e) { // $body.on("click", function (e) {
if ($(e.toElement).parents('.rd-search').length === 0) { // if ($(e.toElement).parents('.rd-search').length === 0) {
$('#rd-search-results-live').addClass('cleared').html(''); // $('#rd-search-results-live').addClass('cleared').html('');
} // }
}) // })
} // }
} else if (this.term.length === 0) { // } else if (this.term.length === 0) {
$('#' + this.live).addClass('cleared').html(''); // $('#' + this.live).addClass('cleared').html('');
} // }
}, options, this)); // }, options, this));
} // }
searchItem.submit($.proxy(function () { // searchItem.submit($.proxy(function () {
$('<input />').attr('type', 'hidden') // $('<input />').attr('type', 'hidden')
.attr('name', "filter") // .attr('name', "filter")
.attr('value', this.filter) // .attr('value', this.filter)
.appendTo(this.element); // .appendTo(this.element);
return true; // return true;
}, options, this)) // }, options, this))
} // }
} // }
if (plugins.searchResults.length) { // if (plugins.searchResults.length) {
var regExp = /\?.*s=([^&]+)\&filter=([^&]+)/g; // var regExp = /\?.*s=([^&]+)\&filter=([^&]+)/g;
var match = regExp.exec(location.search); // var match = regExp.exec(location.search);
if (match !== null) { // if (match !== null) {
$.get(handler, { // $.get(handler, {
s: decodeURI(match[1]), // s: decodeURI(match[1]),
dataType: "html", // dataType: "html",
filter: match[2], // filter: match[2],
template: defaultTemplate, // template: defaultTemplate,
live: '' // live: ''
}, function (data) { // }, function (data) {
plugins.searchResults.html(data); // plugins.searchResults.html(data);
}) // })
} // }
} // }
} // }
// Swiper // Swiper
function makeInterLeaveEffectOptions(interleaveOffset) { function makeInterLeaveEffectOptions(interleaveOffset) {

View 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>
</>
);
}

View 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>
</>
);
}

View File

@ -12,22 +12,26 @@
--font-mono: var(--font-geist-mono); --font-mono: var(--font-geist-mono);
--font-montserrat: var(--font-montserrat); --font-montserrat: var(--font-montserrat);
--font-playfairdisplay: var(--font-playfairdisplay); --font-playfairdisplay: var(--font-playfairdisplay);
--color-colorExt1: #0a0a0a; --color-colorExt10: #0a0a0a;
--color-colorExt2: #ffffff; --color-colorExt20: #ffffff;
--color-colorExt3: #967244; --color-colorExt21: #f3f4f9;
--color-colorext4: #bc986b; --color-colorExt30: #967244;
--color-colorExt5: #1d1d1d; --color-colorext40: #bc986b;
--color-colorHeader: var(--color-colorExt1); --color-colorExt50: #1d1d1d;
--color-colorHeaderText: var(--color-colorExt2); --color-colorSection1: var(--color-colorExt20);
--color-colorHeaderTextHover: var(--color-colorext4); --color-colorSection2: var(--color-colorExt21);
--color-colorFooter: var(--color-colorExt1); --color-colorHeader: var(--color-colorExt10);
--color-colorFooter2: var(--color-colorExt5); --color-colorHeaderText: var(--color-colorExt20);
--color-colorFooterText: var(--color-colorExt2); --color-colorHeaderTextHover: var(--color-colorext40);
--color-colorFooterTextHover: var(--color-colorExt3); --color-colorHeroOverlay: var(--color-colorExt50);
--color-colorFormInput: var(--color-colorExt2); --color-colorFooter: var(--color-colorExt10);
--color-colorContactForm: var(--color-colorExt5); --color-colorFooter2: var(--color-colorExt50);
--color-colorText1: var(--color-colorExt1); --color-colorFooterText: var(--color-colorExt20);
--color-colorText2: var(--color-colorExt2); --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 { @layer components {

View File

@ -43,15 +43,14 @@ export default function RootLayout({
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta charSet="utf-8" /> <meta charSet="utf-8" />
<link rel="icon" href="images/favicon.ico" type="image/x-icon" />
<link <link
rel="stylesheet" rel="stylesheet"
type="text/css" type="text/css"
href="https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700,800,900%7CRoboto:300,400,500,700,900" 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/bootstrap.css" />
<link rel="stylesheet" href="css/style.css" /> <link rel="stylesheet" href="/css/style.css" />
<link rel="stylesheet" href="css/fonts.css" /> <link rel="stylesheet" href="/css/fonts.css" />
</head> </head>
<body <body
className={`${geistSans.variable} ${geistMono.variable} ${montserrat.variable} ${playfairDisplay.variable} antialiased`} className={`${geistSans.variable} ${geistMono.variable} ${montserrat.variable} ${playfairDisplay.variable} antialiased`}
@ -59,7 +58,7 @@ export default function RootLayout({
<div className="ie-panel"> <div className="ie-panel">
<a href="http://windows.microsoft.com/en-US/internet-explorer/"> <a href="http://windows.microsoft.com/en-US/internet-explorer/">
<img <img
src="images/ie8-panel/warning_bar_0000_us.jpg" src="/images/ie8-panel/warning_bar_0000_us.jpg"
height="42" height="42"
width="820" width="820"
alt="You are using an outdated browser. For a faster, safer browsing experience, upgrade for free today." alt="You are using an outdated browser. For a faster, safer browsing experience, upgrade for free today."

View File

@ -23,7 +23,7 @@ export default function Home() {
<ContactFormSection /> <ContactFormSection />
</div> </div>
<section className="section section-lg bg-default"> <section className="section section-lg bg-colorSection1">
<div className="container"> <div className="container">
<div className="row row-30"> <div className="row row-30">
<div className="col-md-6 col-lg-3"> <div className="col-md-6 col-lg-3">
@ -122,7 +122,7 @@ export default function Home() {
<GoogleReviewBox /> <GoogleReviewBox />
<section className="section section-lg bg-gray-12"> <section className="section section-lg bg-colorSection2">
<div className="container"> <div className="container">
<div className="layout-4"> <div className="layout-4">
<h2 className="heading-decoration-1"> <h2 className="heading-decoration-1">
@ -200,7 +200,7 @@ export default function Home() {
</div> </div>
</section> </section>
<section className="section section-lg bg-default"> <section className="section section-lg bg-colorSection1">
<div className="container"> <div className="container">
<h2 className="heading-decoration-1"> <h2 className="heading-decoration-1">
<span className="heading-inner">Find Us</span> <span className="heading-inner">Find Us</span>

View 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

View 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

View 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
}

View 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)

View 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)

View 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)

View File

View 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

View File

@ -1,3 +0,0 @@
export default function Blog() {
return <></>;
}

View File

@ -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>
);
}

View 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",
},
};

View 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
View 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
View 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
View 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",
},
],
};

View File

@ -1,6 +1,6 @@
export default function GoogleReviewBox() { export default function GoogleReviewBox() {
return ( return (
<section className="section section-lg bg-gray-12"> <section className="section section-lg bg-colorSection2">
<div className="container"> <div className="container">
<h2 className="heading-decoration-1"> <h2 className="heading-decoration-1">
<span className="heading-inner">Making Moves, Building Trust</span> <span className="heading-inner">Making Moves, Building Trust</span>

View 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>
);
}

View File

@ -52,7 +52,7 @@ export default function HeroSlider({ onClickBook }: HeroSliderProps) {
objectFit: "cover", 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="container">
<div className="row"> <div className="row">
<div className="col-12 col-lg-8 text-wrap space-y-5 md:space-y-8 lg:space-y-12"> <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
View 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>
);
}

View 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} />;
}

View 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>
);
}

View 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>
</>
);
}

View File

@ -10,7 +10,7 @@ export default function Footer() {
</h3> </h3>
<a className="post-minimal" href="single-property.html"> <a className="post-minimal" href="single-property.html">
<div className="post-minimal-image"> <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>
<div className="post-minimal-body"> <div className="post-minimal-body">
<div className="post-minimal-title"> <div className="post-minimal-title">
@ -25,7 +25,7 @@ export default function Footer() {
</a> </a>
<a className="post-minimal" href="single-property.html"> <a className="post-minimal" href="single-property.html">
<div className="post-minimal-image"> <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>
<div className="post-minimal-body"> <div className="post-minimal-body">
<div className="post-minimal-title"> <div className="post-minimal-title">

View File

@ -75,20 +75,20 @@ export default function Header() {
<img <img
className="brand-logo-dark" className="brand-logo-dark"
// src="images/logo-default-142x41.png" // src="images/logo-default-142x41.png"
src="images/logo2.png" src="/images/logo2.png"
alt="" alt=""
width="142" width="142"
height="41" height="41"
srcSet="images/logo2.png 2x" srcSet="/images/logo2.png 2x"
/> />
<img <img
className="brand-logo-light" className="brand-logo-light"
// src="images/logo-inverse-121x61.png"\ // src="images/logo-inverse-121x61.png"\
src="images/logo2.png" src="/images/logo2.png"
alt="" alt=""
width="121" width="121"
height="61" height="61"
srcSet="images/logo2.png 2x" srcSet="/images/logo2.png 2x"
/> />
</a> </a>
{/* <h3 className="text-colorHeaderText! font-montserrat! font-semibold! hidden lg:inline"> {/* <h3 className="text-colorHeaderText! font-montserrat! font-semibold! hidden lg:inline">
@ -123,7 +123,7 @@ export default function Header() {
</a> </a>
</li> </li>
<li className="rd-nav-item"> <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 BLOGS
</a> </a>
</li> </li>

View File

@ -5,8 +5,8 @@ import Script from "next/script";
export default function InitialScript() { export default function InitialScript() {
return ( return (
<> <>
<Script src="js/core.min.js" strategy="beforeInteractive" /> <Script src="/js/core.min.js" strategy="beforeInteractive" />
<Script src="js/script.js" strategy="lazyOnload" /> <Script src="/js/script.js" strategy="lazyOnload" />
</> </>
); );
} }

View File

@ -20,17 +20,17 @@ export function middleware(request: NextRequest) {
request.headers.set("x-meta-desc", metaDesc); request.headers.set("x-meta-desc", metaDesc);
// redirect for /blog/<slug>/ path // redirect for /blog/<slug>/ path
const blogPathRegex = /^\/blog\/([^\/]+)(\/[^\/]*)*\/?$/; // const blogPathRegex = /^\/blog\/([^\/]+)(\/[^\/]*)*\/?$/;
if (blogPathRegex.test(path) && !path.includes("/blog/?")) { // if (blogPathRegex.test(path) && !path.includes("/blog/?")) {
const newBlogPath = path.replace(/^\/blog/, "") || "/"; // const newBlogPath = path.replace(/^\/blog/, "") || "/";
return NextResponse.redirect(`${mainUrl}${newBlogPath}`, 301); // return NextResponse.redirect(`${mainUrl}${newBlogPath}`, 301);
} // }
// redirect for /about/<slug>/ path // redirect for /about/<slug>/ path
const aboutPathRegex = /^\/about\/([^\/]+)(\/[^\/]*)*\/?$/; // const aboutPathRegex = /^\/about\/([^\/]+)(\/[^\/]*)*\/?$/;
if (aboutPathRegex.test(path)) { // if (aboutPathRegex.test(path)) {
const newAboutPath = path.replace(/^\/about/, "") || "/"; // const newAboutPath = path.replace(/^\/about/, "") || "/";
return NextResponse.redirect(`${mainUrl}${newAboutPath}`, 301); // return NextResponse.redirect(`${mainUrl}${newAboutPath}`, 301);
} // }
return NextResponse.next({ return NextResponse.next({
request: { request: {

416
src/payload-types.ts Normal file
View 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
View 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
View File

@ -0,0 +1,7 @@
export type BlogData = {
slug?: string | null;
title: string;
description: string;
img?: { url: string; alt?: string };
posted_at: string;
};

View File

@ -0,0 +1,6 @@
export type FetchBlogParams = {
page?: number;
search?: string;
categoryId?: number;
tagId?: number;
};

View 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,
};
}

View 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
View 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
View 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
View 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;
}

View 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;

View 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
View 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}...`;
}

View File

@ -1,7 +1,11 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2017", "target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"], "lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
@ -19,9 +23,21 @@
} }
], ],
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": [
"./src/*"
],
"@payload-config": [
"./src/payload.config.ts"
]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": [
"exclude": ["node_modules"] "next-env.d.ts",
} "**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}

6424
yarn.lock

File diff suppressed because it is too large Load Diff