Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(next): added next blog starter template #1

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions next/blog/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/bcms.config.js
/bcms.routes.js
22 changes: 22 additions & 0 deletions next/blog/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
// "extends": "next/core-web-vitals",
"rules": {
"no-debugger": "warn",
"no-shadow": "error",
"@typescript-eslint/no-unused-vars": [
2,
{ "args": "all", "argsIgnorePattern": "^_" }
],
"no-unused-labels": "error",
"no-unused-expressions": "error",
"no-duplicate-imports": "error"
}
}
44 changes: 44 additions & 0 deletions next/blog/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
.env

# vercel
.vercel

# typescript
*.tsbuildinfo

# BCMS
/bcms
/logs
/public/bcms-media
/public/api/bcms-images
5 changes: 5 additions & 0 deletions next/blog/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 80
}
8 changes: 8 additions & 0 deletions next/blog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# BCMS NextJS Starter

This is a simple starter project for NextJS and [BCMS](https://thebcms.com). For more information visit [NextJS plugin repository](https://github.com/becomesco/next-plugin-bcms).

## Getting started

- Install dependencies: `npm i`
- Start a development server: `npm run dev`
91 changes: 91 additions & 0 deletions next/blog/api/_api-route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { BCMSMost, BCMSMostServerRoute } from "@becomes/cms-most/types";
import { HeaderEntry, HeaderEntryMeta } from "~~/bcms/types";
import { FooterEntry, FooterEntryMeta } from "~~/bcms/types/entry/footer";
import { APIResponse, Languages } from "~~/types";
import {getBcmsMost} from "next-plugin-bcms";

interface Route<Result = unknown, Body = unknown>
extends Omit<BCMSMostServerRoute<Result, Body>, "handler"> {
handler(data: {
url: string;
params: {
[name: string]: string;
};
query: {
[name: string]: string;
};
headers: {
[name: string]: string | string[] | undefined;
};
body: Body;
bcms: BCMSMost;
lng: Languages;
}): Promise<Result>;
}

export function apiRoute<Result = unknown, Body = unknown>(
route: Route<Result, Body>
): Route<
APIResponse & {
data: Result;
},
Body
> {
return {
method: route.method,
async handler(data) {
const lng = data.params.lng ? (data.params.lng as Languages) : "en";
const header = (await data.bcms.content.entry.findOne(
"header",
async () => true
)) as unknown as HeaderEntry;
const footer = (await data.bcms.content.entry.findOne(
"footer",
async () => true
)) as unknown as FooterEntry;
const result = await route.handler(data);
return {
data: result,
header: header.meta[lng] as HeaderEntryMeta,
footer: footer.meta[lng] as FooterEntryMeta,
};
},
};
}

export abstract class GenericApi {
public readonly bcms: BCMSMost

constructor() {
this.bcms = getBcmsMost()
}

public async fetchHeaderAndFooter(): Promise<{
header: HeaderEntryMeta;
footer: FooterEntryMeta;
}> {
const header = (await this.bcms.content.entry.findOne(
'header',
async () => true
)) as HeaderEntry;
const footer = (await this.bcms.content.entry.findOne(
'footer',
async () => true
)) as FooterEntry;

return {
header: header.meta.en as HeaderEntryMeta,
footer: footer.meta.en as FooterEntryMeta,
};
}


public async handler<T>(data: T): Promise<APIResponse<T>> {
const { header, footer } = await this.fetchHeaderAndFooter();
return {
data,
header,
footer,
};
}
}
33 changes: 33 additions & 0 deletions next/blog/api/about.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { BCMSPropRichTextDataParsed } from "@becomes/cms-client/types";
import {
AboutPageEntry,
AboutPageEntryMeta,

} from "~~/bcms/types";
import {AboutPageData, APIResponse} from "~~/types";
import { GenericApi} from "./_api-route";
export class AboutApi extends GenericApi {
public async getAboutPageData (): Promise<APIResponse<AboutPageData>> {
try {
const entry = (await this.bcms.content.entry.findOne(
"about_page",
async () => true
)) as unknown as AboutPageEntry;

if (!entry) {
throw new Error("About page entry does not exist.");
}

const data = {
meta: entry.meta.en as AboutPageEntryMeta,
content: entry.content.en as BCMSPropRichTextDataParsed,
}
return await this.handler<AboutPageData>(data)

} catch (error) {
console.error(error)
throw new Error('Failed to fetch about page data ')
}

}
}
90 changes: 90 additions & 0 deletions next/blog/api/blogs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { BCMSPropRichTextDataParsed } from "@becomes/cms-client/types";
import {
BlogEntry,
BlogEntryMeta,
BlogsPageEntry,
BlogsPageEntryMeta,
} from "~~/bcms/types";
import {APIResponse, BlogLight, BlogPageData, BlogsPageData} from "~~/types";
import {GenericApi} from "~/api/_api-route";

export const blogToLight = (blogs: BlogEntry[]): BlogLight[] => {
return blogs.map((e) => {
const meta = e.meta.en as BlogEntryMeta;

return {
title: meta.title,
slug: meta.slug,
cover: meta.cover,
description: meta.description,
date: meta.date,
category: meta.category,
};
});
};

export class BlogsApi extends GenericApi {
public async getBlogs(): Promise<APIResponse<BlogsPageData>> {
try {
const entry = (await this.bcms.content.entry.findOne(
'blogs_page',
async () => true
)) as BlogsPageEntry;

if (!entry) {
throw new Error('Blogs page entry does not exist.');
}

const blogs = (await this.bcms.content.entry.find(
'blog',
async () => true
)) as BlogEntry[];

const data: BlogsPageData = {
meta: entry.meta.en as BlogsPageEntryMeta,
blogs: blogToLight(
blogs.sort((a, b) => (b.meta.en?.date || 0) - (a.meta.en?.date || 0))
),
};

return await this.handler(data);
} catch (error) {
console.error(error);
throw new Error('Cannot fetch blogs at this time. Something went wrong.');
}
}

public async getSingleBlog(
params: { [p: string]: string }
): Promise<APIResponse<BlogPageData>> {
try {
const entry = (await this.bcms.content.entry.findOne(
'blog',
async (e) => e.meta.en.slug === params.slug
)) as BlogEntry;

if (!entry) {
throw new Error('Blog entry does not exist.');
}

const blogs = (await this.bcms.content.entry.find(
'blog',
async (e) => e.meta.en.slug !== params.slug
)) as BlogEntry[];

const data: BlogPageData = {
meta: entry.meta.en as BlogEntryMeta,
content: entry.content.en as BCMSPropRichTextDataParsed,
otherBlogs: blogToLight(
blogs.sort((a, b) => (b.meta.en?.date || 0) - (a.meta.en?.date || 0))
).slice(0, 3),
};

return await this.handler(data);
} catch (error) {
console.error(error);
throw new Error('Cannot fetch blog at this time. Something went wrong.');
}
}
}

22 changes: 22 additions & 0 deletions next/blog/api/contact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ContactPageEntry, ContactPageEntryMeta } from "~~/bcms/types";
import {APIResponse, ContactPageData} from "~~/types";
import { GenericApi} from "./_api-route";

