Skip to content

Commit e8a6864

Browse files
Merge pull request #78 from strapi/fix/preview-cookie
[ ISSUE # 76 ] Add banner to show user that we are in draft mode with button
2 parents e354110 + bcecabb commit e8a6864

4 files changed

Lines changed: 154 additions & 0 deletions

File tree

next/app/[locale]/layout.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { draftMode } from 'next/headers';
12
import { Metadata } from 'next';
23
import { ViewTransitions } from 'next-view-transitions';
34
import { Inter } from 'next/font/google';
45
import React from 'react';
56

7+
import { DraftModeBanner } from '@/components/draft-mode-banner';
68
import { Footer } from '@/components/footer';
79
import { Navbar } from '@/components/navbar';
810
import { CartProvider } from '@/context/cart-context';
@@ -45,6 +47,8 @@ export default async function LocaleLayout(props: {
4547

4648
const { children } = props;
4749

50+
const { isEnabled: isDraftMode } = await draftMode();
51+
4852
const pageData = await fetchContentType(
4953
'global',
5054
{ filters: { locale } },
@@ -62,6 +66,7 @@ export default async function LocaleLayout(props: {
6266
<Navbar data={pageData.navbar} locale={locale} />
6367
{children}
6468
<Footer data={pageData.footer} locale={locale} />
69+
{isDraftMode && <DraftModeBanner />}
6570
</div>
6671
</CartProvider>
6772
</ViewTransitions>

next/app/api/exit-preview/route.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { draftMode } from 'next/headers';
2+
import { NextResponse } from 'next/server';
3+
4+
export async function GET() {
5+
const draft = await draftMode();
6+
draft.disable();
7+
8+
return NextResponse.json({ success: true });
9+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use client';
2+
3+
import { usePathname, useRouter } from 'next/navigation';
4+
import { useState } from 'react';
5+
6+
export function DraftModeBanner() {
7+
const router = useRouter();
8+
const pathname = usePathname();
9+
const [isExiting, setIsExiting] = useState(false);
10+
11+
const handleExitDraft = async () => {
12+
setIsExiting(true);
13+
try {
14+
await fetch('/api/exit-preview');
15+
router.refresh();
16+
} catch (error) {
17+
console.error('Failed to exit draft mode:', error);
18+
setIsExiting(false);
19+
}
20+
};
21+
22+
return (
23+
<div className="fixed bottom-4 right-4 z-50 bg-secondary text-black px-6 py-3 rounded-lg shadow-lg flex items-center gap-4">
24+
<div className="flex items-center gap-2">
25+
<span className="inline-block w-4 h-4 bg-black rounded-full animate-pulse" />
26+
<span className="font-semibold">Draft Mode</span>
27+
</div>
28+
<button
29+
onClick={handleExitDraft}
30+
disabled={isExiting}
31+
className="bg-black text-white px-4 py-1 rounded text-sm font-medium hover:bg-gray-800 disabled:opacity-50 transition-colors"
32+
>
33+
{isExiting ? 'Exiting...' : 'Exit Draft'}
34+
</button>
35+
</div>
36+
);
37+
}

strapi/types/generated/contentTypes.d.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,43 @@ export interface AdminApiTokenPermission extends Struct.CollectionTypeSchema {
107107
};
108108
}
109109

110+
export interface AdminAuditLog extends Struct.CollectionTypeSchema {
111+
collectionName: 'strapi_audit_logs';
112+
info: {
113+
displayName: 'Audit Log';
114+
pluralName: 'audit-logs';
115+
singularName: 'audit-log';
116+
};
117+
options: {
118+
draftAndPublish: false;
119+
timestamps: false;
120+
};
121+
pluginOptions: {
122+
'content-manager': {
123+
visible: false;
124+
};
125+
'content-type-builder': {
126+
visible: false;
127+
};
128+
};
129+
attributes: {
130+
action: Schema.Attribute.String & Schema.Attribute.Required;
131+
createdAt: Schema.Attribute.DateTime;
132+
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
133+
Schema.Attribute.Private;
134+
date: Schema.Attribute.DateTime & Schema.Attribute.Required;
135+
locale: Schema.Attribute.String & Schema.Attribute.Private;
136+
localizations: Schema.Attribute.Relation<'oneToMany', 'admin::audit-log'> &
137+
Schema.Attribute.Private;
138+
payload: Schema.Attribute.JSON;
139+
publishedAt: Schema.Attribute.DateTime;
140+
updatedAt: Schema.Attribute.DateTime;
141+
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
142+
Schema.Attribute.Private;
143+
user: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
144+
};
145+
}
146+
110147
export interface AdminPermission extends Struct.CollectionTypeSchema {
111148
collectionName: 'admin_permissions';
112149
info: {
@@ -441,6 +478,11 @@ export interface ApiArticleArticle extends Struct.CollectionTypeSchema {
441478
localized: true;
442479
};
443480
}>;
481+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
482+
strapi_stage: Schema.Attribute.Relation<
483+
'oneToOne',
484+
'plugin::review-workflows.workflow-stage'
485+
>;
444486
title: Schema.Attribute.String &
445487
Schema.Attribute.SetPluginOptions<{
446488
i18n: {
@@ -512,6 +554,11 @@ export interface ApiBlogPageBlogPage extends Struct.SingleTypeSchema {
512554
localized: true;
513555
};
514556
}>;
557+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
558+
strapi_stage: Schema.Attribute.Relation<
559+
'oneToOne',
560+
'plugin::review-workflows.workflow-stage'
561+
>;
515562
sub_heading: Schema.Attribute.String &
516563
Schema.Attribute.SetPluginOptions<{
517564
i18n: {
@@ -549,6 +596,11 @@ export interface ApiCategoryCategory extends Struct.CollectionTypeSchema {
549596
name: Schema.Attribute.String;
550597
product: Schema.Attribute.Relation<'manyToOne', 'api::product.product'>;
551598
publishedAt: Schema.Attribute.DateTime;
599+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
600+
strapi_stage: Schema.Attribute.Relation<
601+
'oneToOne',
602+
'plugin::review-workflows.workflow-stage'
603+
>;
552604
updatedAt: Schema.Attribute.DateTime;
553605
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
554606
Schema.Attribute.Private;
@@ -590,6 +642,11 @@ export interface ApiFaqFaq extends Struct.CollectionTypeSchema {
590642
localized: true;
591643
};
592644
}>;
645+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
646+
strapi_stage: Schema.Attribute.Relation<
647+
'oneToOne',
648+
'plugin::review-workflows.workflow-stage'
649+
>;
593650
updatedAt: Schema.Attribute.DateTime;
594651
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
595652
Schema.Attribute.Private;
@@ -637,6 +694,11 @@ export interface ApiGlobalGlobal extends Struct.SingleTypeSchema {
637694
localized: true;
638695
};
639696
}>;
697+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
698+
strapi_stage: Schema.Attribute.Relation<
699+
'oneToOne',
700+
'plugin::review-workflows.workflow-stage'
701+
>;
640702
updatedAt: Schema.Attribute.DateTime;
641703
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
642704
Schema.Attribute.Private;
@@ -664,6 +726,11 @@ export interface ApiLogoLogo extends Struct.CollectionTypeSchema {
664726
localizations: Schema.Attribute.Relation<'oneToMany', 'api::logo.logo'> &
665727
Schema.Attribute.Private;
666728
publishedAt: Schema.Attribute.DateTime;
729+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
730+
strapi_stage: Schema.Attribute.Relation<
731+
'oneToOne',
732+
'plugin::review-workflows.workflow-stage'
733+
>;
667734
updatedAt: Schema.Attribute.DateTime;
668735
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
669736
Schema.Attribute.Private;
@@ -726,6 +793,11 @@ export interface ApiPagePage extends Struct.CollectionTypeSchema {
726793
};
727794
}> &
728795
Schema.Attribute.DefaultTo<'slug'>;
796+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
797+
strapi_stage: Schema.Attribute.Relation<
798+
'oneToOne',
799+
'plugin::review-workflows.workflow-stage'
800+
>;
729801
updatedAt: Schema.Attribute.DateTime;
730802
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
731803
Schema.Attribute.Private;
@@ -758,6 +830,11 @@ export interface ApiPlanPlan extends Struct.CollectionTypeSchema {
758830
price: Schema.Attribute.Integer;
759831
product: Schema.Attribute.Relation<'manyToOne', 'api::product.product'>;
760832
publishedAt: Schema.Attribute.DateTime;
833+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
834+
strapi_stage: Schema.Attribute.Relation<
835+
'oneToOne',
836+
'plugin::review-workflows.workflow-stage'
837+
>;
761838
sub_text: Schema.Attribute.String;
762839
updatedAt: Schema.Attribute.DateTime;
763840
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
@@ -823,6 +900,11 @@ export interface ApiProductPageProductPage extends Struct.SingleTypeSchema {
823900
localized: true;
824901
};
825902
}>;
903+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
904+
strapi_stage: Schema.Attribute.Relation<
905+
'oneToOne',
906+
'plugin::review-workflows.workflow-stage'
907+
>;
826908
sub_heading: Schema.Attribute.String &
827909
Schema.Attribute.SetPluginOptions<{
828910
i18n: {
@@ -872,6 +954,11 @@ export interface ApiProductProduct extends Struct.CollectionTypeSchema {
872954
price: Schema.Attribute.Integer;
873955
publishedAt: Schema.Attribute.DateTime;
874956
slug: Schema.Attribute.UID<'name'>;
957+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
958+
strapi_stage: Schema.Attribute.Relation<
959+
'oneToOne',
960+
'plugin::review-workflows.workflow-stage'
961+
>;
875962
updatedAt: Schema.Attribute.DateTime;
876963
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
877964
Schema.Attribute.Private;
@@ -901,6 +988,11 @@ export interface ApiRedirectionRedirection extends Struct.CollectionTypeSchema {
901988
Schema.Attribute.Private;
902989
publishedAt: Schema.Attribute.DateTime;
903990
source: Schema.Attribute.String;
991+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
992+
strapi_stage: Schema.Attribute.Relation<
993+
'oneToOne',
994+
'plugin::review-workflows.workflow-stage'
995+
>;
904996
updatedAt: Schema.Attribute.DateTime;
905997
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
906998
Schema.Attribute.Private;
@@ -933,6 +1025,11 @@ export interface ApiTestimonialTestimonial extends Struct.CollectionTypeSchema {
9331025
'api::testimonial.testimonial'
9341026
>;
9351027
publishedAt: Schema.Attribute.DateTime;
1028+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
1029+
strapi_stage: Schema.Attribute.Relation<
1030+
'oneToOne',
1031+
'plugin::review-workflows.workflow-stage'
1032+
>;
9361033
text: Schema.Attribute.String &
9371034
Schema.Attribute.SetPluginOptions<{
9381035
i18n: {
@@ -1438,6 +1535,11 @@ export interface PluginUsersPermissionsUser
14381535
'manyToOne',
14391536
'plugin::users-permissions.role'
14401537
>;
1538+
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
1539+
strapi_stage: Schema.Attribute.Relation<
1540+
'oneToOne',
1541+
'plugin::review-workflows.workflow-stage'
1542+
>;
14411543
updatedAt: Schema.Attribute.DateTime;
14421544
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
14431545
Schema.Attribute.Private;
@@ -1455,6 +1557,7 @@ declare module '@strapi/strapi' {
14551557
export interface ContentTypeSchemas {
14561558
'admin::api-token': AdminApiToken;
14571559
'admin::api-token-permission': AdminApiTokenPermission;
1560+
'admin::audit-log': AdminAuditLog;
14581561
'admin::permission': AdminPermission;
14591562
'admin::role': AdminRole;
14601563
'admin::transfer-token': AdminTransferToken;

0 commit comments

Comments
 (0)