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({