Merge pull request #1 from rankrunners-dev/payload

Payload CMS
This commit is contained in:
Rizqi Alaudin Syahrendra 2025-02-03 13:48:35 +07:00 committed by GitHub
commit ae27849a45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 8338 additions and 526 deletions

1
.gitignore vendored
View File

@ -32,6 +32,7 @@ yarn-error.log*
# env files (can opt-in for committing if needed)
.env*
!.env.example
# vercel
.vercel

View File

@ -2,35 +2,38 @@ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-
## Getting Started
First, run the development server:
### 1. Create .env file
You can create `.env` file based on `env` file, put some env variable that is required by payload cms.
### 2. Run
You can run the development server with old bundler:
```bash
npm run dev-old
```
or run the development server with new bundler (turbopack):
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
### 3. Open
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
You can access it by visiting:
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
```
http://localhost:3000
```
## Learn More
## Admin Panel
To learn more about Next.js, take a look at the following resources:
The admin panel has been provided by `Payload CMS`, you can access it by visiting:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
```
http://localhost:3000/admin
```

12
env
View File

@ -2,4 +2,14 @@ SERVICE_SUPABASESERVICE_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXB
SERVICE_SUPABASEANON_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTczODQ3MzA2MCwiZXhwIjo0ODk0MTQ2NjYwLCJyb2xlIjoiYW5vbiJ9.WXJvhzD1gD6Kxlq8PLFAcpaVFmh9h3xbJHh9oow2IFQ
SUPABASE_URL=https://supabasekong-n00g8kwoos4skc0gw44k8sks.dev3vds1.link
SUPABASE_URL=https://supabasekong-n00g8kwoos4skc0gw44k8sks.dev3vds1.link
<!-- Payload -->
DATABASE_URI=your-connection-string-here
PAYLOAD_SECRET=YOUR_SECRET_HERE
S3_BUCKET=YOUR_BUCKET
S3_ACCESS_KEY_ID=YOUR_ACCESS_KEY
S3_SECRET_ACCESS_KEY=YOUR_SECRET
S3_REGION=YOUR_REGION
S3_ENDPOINT=YOUR_ENDPOINT

View File

