From 3393863dd26dd4127935a220407cd6a7abfee4e9 Mon Sep 17 00:00:00 2001 From: Giancarlo Buomprisco Date: Thu, 11 Jul 2024 15:06:15 +0800 Subject: [PATCH] Add status property to content item structure (#44) * Add status property to content item structure This commit introduces a new `status` property to the content item structure, allowing content items to maintain a status such as 'draft', 'published', 'review', 'pending'. This status is mapped to the corresponding status in Wordpress and Keystatic clients to ensure consistent usage across platforms. Content retrieval methods now also include a status filter. * Refactor code for style and readability improvements This commit includes changes in various files across different packages to improve code readability, including adjusting spacing and breaking down complex lines into simpler parts. In addition, the switch statement in `wp-client.ts` has been refactored and status values are now held in variables, and the CSS classes in `global-loader.tsx` have been reorganized. --- .../authentication/authentication.mdoc | 1 + .../authentication/configuration.mdoc | 1 + .../getting-started/getting-started.mdoc | 1 + .../installing-dependencies.mdoc | 1 + .../content/posts/brainstorming-ideas.mdoc | 1 + .../web/content/posts/must-have-features.mdoc | 1 + .../web/content/posts/saas-starter-guide.mdoc | 1 + packages/cms/core/src/cms-client.ts | 5 ++ .../cms/keystatic/src/keystatic-client.ts | 28 +++++++++-- .../cms/keystatic/src/keystatic.config.ts | 20 ++++++++ packages/cms/wordpress/src/wp-client.ts | 48 ++++++++++++++++++- .../components/magic-link-auth-container.tsx | 2 +- .../components/sign-up-methods-container.tsx | 2 +- .../account-invitations-webhook.service.ts | 5 +- .../supabase/src/auth-callback.service.ts | 2 +- packages/ui/src/makerkit/global-loader.tsx | 2 +- 16 files changed, 110 insertions(+), 11 deletions(-) diff --git a/apps/web/content/documentation/authentication/authentication.mdoc b/apps/web/content/documentation/authentication/authentication.mdoc index 59776220b..f83208904 100644 --- a/apps/web/content/documentation/authentication/authentication.mdoc +++ b/apps/web/content/documentation/authentication/authentication.mdoc @@ -3,6 +3,7 @@ title: "Authentication" description: "Learn how to set up authentication in your MakerKit application." publishedAt: 2024-04-11 order: 1 +status: "published" --- MakerKit uses Supabase to manage authentication within your application. diff --git a/apps/web/content/documentation/authentication/configuration.mdoc b/apps/web/content/documentation/authentication/configuration.mdoc index 52222bf44..08250384c 100644 --- a/apps/web/content/documentation/authentication/configuration.mdoc +++ b/apps/web/content/documentation/authentication/configuration.mdoc @@ -4,6 +4,7 @@ description: "Learn how authentication works in MakerKit and how to configure it publishedAt: 2024-04-11 order: 0 parent: "authentication/authentication" +status: "published" --- The way you want your users to authenticate can be driven via configuration. diff --git a/apps/web/content/documentation/getting-started/getting-started.mdoc b/apps/web/content/documentation/getting-started/getting-started.mdoc index 86510f385..8f828a01e 100644 --- a/apps/web/content/documentation/getting-started/getting-started.mdoc +++ b/apps/web/content/documentation/getting-started/getting-started.mdoc @@ -3,6 +3,7 @@ title: "Getting started with Makerkit" description: "Makerkit is a SaaS Starter Kit that helps you build a SaaS. Learn how to get started with Makerkit." publishedAt: 2024-04-11 order: 0 +status: "published" --- Makerkit is a SaaS Starter Kit that helps you build a SaaS. It provides you with a set of tools and best practices to help you build a SaaS quickly and efficiently. diff --git a/apps/web/content/documentation/getting-started/installing-dependencies.mdoc b/apps/web/content/documentation/getting-started/installing-dependencies.mdoc index bf17f8e89..1af89bf83 100644 --- a/apps/web/content/documentation/getting-started/installing-dependencies.mdoc +++ b/apps/web/content/documentation/getting-started/installing-dependencies.mdoc @@ -4,6 +4,7 @@ description: "Learn how to install dependencies for your project." publishedAt: 2024-04-11 parent: "getting-started/getting-started" order: 0 +status: "published" --- To install dependencies in your project, please install `pnpm` by running the following command: diff --git a/apps/web/content/posts/brainstorming-ideas.mdoc b/apps/web/content/posts/brainstorming-ideas.mdoc index f4b29bf9d..61200d1f4 100644 --- a/apps/web/content/posts/brainstorming-ideas.mdoc +++ b/apps/web/content/posts/brainstorming-ideas.mdoc @@ -5,6 +5,7 @@ categories: ['blog'] tags: [] image: "/images/posts/brainstorming.webp" publishedAt: 2024-04-11 +status: "published" --- When it comes to choosing a SaaS starter kit for your business, it's essential to look for certain features that will ensure the smooth functioning and growth of your operations. Here are five must-have features that every SaaS starter kit should include: diff --git a/apps/web/content/posts/must-have-features.mdoc b/apps/web/content/posts/must-have-features.mdoc index 32f08272d..0636c05fd 100644 --- a/apps/web/content/posts/must-have-features.mdoc +++ b/apps/web/content/posts/must-have-features.mdoc @@ -5,6 +5,7 @@ categories: [] tags: [] image: "/images/posts/indie-hacker.webp" publishedAt: 2024-04-12 +status: "published" --- # Brainstorming Ideas for Your Next Micro SaaS diff --git a/apps/web/content/posts/saas-starter-guide.mdoc b/apps/web/content/posts/saas-starter-guide.mdoc index d4bb54dc0..dba5f3c81 100644 --- a/apps/web/content/posts/saas-starter-guide.mdoc +++ b/apps/web/content/posts/saas-starter-guide.mdoc @@ -5,6 +5,7 @@ categories: ['blog'] tags: [] image: "/images/posts/saas-starter-blog-post.webp" publishedAt: 2024-04-10 +status: "published" --- In the dynamic world of entrepreneurship, the Software as a Service (SaaS) model has emerged as a beacon of opportunity for indie hackers – individuals or small teams with big dreams of creating impactful software solutions. With the right tools and strategies, launching a successful SaaS startup is within reach for anyone with passion, dedication, and a clear vision. To empower aspiring entrepreneurs on this journey, we've curated a comprehensive starter kit tailored specifically for SaaS and indie hackers. diff --git a/packages/cms/core/src/cms-client.ts b/packages/cms/core/src/cms-client.ts index d53379760..de7fc8f09 100644 --- a/packages/cms/core/src/cms-client.ts +++ b/packages/cms/core/src/cms-client.ts @@ -8,6 +8,7 @@ export namespace Cms { content: unknown; publishedAt: string; image: string | undefined; + status: ContentItemStatus; slug: string; categories: Category[]; tags: Tag[]; @@ -16,6 +17,8 @@ export namespace Cms { parentId: string | undefined; } + export type ContentItemStatus = 'draft' | 'published' | 'review' | 'pending'; + export interface Category { id: string; name: string; @@ -38,6 +41,7 @@ export namespace Cms { language?: string | undefined; sortDirection?: 'asc' | 'desc'; sortBy?: 'publishedAt' | 'order' | 'title'; + status?: ContentItemStatus; } export interface GetCategoriesOptions { @@ -74,6 +78,7 @@ export abstract class CmsClient { abstract getContentItemBySlug(params: { slug: string; collection: string; + status?: Cms.ContentItemStatus; }): Promise; /** diff --git a/packages/cms/keystatic/src/keystatic-client.ts b/packages/cms/keystatic/src/keystatic-client.ts index 719e28160..bdd4445b5 100644 --- a/packages/cms/keystatic/src/keystatic-client.ts +++ b/packages/cms/keystatic/src/keystatic-client.ts @@ -1,5 +1,3 @@ -import Markdoc from '@markdoc/markdoc'; - import { Cms, CmsClient } from '@kit/cms'; import { createKeystaticReader } from './create-reader'; @@ -27,6 +25,12 @@ class KeystaticClient implements CmsClient { const filtered = docs .filter((item) => { + const status = options?.status ?? 'published'; + + if (item.entry.status !== status) { + return false; + } + const categoryMatch = options?.categories?.length ? options.categories.find((category) => item.entry.categories.includes(category), @@ -89,7 +93,11 @@ class KeystaticClient implements CmsClient { }; } - async getContentItemBySlug(params: { slug: string; collection: string }) { + async getContentItemBySlug(params: { + slug: string; + collection: string; + status?: Cms.ContentItemStatus; + }) { const reader = await createKeystaticReader(); const collection = @@ -100,11 +108,18 @@ class KeystaticClient implements CmsClient { } const doc = await reader.collections[collection].read(params.slug); + const status = params.status ?? 'published'; + // verify that the document exists if (!doc) { return Promise.resolve(undefined); } + // check the document matches the status provided in the params + if (doc.status !== status) { + return Promise.resolve(undefined); + } + const allPosts = await reader.collections[collection].all(); const children = allPosts.filter( @@ -136,13 +151,15 @@ class KeystaticClient implements CmsClient { slug: string; }, >(item: Type, children: Type[] = []): Promise { + const { transform, renderers } = await import('@markdoc/markdoc'); + const publishedAt = item.entry.publishedAt ? new Date(item.entry.publishedAt) : new Date(); const markdoc = await item.entry.content(); - const content = Markdoc.transform(markdoc.node); - const html = Markdoc.renderers.html(content); + const content = transform(markdoc.node); + const html = renderers.html(content); return { id: item.slug, @@ -153,6 +170,7 @@ class KeystaticClient implements CmsClient { publishedAt: publishedAt.toISOString(), content: html, image: item.entry.image ?? undefined, + status: item.entry.status, categories: item.entry.categories.map((item) => { return { diff --git a/packages/cms/keystatic/src/keystatic.config.ts b/packages/cms/keystatic/src/keystatic.config.ts index b94aea03a..9716262df 100644 --- a/packages/cms/keystatic/src/keystatic.config.ts +++ b/packages/cms/keystatic/src/keystatic.config.ts @@ -124,6 +124,16 @@ function getKeystaticCollections(path: string) { language: fields.text({ label: 'Language' }), order: fields.number({ label: 'Order' }), content: getContentField(), + status: fields.select({ + defaultValue: 'draft', + label: 'Status', + options: [ + { label: 'Draft', value: 'draft' }, + { label: 'Published', value: 'published' }, + { label: 'Review', value: 'review' }, + { label: 'Pending', value: 'pending' }, + ], + }), }, }), documentation: collection({ @@ -149,6 +159,16 @@ function getKeystaticCollections(path: string) { }), categories: fields.array(fields.text({ label: 'Category' })), tags: fields.array(fields.text({ label: 'Tag' })), + status: fields.select({ + defaultValue: 'draft', + label: 'Status', + options: [ + { label: 'Draft', value: 'draft' }, + { label: 'Published', value: 'published' }, + { label: 'Review', value: 'review' }, + { label: 'Pending', value: 'pending' }, + ], + }), }, }), }; diff --git a/packages/cms/wordpress/src/wp-client.ts b/packages/cms/wordpress/src/wp-client.ts index 1736f0a09..3c26f8017 100644 --- a/packages/cms/wordpress/src/wp-client.ts +++ b/packages/cms/wordpress/src/wp-client.ts @@ -1,4 +1,5 @@ import type { + WP_Post_Status_Name, WP_REST_API_Category, WP_REST_API_Post, WP_REST_API_Tag, @@ -104,7 +105,9 @@ class WordpressClient implements CmsClient { const total = totalHeader ? Number(totalHeader) : 0; const results = (await response.json()) as WP_REST_API_Post[]; - const posts = await Promise.all( + const status = options.status ?? 'published'; + + const postsResults = await Promise.allSettled( results.map(async (item: WP_REST_API_Post) => { let parentId: string | undefined; @@ -116,6 +119,12 @@ class WordpressClient implements CmsClient { parentId = item.parent.toString(); } + const mappedStatus = mapToStatus(item.status as WP_Post_Status_Name); + + if (mappedStatus !== status) { + throw new Error('Status does not match'); + } + const categories = await this.getCategoriesByIds(item.categories ?? []); const tags = await this.getTagsByIds(item.tags ?? []); const image = item.featured_media ? this.getFeaturedMedia(item) : ''; @@ -130,6 +139,7 @@ class WordpressClient implements CmsClient { url: item.link, slug: item.slug, publishedAt: item.date, + status: mappedStatus ?? 'draft', categories: categories, tags: tags, parentId, @@ -139,6 +149,10 @@ class WordpressClient implements CmsClient { }), ); + const posts = postsResults + .filter((item) => item.status === 'fulfilled') + .map((item) => item.value); + return { total, items: posts, @@ -148,9 +162,11 @@ class WordpressClient implements CmsClient { async getContentItemBySlug({ slug, collection, + status, }: { slug: string; collection: string; + status?: Cms.ContentItemStatus; }) { const searchParams = new URLSearchParams({ _embed: 'true', @@ -174,6 +190,15 @@ class WordpressClient implements CmsClient { return; } + const mappedStatus = status + ? mapToStatus(item.status as WP_Post_Status_Name) + : undefined; + const statusMatch = status ? mappedStatus === status : true; + + if (!statusMatch) { + return; + } + const categories = await this.getCategoriesByIds(item.categories ?? []); const tags = await this.getTagsByIds(item.tags ?? []); const image = item.featured_media ? this.getFeaturedMedia(item) : ''; @@ -189,6 +214,7 @@ class WordpressClient implements CmsClient { content: item.content.rendered, slug: item.slug, publishedAt: item.date, + status: mappedStatus ?? 'draft', categories, tags, parentId: item.parent?.toString(), @@ -370,3 +396,23 @@ function mapSortByParam(sortBy: string) { return; } } + +function mapToStatus(status: WP_Post_Status_Name): Cms.ContentItemStatus { + const Draft = 'draft' as WP_Post_Status_Name; + const Publish = 'publish' as WP_Post_Status_Name; + const Pending = 'pending' as WP_Post_Status_Name; + + switch (status) { + case Draft: + return 'draft'; + + case Publish: + return 'published'; + + case Pending: + return 'pending'; + + default: + return 'draft'; + } +} diff --git a/packages/features/auth/src/components/magic-link-auth-container.tsx b/packages/features/auth/src/components/magic-link-auth-container.tsx index 33f5e7260..637278461 100644 --- a/packages/features/auth/src/components/magic-link-auth-container.tsx +++ b/packages/features/auth/src/components/magic-link-auth-container.tsx @@ -48,7 +48,7 @@ export function MagicLinkAuthContainer({ }), ), defaultValues: { - email: defaultValues?.email ?? '' + email: defaultValues?.email ?? '', }, }); diff --git a/packages/features/auth/src/components/sign-up-methods-container.tsx b/packages/features/auth/src/components/sign-up-methods-container.tsx index 17802db84..c317f320d 100644 --- a/packages/features/auth/src/components/sign-up-methods-container.tsx +++ b/packages/features/auth/src/components/sign-up-methods-container.tsx @@ -28,7 +28,7 @@ export function SignUpMethodsContainer(props: { }) { const redirectUrl = getCallbackUrl(props); const defaultValues = getDefaultValues(); - + return ( <> diff --git a/packages/features/team-accounts/src/server/services/webhooks/account-invitations-webhook.service.ts b/packages/features/team-accounts/src/server/services/webhooks/account-invitations-webhook.service.ts index c44fcf459..8580e8a7b 100644 --- a/packages/features/team-accounts/src/server/services/webhooks/account-invitations-webhook.service.ts +++ b/packages/features/team-accounts/src/server/services/webhooks/account-invitations-webhook.service.ts @@ -102,7 +102,10 @@ class AccountInvitationsWebhookService { const { getMailer } = await import('@kit/mailers'); const mailer = await getMailer(); - const link = this.getInvitationLink(invitation.invite_token, invitation.email); + const link = this.getInvitationLink( + invitation.invite_token, + invitation.email, + ); const { html, subject } = await renderInviteEmail({ link, diff --git a/packages/supabase/src/auth-callback.service.ts b/packages/supabase/src/auth-callback.service.ts index b8fa7d570..490542029 100644 --- a/packages/supabase/src/auth-callback.service.ts +++ b/packages/supabase/src/auth-callback.service.ts @@ -148,7 +148,7 @@ class AuthCallbackService { const urlParams = new URLSearchParams({ invite_token: inviteToken, - email: emailParam ?? '' + email: emailParam ?? '', }); nextUrl = `${params.joinTeamPath}?${urlParams.toString()}`; diff --git a/packages/ui/src/makerkit/global-loader.tsx b/packages/ui/src/makerkit/global-loader.tsx index dbccbbfa3..7878ea6d9 100644 --- a/packages/ui/src/makerkit/global-loader.tsx +++ b/packages/ui/src/makerkit/global-loader.tsx @@ -22,7 +22,7 @@ export function GlobalLoader({