export class ContactApi extends GenericApi {
public async getContactPage (): Promise<APIResponse<ContactPageData>> {
const entry = (await this.bcms.content.entry.findOne(
"contact_page",
async () => true
)) as unknown as ContactPageEntry;

if (!entry) {
throw new Error("Contact page entry does not exist.");
}

const data = {
meta: entry.meta.en as ContactPageEntryMeta,
};

return this.handler<ContactPageData>(data)
}
}
37 changes: 37 additions & 0 deletions next/blog/api/home.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { BlogEntry, HomePageEntry, HomePageEntryMeta } from "~~/bcms/types";
import {APIResponse, HomePageData} from "~~/types";
import { blogToLight } from "./blogs";
import {GenericApi} from "./_api-route";
export class HomeApi extends GenericApi{
public async getHomePageData (): Promise<APIResponse<HomePageData>> {
try {
const entry = (await this.bcms.content.entry.findOne(
"home_page",
async () => true
)) as unknown as HomePageEntry;

if (!entry) {
throw new Error("Home page entry does not exist.");
}

const blogs = (await this.bcms.content.entry.find("blog", async (e) =>
entry.meta.en?.hero.featured_blogs.find(
(i) => e.meta.en?.slug !== i.meta.en?.slug
)
)) as unknown as BlogEntry[];

const data = {
meta: entry.meta.en as HomePageEntryMeta,
blogs: blogToLight(
blogs.sort((a, b) => (b.meta.en?.date || 0) - (a.meta.en?.date || 0))
).slice(0, 6),
};

return await this.handler<HomePageData>(data)

} catch(error){
console.error(error)
throw new Error('Failed to fetch homepage data. something went wrong')
}
}
}
4 changes: 4 additions & 0 deletions next/blog/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./home";
export * from "./blogs";
export * from "./contact";
export * from "./about";
13 changes: 13 additions & 0 deletions next/blog/assets/css/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

*,
*::before,
*::after {
@apply box-border m-0 p-0;
}

body {
@apply font-Inter overflow-x-hidden bg-appBody;
}
12 changes: 12 additions & 0 deletions next/blog/assets/css/prose.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.prose h2 {
@apply leading-none font-medium tracking-[-0.41px] mb-[14px] md:text-xl md:leading-none lg:text-[32px] lg:leading-none lg:mb-6;
}
.prose p {
@apply text-sm leading-[1.4] tracking-[-0.41px] text-appGray-500 mb-3 md:text-base md:leading-[1.4] md:mb-4 lg:text-xl lg:leading-[1.4] lg:mb-6;
}
.prose p strong {
@apply text-appText font-medium;
}
.prose p a {
@apply underline text-inherit;
}
Loading