@ -1,21 +1,7 @@
import { withPayload } from "@payloadcms/next/withPayload";
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
// webpack: (config, options) => {
// config.module.rules.push({
// test: require.resolve("wowjs/dist/wow.js"),
// use: [
// {
// loader: "exports-loader",
// options: {
// exports: "this.WOW",
// },
// },
// ],
// });
// return config;
// },
images: {
remotePatterns: [
{
@ -26,4 +12,4 @@ const nextConfig: NextConfig = {
},
};
export default nextConfig;
export default withPayload(nextConfig);

7041
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,28 +2,42 @@
"name": "my-app",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev --turbopack",
"dev-old": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"payload:generate:types": "payload generate:types",
"tsc": "tsc"
},
"dependencies": {
"@payloadcms/db-postgres": "^3.20.0",
"@payloadcms/next": "^3.20.0",
"@payloadcms/payload-cloud": "^3.20.0",
"@payloadcms/richtext-lexical": "^3.20.0",
"@payloadcms/storage-s3": "^3.20.0",
"@popperjs/core": "2.11.8",
"bootstrap": "^5.1.3",
"dayjs": "^1.11.13",
"graphql": "^16.10.0",
"imagesloaded": "^5.0.0",
"isotope-layout": "^3.0.6",
"jarallax": "^2.2.1",
"next": "15.1.6",
"payload": "^3.20.0",
"photoswipe": "^5.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-modal-video": "^2.0.2",
"react-photoswipe-gallery": "^3.0.1",
"rellax": "^1.12.1",
"sharp": "^0.33.5",
"swiper": "^11.1.4",
"tippy.js": "^6.3.7",
"typewriter-effect": "^2.21.0",
"wow.js": "^1.2.2",
"wowjs": "^1.1.3"
},
"devDependencies": {
@ -39,9 +53,10 @@
"eslint-config-next": "15.1.6",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.3",
"exports-loader": "^5.0.0",
"postcss": "^8",
"prettier": "^3.4.2",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
}

View File

@ -0,0 +1,145 @@
import BlogComments from "@/components/Blogs/BlogComments";
import BlogWidget from "@/components/Blogs/BlogWidget";
import CommentForm from "@/components/CommentForm";
import { fetchBlogDetail } from "@/services/payload/blog";
import { RichText } from "@payloadcms/richtext-lexical/react";
import { Metadata } from "next";
import Image from "next/image";
export async function generateMetadata({
params,
}: {
params: { slug: string };
}): Promise<Metadata> {
const blog = await fetchBlogDetail(params.slug);
if (!blog) {
return {
title: "Cochise Oncology",
description: "Cochise Oncology",
openGraph: {
title: "Cochise Oncology",
description: "Cochise Oncology",
images: [""],
},
};
}
const title = `${blog.data.title} - Cochise Oncology`;
return {
title: title,
description: title,
openGraph: {
title: title,
description: title,
images: [blog.imgUrl || ""],
},
};
}
export default async function SingleBlogPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const slug = (await params).slug;
const data = await fetchBlogDetail(slug);
if (!data) return <></>;
return (
<>
<section
className="page-section bg-gradient-gray-light-1 bg-scroll overflow-hidden"
id="home"
>
{/* <!-- Background Shape --> */}
<div className="bg-shape-1 wow fadeIn">
<Image
src="/assets/images/demo-fancy/bg-shape-1.svg"
width={1443}
height={844}
alt=""
/>
</div>
{/* <!-- End Background Shape --> */}
<div className="container position-relative pt-sm-40 text-center">
<div className="row">
<div className="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
<h1 className="hs-title-10 mb-10 wow fadeInUp">
{data.data.title}
</h1>
{/* Author, Categories, Comments */}
<div
className="blog-item-data mb-0 wow fadeIn"
data-wow-delay="0.2s"
>
<div className="d-inline-block me-3">
<a href="#">
<i className="mi-clock size-16" />
<span className="visually-hidden">Date:</span>{" "}
{data.createdAt}
</a>
</div>
<div className="d-inline-block me-3">
<i className="mi-folder size-16" />
<span className="visually-hidden">Categories:</span>{" "}
<a href="#">Design</a>, <a href="#">Branding</a>
</div>
</div>
{/* End Author, Categories, Comments */}
</div>
</div>
</div>
</section>
<>
{/* Section */}
<section className="page-section">
<div className="container relative">
<div className="row">
{/* Content */}
<div className="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
{/* Post */}
<div className="blog-item mb-80 mb-xs-40">
<div className="blog-item-body">
<div className="mb-40 mb-xs-30">
<Image
src={data.imgUrl}
alt="Image Description"
width={1350}
height={759}
className="round"
/>
</div>
<div>
<RichText data={data.data.content} />
</div>
</div>
</div>
{/* End Post */}
</div>
{/* End Content */}
</div>
</div>
</section>
{/* End Section */}
{/* Divider */}
<hr className="mt-0 mb-0" />
{/* End Divider */}
{/* Section */}
<section className="page-section">
<div className="container relative">
<div className="row mt-n60">
<BlogWidget
inputClass="newsletter-field form-control input-md circle mb-10"
btnClass="btn btn-mod btn-color btn-medium btn-circle btn-hover-anim form-control"
/>
</div>
</div>
</section>
</>
</>
);
}

View File

@ -1,19 +1,22 @@
import Blogs from "@/components/Blogs";
import NewsletterForm from "@/components/NewsletterForm";
import Blogs from "@/components/Blogs/Blogs";
import { archiveLinks } from "@/data/archieve";
import { categories } from "@/data/categories";
import { tags } from "@/data/tags";
import { sanitizePageNumber } from "@/utils/sanitize";
import Image from "next/image";
export const metadata = {
title:
"Slick Blogs || Resonance &mdash; One & Multi Page React Nextjs Creative Template",
description:
"Resonance &mdash; One & Multi Page React Nextjs Creative Template",
title: "Blogs | Cochise Oncology",
description: "Blogs | Cochise Oncology",
};
const onePage = false;
const dark = false;
export default function SlickBlogPage() {
export default async function BlogPage({
searchParams,
}: {
searchParams?: { page?: string };
}) {
const page = sanitizePageNumber(searchParams?.page);
return (
<>
<section
@ -47,7 +50,7 @@ export default function SlickBlogPage() {
</div>
</section>
<section className="page-section" id="blog">
<Blogs />
<Blogs page={page} />
</section>
<>
@ -58,7 +61,7 @@ export default function SlickBlogPage() {
<section className="page-section">
<div className="container relative">
<div className="row mt-n60">
<div className="col-sm-6 col-lg-3 mt-60">
<div className="col-sm-6 col-lg-4 mt-60">
{/* Widget */}
<div className="widget mb-0">
<h3 className="widget-title">Categories</h3>
@ -77,7 +80,7 @@ export default function SlickBlogPage() {
</div>
{/* End Widget */}
</div>
<div className="col-sm-6 col-lg-3 mt-60">
<div className="col-sm-6 col-lg-4 mt-60">
{/* Widget */}
<div className="widget mb-0">
<h3 className="widget-title">Tags</h3>
@ -93,7 +96,7 @@ export default function SlickBlogPage() {
</div>
{/* End Widget */}
</div>
<div className="col-sm-6 col-lg-3 mt-60">
<div className="col-sm-6 col-lg-4 mt-60">
{/* Widget */}
<div className="widget mb-0">
<h3 className="widget-title">Archive</h3>
@ -111,18 +114,6 @@ export default function SlickBlogPage() {
</div>
{/* End Widget */}
</div>
<div className="col-sm-6 col-lg-3 mt-60">
{/* Widget */}
<div className="widget mb-0">
<h3 className="widget-title">Newsletter</h3>
<div className="widget-body">
<div className="widget-text clearfix">
<NewsletterForm />
</div>
</div>
</div>
{/* End Widget */}
</div>
</div>
</div>
</section>

View File

@ -1,202 +0,0 @@
import BlogComments from "@/components/BlogComments";
import BlogWidget from "@/components/BlogWidget";
import CommentForm from "@/components/CommentForm";
import { allBlogs } from "@/data/blogs";
import Image from "next/image";
export const metadata = {
title:
"Slick Blogs Single || Resonance &mdash; One & Multi Page React Nextjs Creative Template",
description:
"Resonance &mdash; One & Multi Page React Nextjs Creative Template",
};
export default async function asyncSlickBlogSinglePage(
params: Promise<{ id: number }>
) {
const id = (await params).id;
const blog = allBlogs.filter((elm) => elm.id == id)[0] || allBlogs[0];
return (
<>
<section
className="page-section bg-gradient-gray-light-1 bg-scroll overflow-hidden"
id="home"
>
{/* <!-- Background Shape --> */}
<div className="bg-shape-1 wow fadeIn">
<Image
src="/assets/images/demo-fancy/bg-shape-1.svg"
width={1443}
height={844}
alt=""
/>
</div>
{/* <!-- End Background Shape --> */}
<div className="container position-relative pt-sm-40 text-center">
<div className="row">
<div className="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
<h1 className="hs-title-10 mb-10 wow fadeInUp">
{/* @ts-ignore */}
{blog?.title || blog?.postTitle}
</h1>
{/* Author, Categories, Comments */}
<div
className="blog-item-data mb-0 wow fadeIn"
data-wow-delay="0.2s"
>
<div className="d-inline-block me-3">
<a href="#">
<i className="mi-clock size-16" />
<span className="visually-hidden">Date:</span> December 25
</a>
</div>
<div className="d-inline-block me-3">
<a href="#">
<i className="mi-user size-16" />
<span className="visually-hidden">Author:</span> John Doe
</a>
</div>
<div className="d-inline-block me-3">
<i className="mi-folder size-16" />
<span className="visually-hidden">Categories:</span>
<a href="#">Design</a>, <a href="#">Branding</a>
</div>
<div className="d-inline-block me-3">
<a href="#">
<i className="mi-message size-16" /> 5 Comments
</a>
</div>
</div>
{/* End Author, Categories, Comments */}
</div>
</div>
</div>
</section>
<>
{/* Section */}
<section className="page-section">
<div className="container relative">
<div className="row">
{/* Content */}
<div className="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
{/* Post */}
<div className="blog-item mb-80 mb-xs-40">
<div className="blog-item-body">
<div className="mb-40 mb-xs-30">
<Image
src="/assets/images/demo-fancy/blog/post-prev-3-large.jpg"
alt="Image Description"
width={1350}
height={759}
className="round"
/>
</div>
<p>
Morbi lacus massa, euismod ut turpis molestie, tristique
sodales est. Integer sit amet mi id sapien tempor molestie
in nec massa. Fusce non ante sed lorem rutrum feugiat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Mauris non laoreet dui. Morbi lacus massa, euismod ut
turpis molestie, tristique sodales est. Integer sit amet
mi id sapien tempor molestie in nec massa.
</p>
<p>
Fusce non ante sed lorem rutrum feugiat. Vestibulum
pellentesque, purus ut&nbsp;dignissim consectetur, nulla
erat ultrices purus, ut&nbsp;consequat sem elit non sem.
Morbi lacus massa, euismod ut turpis molestie, tristique
sodales est. Integer sit amet mi id sapien tempor molestie
in nec massa. Fusce non ante sed lorem rutrum feugiat.
</p>
<blockquote>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer posuere erat a&nbsp;ante. Vestibulum
pellentesque, purus ut dignissim consectetur, nulla erat
ultrices purus.
</p>
<footer>
Someone famous in
<cite title="Source Title"> Source Title </cite>
</footer>
</blockquote>
<p>
Praesent ultricies ut ipsum non laoreet. Nunc ac
<a href="#">ultricies</a> leo. Nulla ac ultrices arcu.
Nullam adipiscing lacus in consectetur posuere. Nunc
malesuada tellus turpis, ac pretium orci molestie vel.
Morbi lacus massa, euismod ut turpis molestie, tristique
sodales est. Integer sit amet mi id sapien tempor molestie
in nec massa. Fusce non ante sed lorem rutrum feugiat.
</p>
<ul>
<li>First item of the list</li>
<li>Second item of the list</li>
<li>Third item of the list</li>
</ul>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Mauris non laoreet dui. Morbi lacus massa, euismod ut
turpis molestie, tristique sodales est. Integer sit amet
mi id sapien tempor molestie in nec massa. Fusce non ante
sed lorem rutrum feugiat. Vestibulum pellentesque, purus
ut&nbsp;dignissim consectetur, nulla erat ultrices purus,
ut&nbsp;consequat sem elit non sem.
</p>
</div>
</div>
{/* End Post */}
{/* Comments */}
<div className="mb-80 mb-xs-40">
<h4 className="blog-page-title">
Comments <small className="number">(3)</small>
</h4>
<ul className="media-list comment-list clearlist">
<BlogComments />
</ul>
</div>
{/* End Comments */}
{/* Add Comment */}
<div className="mb-80 mb-xs-40">
<h4 className="blog-page-title">Leave a comment</h4>
{/* Form */}
<CommentForm />
{/* End Form */}
</div>
{/* End Add Comment */}
{/* Prev/Next Post */}
<div className="clearfix mt-40">
<a href="#" className="blog-item-more circle left">
<i className="mi-chevron-left" />
&nbsp;Prev post
</a>
<a href="#" className="blog-item-more circle right">
Next post&nbsp;
<i className="mi-chevron-right" />
</a>
</div>
{/* End Prev/Next Post */}
</div>
{/* End Content */}
</div>
</div>
</section>
{/* End Section */}
{/* Divider */}
<hr className="mt-0 mb-0" />
{/* End Divider */}
{/* Section */}
<section className="page-section">
<div className="container relative">
<div className="row mt-n60">
<BlogWidget
inputClass="newsletter-field form-control input-md circle mb-10"
btnClass="btn btn-mod btn-color btn-medium btn-circle btn-hover-anim form-control"
/>
</div>
</div>
</section>
</>
</>
);
}

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,47 @@
import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { RscEntryLexicalField as RscEntryLexicalField_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'
export const importMap = {
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_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
}

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

@ -0,0 +1,19 @@
import React from 'react'
import './styles.css'
export const metadata = {
description: 'A blank template using Payload in a Next.js app.',
title: 'Payload Blank Template',
}
export default async function RootLayout(props: { children: React.ReactNode }) {
const { children } = props
return (
<html lang="en">
<body>
<main>{children}</main>
</body>
</html>
)
}

View File

@ -0,0 +1,59 @@
import { headers as getHeaders } from 'next/headers.js'
import Image from 'next/image'
import { getPayload } from 'payload'
import React from 'react'
import { fileURLToPath } from 'url'
import config from '@/payload.config'
import './styles.css'
export default async function HomePage() {
const headers = await getHeaders()
const payloadConfig = await config
const payload = await getPayload({ config: payloadConfig })
const { user } = await payload.auth({ headers })
const fileURL = `vscode://file/${fileURLToPath(import.meta.url)}`
return (
<div className="home">
<div className="content">
<picture>
<source srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-favicon.svg" />
<Image
alt="Payload Logo"
height={65}
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-favicon.svg"
width={65}
/>
</picture>
{!user && <h1>Welcome to your new project.</h1>}
{user && <h1>Welcome back, {user.email}</h1>}
<div className="links">
<a
className="admin"
href={payloadConfig.routes.admin}
rel="noopener noreferrer"
target="_blank"
>
Go to admin panel
</a>
<a
className="docs"
href="https://payloadcms.com/docs"
rel="noopener noreferrer"
target="_blank"
>
Documentation
</a>
</div>
</div>
<div className="footer">
<p>Update this page by editing</p>
<a className="codeLink" href={fileURL}>
<code>app/(frontend)/page.tsx</code>
</a>
</div>
</div>
)
}

View File

@ -0,0 +1,164 @@
:root {
--font-mono: 'Roboto Mono', monospace;
}
* {
box-sizing: border-box;
}
html {
font-size: 18px;
line-height: 32px;
background: rgb(0, 0, 0);
-webkit-font-smoothing: antialiased;
}
html,
body,
#app {
height: 100%;
}
body {
font-family: system-ui;
font-size: 18px;
line-height: 32px;
margin: 0;
color: rgb(1000, 1000, 1000);
@media (max-width: 1024px) {
font-size: 15px;
line-height: 24px;
}
}
img {
max-width: 100%;
height: auto;
display: block;
}
h1 {
margin: 40px 0;
font-size: 64px;
line-height: 70px;
font-weight: bold;
@media (max-width: 1024px) {
margin: 24px 0;
font-size: 42px;
line-height: 42px;
}
@media (max-width: 768px) {
font-size: 38px;
line-height: 38px;
}
@media (max-width: 400px) {
font-size: 32px;
line-height: 32px;
}
}
p {
margin: 24px 0;
@media (max-width: 1024px) {
margin: calc(var(--base) * 0.75) 0;
}
}
a {
color: currentColor;
&:focus {
opacity: 0.8;
outline: none;
}
&:active {
opacity: 0.7;
outline: none;
}
}
svg {
vertical-align: middle;
}
.home {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
height: 100vh;
padding: 45px;
max-width: 1024px;
margin: 0 auto;
overflow: hidden;
@media (max-width: 400px) {
padding: 24px;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-grow: 1;
h1 {
text-align: center;
}
}
.links {
display: flex;
align-items: center;
gap: 12px;
a {
text-decoration: none;
padding: 0.25rem 0.5rem;
border-radius: 4px;
}
.admin {
color: rgb(0, 0, 0);
background: rgb(1000, 1000, 1000);
border: 1px solid rgb(0, 0, 0);
}
.docs {
color: rgb(1000, 1000, 1000);
background: rgb(0, 0, 0);
border: 1px solid rgb(1000, 1000, 1000);
}
}
.footer {
display: flex;
align-items: center;
gap: 8px;
@media (max-width: 1024px) {
flex-direction: column;
gap: 6px;
}
p {
margin: 0;
}
.codeLink {
text-decoration: none;
padding: 0 0.5rem;
background: rgb(60, 60, 60);
border-radius: 4px;
}
}
}

View File

@ -1,7 +0,0 @@
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return <>{children}</>;
}

14
src/app/my-route/route.ts Normal file
View File

@ -0,0 +1,14 @@
import configPromise from '@payload-config'
import { getPayload } from 'payload'
export const GET = async () => {
const payload = await getPayload({
config: configPromise,
})
const data = await payload.find({
collection: 'users',
})
return Response.json(data)
}

31
src/collections/Blogs.ts Normal file
View File

@ -0,0 +1,31 @@
import type { CollectionConfig } from "payload";
import { lexicalEditor } from "@payloadcms/richtext-lexical";
export const Blogs: CollectionConfig = {
slug: "blogs",
fields: [
{
name: "title",
type: "text",
required: true,
},
{
name: "slug",
type: "text",
required: true,
},
{
name: "img",
label: "Image",
type: "upload",
relationTo: "media",
required: true,
},
{
name: "content",
type: "richText",
required: true,
editor: lexicalEditor({}),
},
],
};

16
src/collections/Media.ts Normal file
View File

@ -0,0 +1,16 @@
import type { CollectionConfig } from "payload";
export const Media: CollectionConfig = {
slug: "media",
access: {
read: () => true,
},
fields: [
{
name: "alt",
type: "text",
required: true,
},
],
upload: true,
};

13
src/collections/Users.ts Normal file
View File

@ -0,0 +1,13 @@
import type { CollectionConfig } from "payload";
export const Users: CollectionConfig = {
slug: "users",
admin: {
useAsTitle: "email",
},
auth: true,
fields: [
// Email added by default
// Add more fields as needed
],
};

View File

@ -1,63 +0,0 @@
import Pagination from "@/components/Pagination";
import { blogs11 } from "@/data/blogs";
import Image from "next/image";
import Link from "next/link";
import React from "react";
export default function Blogs() {
return (
<div className="container position-relative">
{/* Blog Posts Grid */}
<div className="row mt-n50 mb-50">
{/* Post Item */}
{blogs11.map((elm, i) => (
<div
key={i}
className="post-prev-3 col-12 col-lg-10 offset-lg-1 col-xl-6 offset-xl-0 mt-50"
>
<div className="post-prev-3-container d-block d-sm-flex">
<div className="post-prev-3-img">
<Link href={`/slick-blog-single/${elm.id}`}>
<Image
width={400}
height={488}
src={elm.imgSrc}
alt="Add Image Description"
/>
</Link>
</div>
<div className="post-prev-3-intro">
<h4 className="post-prev-3-title">
<Link href={`/slick-blog-single/${elm.id}`}>{elm.title}</Link>
</h4>
<div className="post-prev-3-text">{elm.text}</div>
<div className="post-prev-3-info clearfix">
<div className="float-start">
<a href="#">
<Image
className="post-prev-3-author-img"
width={30}
height={30}
src={elm.authorImg}
alt="Image Description"
/>
</a>
<a href="#">{elm.author}</a>
</div>
<div className="float-end">
<a href="#">{elm.date}</a>
</div>
</div>
</div>
</div>
</div>
))}
{/* End Post Item */}
</div>
{/* End Blog Posts Grid */}
{/* Pagination */}
<Pagination />
{/* End Pagination */}
</div>
);
}

View File

@ -0,0 +1,63 @@
import Image from "next/image";
import Link from "next/link";
export interface BlogCardItemProps {
data: {
slug: string;
title: string;
img?: {
url: string;
alt: string;
};
contentPreview: string;
author?: {
name: string;
img: string;
};
date: string;
};
}
export function BlogCardItem({ data }: BlogCardItemProps) {
return (
<div className="post-prev-3 col-12 col-lg-10 offset-lg-1 col-xl-6 offset-xl-0 mt-50">
<div className="post-prev-3-container d-block d-sm-flex">
<div className="post-prev-3-img">
<Link href={`/blog/${data.slug}`}>
<Image
width={400}
height={488}
src={data?.img?.url ?? ""}
alt={data?.img?.alt ?? ""}
/>
</Link>
</div>
<div className="post-prev-3-intro">
<h4 className="post-prev-3-title">
<Link href={`/blog/${data.slug}`}>{data.title}</Link>
</h4>
<div className="post-prev-3-text">{data.contentPreview}</div>
<div className="post-prev-3-info clearfix">
{!!data?.author?.name && (
<div className="float-start">
<a href="#">
<Image
className="post-prev-3-author-img"
width={30}
height={30}
src={data.author?.img ?? "#"}
alt="Image Description"
/>
</a>
<a href="#">{data.author.name}</a>
</div>
)}
<div className={!!data?.author?.name ? "float-end" : "float-start"}>
<a href="#">{data.date}</a>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@ -10,7 +10,7 @@ export default function BlogWidget({
}) {
return (
<>
<div className="col-sm-6 col-lg-3 mt-60">
<div className="col-sm-6 col-lg-4 mt-60">
{/* Widget */}
<div className="widget mb-0">
<h3 className="widget-title">Categories</h3>
@ -29,7 +29,7 @@ export default function BlogWidget({
</div>
{/* End Widget */}
</div>
<div className="col-sm-6 col-lg-3 mt-60">
<div className="col-sm-6 col-lg-4 mt-60">
{/* Widget */}
<div className="widget mb-0">
<h3 className="widget-title">Tags</h3>
@ -45,7 +45,7 @@ export default function BlogWidget({
</div>
{/* End Widget */}
</div>
<div className="col-sm-6 col-lg-3 mt-60">
<div className="col-sm-6 col-lg-4 mt-60">
{/* Widget */}
<div className="widget mb-0">
<h3 className="widget-title">Archive</h3>
@ -63,52 +63,6 @@ export default function BlogWidget({
</div>
{/* End Widget */}
</div>
<div className="col-sm-6 col-lg-3 mt-60">
<div className="widget mb-0">
<h3 className="widget-title">Newsletter</h3>
<div className="widget-body">
<div className="widget-text clearfix">
<form
onSubmit={(e) => e.preventDefault()}
className="form"
id="mailchimp"
>
<div className="mb-20">Stay informed with our newsletter.</div>
<div className="mb-20">
<input
placeholder="Enter your email"
className={inputClass}
type="email"
pattern=".{5,100}"
required
aria-required="true"
/>
<button type="submit" className={btnClass}>
<span>Subscribe</span>
</button>
</div>
<div className="form-tip">
<i className="icon-info size-16" aria-hidden="true"></i>
By sending the form you agree to the
<a href="#">Terms &amp; Conditions</a> and
<a href="#">Privacy Policy</a>.
</div>
<div
id="subscribe-result"
role="region"
aria-live="polite"
aria-atomic="true"
></div>
</form>
</div>
</div>
</div>
</div>
</>
);
}

View File

@ -0,0 +1,56 @@
import Pagination from "@/components/Pagination";
import { fetchBlog } from "@/services/payload/blog";
import { BlogCardItem } from "./BlogCardItem";
import { sanitizeBlogContentIntoStringPreview } from "@/utils/sanitize";
export interface BlogsProps {
page: number;
}
export default async function Blogs({ page }: BlogsProps) {
const data = await fetchBlog(page);
if (!data?.totalDocs) return <></>;
const handleClickPage = (clickedPage: number) => {
if (typeof window === "undefined") return;
window.location.href = `/blog/?page=${clickedPage}`;
};
return (
<div className="container position-relative">
{/* Blog Posts Grid */}
<div className="row mt-n50 mb-50">
{/* Post Item */}
{data.formattedData.map((blog, i) => (
<BlogCardItem
key={i}
data={{
slug: blog.slug,
title: blog.title,
contentPreview: sanitizeBlogContentIntoStringPreview(
blog.content
),
date: blog.createdAtFormatted,
img: blog.imgFormatted,
}}
/>
))}
{/* End Post Item */}
</div>
{/* End Blog Posts Grid */}
{/* Pagination */}
{data.totalPages > 1 && (
<Pagination
page={data.page ?? 1}
hasNextPage={data.hasNextPage}
hasPreviousPage={data.hasPrevPage}
totalPages={data.totalPages}
onClickPage={handleClickPage}
/>
)}
{/* End Pagination */}
</div>
);
}

View File

@ -4,7 +4,7 @@ import Facts from "./Facts";
import Service from "./Service";
import Image from "next/image";
import Testimonials from "./Testimonials";
import Blog from "./Blog";
import Blog from "./Blogs/Blog";
import Newsletter from "./Newsletter";
import Contact from "./Contact";
import Link from "next/link";

View File

@ -1,78 +1,104 @@
"use client";
import React, { useState } from "react";
export default function Pagination({ className }: { className?: string }) {
const [activePage, setActivePage] = useState(1); // Initialize active page
import React from "react";
interface PaginationProps {
page: number;
hasPreviousPage: boolean;
hasNextPage: boolean;
totalPages: number;
onClickPage?: (page: number) => void;
}
export default function Pagination({
page,
hasPreviousPage,
hasNextPage,
totalPages,
onClickPage,
}: PaginationProps) {
const activePage = page;
// Function to handle page change
const handlePageChange = (page: number) => {
setActivePage(page);
const handlePageChange = (page: string | number) => {
if (typeof page === "string") return;
onClickPage?.(page);
};
const getPageNumbers = () => {
const pages = [];
const showEllipsisStart = activePage > 4;
const showEllipsisEnd = activePage < totalPages - 3;
if (totalPages <= 7) {
// Show all pages if total is 7 or less
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
} else {
// Always show first page
pages.push(1);
if (showEllipsisStart) {
pages.push("...");
}
// Show pages around current page
const start = showEllipsisStart ? Math.max(2, activePage - 1) : 2;
const end = showEllipsisEnd
? Math.min(totalPages - 1, activePage + 1)
: totalPages - 1;
for (let i = start; i <= end; i++) {
pages.push(i);
}
if (showEllipsisEnd) {
pages.push("...");
}
// Always show last page
pages.push(totalPages);
}
return pages;
};
return (
<div
className={className ? className : "pagination justify-content-center"}
>
<div className={"pagination justify-content-center"}>
{/* Previous Page Button */}
<a
onClick={() => activePage > 1 && handlePageChange(activePage - 1)}
className={activePage === 1 ? "disabled" : ""}
>
<i className="mi-chevron-left" />
<span className="visually-hidden">Previous page</span>
</a>
{/* Page Number 1 */}
<a
onClick={() => handlePageChange(1)}
className={activePage === 1 ? "active" : ""}
>
1
</a>
{/* Page Number 2 */}
<a
onClick={() => handlePageChange(2)}
className={activePage === 2 ? "active" : ""}
>
2
</a>
{/* Page Number 3 */}
<a
onClick={() => handlePageChange(3)}
className={activePage === 3 ? "active" : ""}
>
3
</a>
{activePage > 4 && activePage < 8 && (
<span className="no-active">...</span>
{hasPreviousPage && (
<a
onClick={() => activePage > 1 && handlePageChange(activePage - 1)}
className={activePage === 1 ? "disabled" : ""}
>
<i className="mi-chevron-left" />
<span className="visually-hidden">Previous page</span>
</a>
)}
{activePage > 3 && activePage < 8 && (
<a className={"active"}>{activePage}</a>
)}
{/* Ellipsis */}
<span className="no-active">...</span>
{activePage == 8 && <a className={"active"}>{8}</a>}
{/* Page Number 9 */}
<a
onClick={() => handlePageChange(9)}
className={activePage === 9 ? "active" : ""}
>
9
</a>
{getPageNumbers().map((page, key) => (
<a
key={key}
onClick={() => handlePageChange(page)}
className={activePage === page ? "active" : ""}
>
{page}
</a>
))}
{/* Next Page Button */}
<a
onClick={() => activePage < 9 && handlePageChange(activePage + 1)}
className={activePage === 9 ? "disabled" : ""}
>
<i className="mi-chevron-right" />
<span className="visually-hidden">Next page</span>
</a>
{hasNextPage && (
<a
onClick={() =>
activePage < totalPages && handlePageChange(activePage + 1)
}
className={activePage === totalPages ? "disabled" : ""}
>
<i className="mi-chevron-right" />
<span className="visually-hidden">Next page</span>
</a>
)}
</div>
);
}

View File

@ -28,7 +28,7 @@ export const slickMultipages = [
{ href: "/slick-about", text: "About", class: "active" },
{ href: "/slick-services", text: "Services" },
{ href: "/slick-portfolio", text: "Portfolio" },
{ href: "/slick-blog", text: "Blog" },
{ href: "/blog", text: "Blog" },
{ href: "/slick-contact", text: "Contact" },
];
export const slickMultipagesDark = [
@ -36,7 +36,7 @@ export const slickMultipagesDark = [
{ href: "/slick-about-dark", text: "About", class: "active" },
{ href: "/slick-services-dark", text: "Services" },
{ href: "/slick-portfolio-dark", text: "Portfolio" },
{ href: "/slick-blog-dark", text: "Blog" },
{ href: "/blog", text: "Blog" },
{ href: "/slick-contact-dark", text: "Contact" },
];
export const slickOnepage = [

276
src/payload-types.ts Normal file
View File

@ -0,0 +1,276 @@
/* 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.
*/
export interface Config {
auth: {
users: UserAuthOperations;
};
collections: {
users: User;
media: Media;
blogs: Blog;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
collectionsJoins: {};
collectionsSelect: {
users: UsersSelect<false> | UsersSelect<true>;
media: MediaSelect<false> | MediaSelect<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;
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` "blogs".
*/
export interface Blog {
id: number;
title: string;
slug: string;
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;
};
updatedAt: string;
createdAt: string;
}
/**
* 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: '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> {
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` "blogs_select".
*/
export interface BlogsSelect<T extends boolean = true> {
title?: T;
slug?: T;
img?: T;
content?: T;
updatedAt?: T;
createdAt?: 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 {}
}

56
src/payload.config.ts Normal file
View File

@ -0,0 +1,56 @@
// storage-adapter-import-placeholder
import { postgresAdapter } from "@payloadcms/db-postgres";
import { payloadCloudPlugin } from "@payloadcms/payload-cloud";
import { s3Storage } from "@payloadcms/storage-s3";
import path from "path";
import { buildConfig } from "payload";
import { fileURLToPath } from "url";
import sharp from "sharp";
import { Users } from "./collections/Users";
import { Media } from "./collections/Media";
import { Blogs } from "./collections/Blogs";
const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);
export default buildConfig({
admin: {
user: Users.slug,
importMap: {
baseDir: path.resolve(dirname),
},
},
collections: [Users, Media, Blogs],
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,
},
}),
],
});

View File

@ -0,0 +1,52 @@
import payloadConfig from "@/payload.config";
import { formatDate } from "@/utils/datetime";
import { getPayload } from "payload";
export async function fetchBlog(page: number = 1) {
const payload = await getPayload({ config: payloadConfig });
const blogDataQuery = await payload.find({
collection: "blogs",
page,
pagination: true,
});
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) {
const payload = await getPayload({ config: payloadConfig });
const blogDataQuery = await payload.find({
collection: "blogs",
where: {
slug: { equals: slug },
},
limit: 1,
pagination: false,
});
if (!blogDataQuery?.docs?.[0]) return null;
const data = blogDataQuery?.docs?.[0];
const createdAt = formatDate(data.createdAt);
const imgUrl = typeof data.img !== "number" ? (data?.img?.url ?? "") : "";
return {
data,
createdAt,
imgUrl,
};
}

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

View File

@ -1,8 +1,9 @@
// @ts-nocheck
/* eslint-disable @typescript-eslint/no-require-imports */
// @ts-nocheck
export function init_wow() {
const { WOW } = require("wowjs");
const WOW = require("wow.js");
setTimeout(() => {
/* Wow init */
if (document.body.classList.contains("appear-animate")) {
@ -14,13 +15,11 @@ export function init_wow() {
boxClass: "wow",
animateClass: "animatedfgfg",
offset: 100,
live: false,
callback: function (box) {
box.classList.add("animated");
},
});
if (document.body.classList.contains("appear-animate")) {
wow.init();
} else {
@ -28,7 +27,6 @@ export function init_wow() {
.querySelectorAll(".wow")
.forEach((el) => (el.style.opacity = "1"));
}
/* Wow for portfolio init */
if (document.body.classList.contains("appear-animate")) {
document
@ -39,13 +37,11 @@ export function init_wow() {
boxClass: "wow-p",
animateClass: "animatedfgfg",
offset: 100,
live: false,
callback: function (box) {
box.classList.add("animated");
},
});
if (document.body.classList.contains("appear-animate")) {
wow_p.init();
} else {
@ -53,7 +49,6 @@ export function init_wow() {
.querySelectorAll(".wow-p")
.forEach((el) => (el.style.opacity = "1"));
}
/* Wow for menu bar init */
if (
document.body.classList.contains("appear-animate") &&

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": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@ -19,10 +23,25 @@
}
],
"paths": {
"@/*": ["./src/*"],
"@public/*": ["./public/*"]
"@/*": [
"./src/*"
],
"@public/*": [
"./public/*"
],
"@payload-config": [
"./src/payload.config.ts"
]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/utils/initWow.js"],
"exclude": ["node_modules"]
}
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"src/utils/initWow.js"
],
"exclude": [
"node_modules"
]
}