@nextsparkjs/theme-blog 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/about.md +93 -0
- package/api/authors/[username]/route.ts +150 -0
- package/api/authors/route.ts +63 -0
- package/api/posts/public/route.ts +151 -0
- package/components/ExportPostsButton.tsx +102 -0
- package/components/ImportPostsDialog.tsx +284 -0
- package/components/PostsToolbar.tsx +24 -0
- package/components/editor/FeaturedImageUpload.tsx +185 -0
- package/components/editor/WysiwygEditor.tsx +340 -0
- package/components/index.ts +4 -0
- package/components/public/AuthorBio.tsx +105 -0
- package/components/public/AuthorCard.tsx +130 -0
- package/components/public/BlogFooter.tsx +185 -0
- package/components/public/BlogNavbar.tsx +201 -0
- package/components/public/PostCard.tsx +306 -0
- package/components/public/ReadingProgress.tsx +70 -0
- package/components/public/RelatedPosts.tsx +78 -0
- package/config/app.config.ts +200 -0
- package/config/billing.config.ts +146 -0
- package/config/dashboard.config.ts +333 -0
- package/config/dev.config.ts +48 -0
- package/config/features.config.ts +196 -0
- package/config/flows.config.ts +333 -0
- package/config/permissions.config.ts +101 -0
- package/config/theme.config.ts +128 -0
- package/entities/categories/categories.config.ts +60 -0
- package/entities/categories/categories.fields.ts +115 -0
- package/entities/categories/categories.service.ts +333 -0
- package/entities/categories/categories.types.ts +58 -0
- package/entities/categories/messages/en.json +33 -0
- package/entities/categories/messages/es.json +33 -0
- package/entities/posts/messages/en.json +100 -0
- package/entities/posts/messages/es.json +100 -0
- package/entities/posts/migrations/001_posts_table.sql +110 -0
- package/entities/posts/migrations/002_add_featured.sql +19 -0
- package/entities/posts/migrations/003_post_categories_pivot.sql +47 -0
- package/entities/posts/posts.config.ts +61 -0
- package/entities/posts/posts.fields.ts +234 -0
- package/entities/posts/posts.service.ts +464 -0
- package/entities/posts/posts.types.ts +80 -0
- package/lib/selectors.ts +179 -0
- package/messages/en.json +113 -0
- package/messages/es.json +113 -0
- package/migrations/002_author_profile_fields.sql +37 -0
- package/migrations/003_categories_table.sql +90 -0
- package/migrations/999_sample_data.sql +412 -0
- package/migrations/999_theme_sample_data.sql +1070 -0
- package/package.json +18 -0
- package/permissions-matrix.md +63 -0
- package/styles/article.css +333 -0
- package/styles/components.css +204 -0
- package/styles/globals.css +327 -0
- package/styles/theme.css +167 -0
- package/templates/(public)/author/[username]/page.tsx +247 -0
- package/templates/(public)/authors/page.tsx +161 -0
- package/templates/(public)/layout.tsx +44 -0
- package/templates/(public)/page.tsx +276 -0
- package/templates/(public)/posts/[slug]/page.tsx +342 -0
- package/templates/dashboard/(main)/page.tsx +385 -0
- package/templates/dashboard/(main)/posts/[id]/edit/page.tsx +529 -0
- package/templates/dashboard/(main)/posts/[id]/page.tsx +33 -0
- package/templates/dashboard/(main)/posts/create/page.tsx +353 -0
- package/templates/dashboard/(main)/posts/page.tsx +833 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reading Progress Component
|
|
5
|
+
*
|
|
6
|
+
* Fixed progress bar at the top of the page that tracks
|
|
7
|
+
* scroll progress through an article.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useState, useEffect } from 'react'
|
|
11
|
+
import { cn } from '@nextsparkjs/core/lib/utils'
|
|
12
|
+
|
|
13
|
+
interface ReadingProgressProps {
|
|
14
|
+
className?: string
|
|
15
|
+
height?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function ReadingProgress({
|
|
19
|
+
className,
|
|
20
|
+
height = 3
|
|
21
|
+
}: ReadingProgressProps) {
|
|
22
|
+
const [progress, setProgress] = useState(0)
|
|
23
|
+
const [isVisible, setIsVisible] = useState(false)
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const calculateProgress = () => {
|
|
27
|
+
const scrollTop = window.scrollY
|
|
28
|
+
const docHeight = document.documentElement.scrollHeight - window.innerHeight
|
|
29
|
+
const scrollPercent = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0
|
|
30
|
+
|
|
31
|
+
setProgress(Math.min(100, Math.max(0, scrollPercent)))
|
|
32
|
+
setIsVisible(scrollTop > 100)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Calculate initial progress
|
|
36
|
+
calculateProgress()
|
|
37
|
+
|
|
38
|
+
// Add scroll listener with passive for better performance
|
|
39
|
+
window.addEventListener('scroll', calculateProgress, { passive: true })
|
|
40
|
+
|
|
41
|
+
return () => {
|
|
42
|
+
window.removeEventListener('scroll', calculateProgress)
|
|
43
|
+
}
|
|
44
|
+
}, [])
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
data-cy="reading-progress"
|
|
49
|
+
className={cn(
|
|
50
|
+
'fixed top-0 left-0 right-0 z-[100] transition-opacity duration-300',
|
|
51
|
+
isVisible ? 'opacity-100' : 'opacity-0',
|
|
52
|
+
className
|
|
53
|
+
)}
|
|
54
|
+
style={{ height: `${height}px` }}
|
|
55
|
+
role="progressbar"
|
|
56
|
+
aria-valuenow={Math.round(progress)}
|
|
57
|
+
aria-valuemin={0}
|
|
58
|
+
aria-valuemax={100}
|
|
59
|
+
aria-label="Reading progress"
|
|
60
|
+
>
|
|
61
|
+
<div
|
|
62
|
+
data-cy="reading-progress-bar"
|
|
63
|
+
className="h-full bg-primary transition-[width] duration-100 ease-out"
|
|
64
|
+
style={{ width: `${progress}%` }}
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default ReadingProgress
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Related Posts Component
|
|
5
|
+
*
|
|
6
|
+
* Displays a horizontal list of related posts from the same category.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import Link from 'next/link'
|
|
10
|
+
import Image from 'next/image'
|
|
11
|
+
import { Clock } from 'lucide-react'
|
|
12
|
+
|
|
13
|
+
interface RelatedPost {
|
|
14
|
+
id: string
|
|
15
|
+
title: string
|
|
16
|
+
slug: string
|
|
17
|
+
featuredImage?: string | null
|
|
18
|
+
readingTime?: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface RelatedPostsProps {
|
|
22
|
+
posts: RelatedPost[]
|
|
23
|
+
title?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function RelatedPosts({
|
|
27
|
+
posts,
|
|
28
|
+
title = 'Related Articles'
|
|
29
|
+
}: RelatedPostsProps) {
|
|
30
|
+
if (posts.length === 0) return null
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<section data-cy="related-posts" className="py-8">
|
|
34
|
+
<h2 data-cy="related-posts-title" className="font-serif text-xl font-bold mb-6">{title}</h2>
|
|
35
|
+
|
|
36
|
+
<div data-cy="related-posts-grid" className="grid gap-4 md:grid-cols-3">
|
|
37
|
+
{posts.slice(0, 3).map((post) => (
|
|
38
|
+
<Link
|
|
39
|
+
key={post.id}
|
|
40
|
+
href={`/posts/${post.slug || post.id}`}
|
|
41
|
+
data-cy={`related-post-${post.id}`}
|
|
42
|
+
className="group flex flex-col overflow-hidden rounded-lg border border-border bg-card hover:shadow-md transition-all duration-200"
|
|
43
|
+
>
|
|
44
|
+
{/* Image */}
|
|
45
|
+
<div data-cy={`related-post-image-${post.id}`} className="aspect-[16/9] relative overflow-hidden">
|
|
46
|
+
{post.featuredImage ? (
|
|
47
|
+
<Image
|
|
48
|
+
src={post.featuredImage}
|
|
49
|
+
alt={post.title}
|
|
50
|
+
fill
|
|
51
|
+
className="object-cover transition-transform duration-300 group-hover:scale-105"
|
|
52
|
+
sizes="(max-width: 768px) 100vw, 33vw"
|
|
53
|
+
/>
|
|
54
|
+
) : (
|
|
55
|
+
<div className="w-full h-full bg-gradient-to-br from-primary/20 to-primary/5" />
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{/* Content */}
|
|
60
|
+
<div data-cy={`related-post-content-${post.id}`} className="p-4">
|
|
61
|
+
<h3 data-cy={`related-post-title-${post.id}`} className="font-medium text-sm line-clamp-2 group-hover:text-primary transition-colors">
|
|
62
|
+
{post.title}
|
|
63
|
+
</h3>
|
|
64
|
+
{post.readingTime && (
|
|
65
|
+
<div data-cy={`related-post-reading-time-${post.id}`} className="flex items-center gap-1 mt-2 text-xs text-muted-foreground">
|
|
66
|
+
<Clock className="w-3 h-3" />
|
|
67
|
+
<span>{post.readingTime} min read</span>
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
</Link>
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
</section>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default RelatedPosts
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blog Theme - Application Configuration
|
|
3
|
+
*
|
|
4
|
+
* Multi-author blog platform with single-user mode.
|
|
5
|
+
* Each author has their own isolated team but content is aggregated publicly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const APP_CONFIG_OVERRIDES = {
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// APPLICATION METADATA
|
|
11
|
+
// =============================================================================
|
|
12
|
+
app: {
|
|
13
|
+
name: 'Blog Platform',
|
|
14
|
+
version: '2.0.0',
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// TEAMS CONFIGURATION - SINGLE USER MODE
|
|
19
|
+
// =============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Single-user mode:
|
|
22
|
+
* - No team switching
|
|
23
|
+
* - No invitations
|
|
24
|
+
* - No team creation
|
|
25
|
+
* - Each author has their personal team
|
|
26
|
+
* - Content aggregates publicly across all authors
|
|
27
|
+
*/
|
|
28
|
+
teams: {
|
|
29
|
+
mode: 'single-user' as const,
|
|
30
|
+
options: {
|
|
31
|
+
maxTeamsPerUser: 1,
|
|
32
|
+
maxMembersPerTeam: 1,
|
|
33
|
+
allowLeaveAllTeams: false,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// TOPBAR CONFIGURATION
|
|
39
|
+
// =============================================================================
|
|
40
|
+
topbar: {
|
|
41
|
+
features: {
|
|
42
|
+
quickCreate: true,
|
|
43
|
+
search: false, // No search in MVP
|
|
44
|
+
notifications: false,
|
|
45
|
+
help: false,
|
|
46
|
+
theme: true,
|
|
47
|
+
sector7: false,
|
|
48
|
+
userMenu: true,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// SETTINGS PAGES CONFIGURATION
|
|
54
|
+
// =============================================================================
|
|
55
|
+
settings: {
|
|
56
|
+
pages: {
|
|
57
|
+
profile: { enabled: true, order: 1 },
|
|
58
|
+
billing: { enabled: true, order: 2 }, // Billing enabled for Pro/Enterprise plans
|
|
59
|
+
apiKeys: { enabled: false },
|
|
60
|
+
password: { enabled: true, order: 3 },
|
|
61
|
+
teams: { enabled: false }, // Hidden in single-user mode
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// INTERNATIONALIZATION
|
|
67
|
+
// =============================================================================
|
|
68
|
+
i18n: {
|
|
69
|
+
supportedLocales: ['en', 'es'],
|
|
70
|
+
defaultLocale: 'en' as const,
|
|
71
|
+
namespaces: [
|
|
72
|
+
'common',
|
|
73
|
+
'dashboard',
|
|
74
|
+
'settings',
|
|
75
|
+
'auth',
|
|
76
|
+
'public',
|
|
77
|
+
'validation',
|
|
78
|
+
// Theme specific
|
|
79
|
+
'posts',
|
|
80
|
+
'blog',
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// =============================================================================
|
|
85
|
+
// API CONFIGURATION
|
|
86
|
+
// =============================================================================
|
|
87
|
+
api: {
|
|
88
|
+
cors: {
|
|
89
|
+
allowedOrigins: {
|
|
90
|
+
development: [
|
|
91
|
+
'http://localhost:3000',
|
|
92
|
+
'http://localhost:5173',
|
|
93
|
+
],
|
|
94
|
+
production: [],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
// =============================================================================
|
|
100
|
+
// DOCUMENTATION
|
|
101
|
+
// =============================================================================
|
|
102
|
+
docs: {
|
|
103
|
+
enabled: true,
|
|
104
|
+
public: true,
|
|
105
|
+
searchEnabled: true,
|
|
106
|
+
breadcrumbs: true,
|
|
107
|
+
theme: {
|
|
108
|
+
enabled: true,
|
|
109
|
+
open: true,
|
|
110
|
+
label: "Blog Theme",
|
|
111
|
+
},
|
|
112
|
+
plugins: {
|
|
113
|
+
enabled: false,
|
|
114
|
+
open: false,
|
|
115
|
+
label: "Plugins",
|
|
116
|
+
},
|
|
117
|
+
core: {
|
|
118
|
+
enabled: true,
|
|
119
|
+
open: false,
|
|
120
|
+
label: "Core",
|
|
121
|
+
},
|
|
122
|
+
showPluginsDocsInProd: false,
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// SCHEDULED ACTIONS
|
|
127
|
+
// =============================================================================
|
|
128
|
+
scheduledActions: {
|
|
129
|
+
enabled: true,
|
|
130
|
+
retentionDays: 7,
|
|
131
|
+
batchSize: 10,
|
|
132
|
+
defaultTimeout: 30000,
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// =============================================================================
|
|
136
|
+
// MOBILE NAVIGATION
|
|
137
|
+
// =============================================================================
|
|
138
|
+
mobileNav: {
|
|
139
|
+
items: [
|
|
140
|
+
{
|
|
141
|
+
id: 'home',
|
|
142
|
+
labelKey: 'common.mobileNav.home',
|
|
143
|
+
href: '/dashboard',
|
|
144
|
+
icon: 'Home',
|
|
145
|
+
enabled: true,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: 'posts',
|
|
149
|
+
labelKey: 'posts.title',
|
|
150
|
+
href: '/dashboard/posts',
|
|
151
|
+
icon: 'FileText',
|
|
152
|
+
enabled: true,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'create',
|
|
156
|
+
labelKey: 'common.mobileNav.create',
|
|
157
|
+
icon: 'Plus',
|
|
158
|
+
isCentral: true,
|
|
159
|
+
action: 'quickCreate',
|
|
160
|
+
enabled: true,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'settings',
|
|
164
|
+
labelKey: 'common.mobileNav.settings',
|
|
165
|
+
href: '/dashboard/settings',
|
|
166
|
+
icon: 'Settings',
|
|
167
|
+
enabled: true,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'view-blog',
|
|
171
|
+
labelKey: 'blog.viewBlog',
|
|
172
|
+
href: '/',
|
|
173
|
+
icon: 'ExternalLink',
|
|
174
|
+
enabled: true,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
moreSheetItems: [
|
|
178
|
+
{
|
|
179
|
+
id: 'profile',
|
|
180
|
+
labelKey: 'common.navigation.profile',
|
|
181
|
+
href: '/dashboard/settings/profile',
|
|
182
|
+
icon: 'User',
|
|
183
|
+
enabled: true,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'export',
|
|
187
|
+
labelKey: 'blog.export',
|
|
188
|
+
href: '/dashboard/settings/export',
|
|
189
|
+
icon: 'Download',
|
|
190
|
+
enabled: true,
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
// =============================================================================
|
|
196
|
+
// DEV KEYRING - MOVED TO dev.config.ts
|
|
197
|
+
// =============================================================================
|
|
198
|
+
// DevKeyring configuration has been moved to config/dev.config.ts
|
|
199
|
+
// This separates development-only settings from production configuration.
|
|
200
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Billing Configuration - Blog Theme
|
|
3
|
+
*
|
|
4
|
+
* Defines plans, features, limits, and action mappings for the Blog theme.
|
|
5
|
+
* Customized for content publishing and blogging.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { BillingConfig } from '@nextsparkjs/core/lib/billing/config-types'
|
|
9
|
+
|
|
10
|
+
export const billingConfig: BillingConfig = {
|
|
11
|
+
provider: 'stripe',
|
|
12
|
+
currency: 'usd',
|
|
13
|
+
defaultPlan: 'free',
|
|
14
|
+
|
|
15
|
+
// ===========================================
|
|
16
|
+
// FEATURE DEFINITIONS (Blog-specific)
|
|
17
|
+
// ===========================================
|
|
18
|
+
features: {
|
|
19
|
+
basic_analytics: {
|
|
20
|
+
name: 'billing.features.basic_analytics',
|
|
21
|
+
description: 'billing.features.basic_analytics_description',
|
|
22
|
+
},
|
|
23
|
+
custom_domain: {
|
|
24
|
+
name: 'billing.features.custom_domain',
|
|
25
|
+
description: 'billing.features.custom_domain_description',
|
|
26
|
+
},
|
|
27
|
+
advanced_seo: {
|
|
28
|
+
name: 'billing.features.advanced_seo',
|
|
29
|
+
description: 'billing.features.advanced_seo_description',
|
|
30
|
+
},
|
|
31
|
+
priority_support: {
|
|
32
|
+
name: 'billing.features.priority_support',
|
|
33
|
+
description: 'billing.features.priority_support_description',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// ===========================================
|
|
38
|
+
// LIMIT DEFINITIONS (Blog-specific)
|
|
39
|
+
// ===========================================
|
|
40
|
+
limits: {
|
|
41
|
+
posts: {
|
|
42
|
+
name: 'billing.limits.posts',
|
|
43
|
+
unit: 'count',
|
|
44
|
+
resetPeriod: 'never',
|
|
45
|
+
},
|
|
46
|
+
monthly_views: {
|
|
47
|
+
name: 'billing.limits.monthly_views',
|
|
48
|
+
unit: 'count',
|
|
49
|
+
resetPeriod: 'monthly',
|
|
50
|
+
},
|
|
51
|
+
storage_gb: {
|
|
52
|
+
name: 'billing.limits.storage',
|
|
53
|
+
unit: 'bytes',
|
|
54
|
+
resetPeriod: 'never',
|
|
55
|
+
},
|
|
56
|
+
authors: {
|
|
57
|
+
name: 'billing.limits.authors',
|
|
58
|
+
unit: 'count',
|
|
59
|
+
resetPeriod: 'never',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// ===========================================
|
|
64
|
+
// PLAN DEFINITIONS (Blog-optimized)
|
|
65
|
+
// ===========================================
|
|
66
|
+
plans: [
|
|
67
|
+
{
|
|
68
|
+
slug: 'free',
|
|
69
|
+
name: 'billing.plans.free.name',
|
|
70
|
+
description: 'billing.plans.free.description',
|
|
71
|
+
type: 'free',
|
|
72
|
+
visibility: 'public',
|
|
73
|
+
price: { monthly: 0, yearly: 0 },
|
|
74
|
+
trialDays: 0,
|
|
75
|
+
features: ['basic_analytics'],
|
|
76
|
+
limits: {
|
|
77
|
+
posts: 10,
|
|
78
|
+
monthly_views: 10000,
|
|
79
|
+
storage_gb: 1,
|
|
80
|
+
authors: 1,
|
|
81
|
+
},
|
|
82
|
+
stripePriceIdMonthly: null,
|
|
83
|
+
stripePriceIdYearly: null,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
slug: 'pro',
|
|
87
|
+
name: 'billing.plans.pro.name',
|
|
88
|
+
description: 'billing.plans.pro.description',
|
|
89
|
+
type: 'paid',
|
|
90
|
+
visibility: 'public',
|
|
91
|
+
price: {
|
|
92
|
+
monthly: 2900, // $29.00 - Same as default theme
|
|
93
|
+
yearly: 29000, // $290.00 (16% savings)
|
|
94
|
+
},
|
|
95
|
+
trialDays: 14,
|
|
96
|
+
features: ['basic_analytics', 'custom_domain', 'advanced_seo'],
|
|
97
|
+
limits: {
|
|
98
|
+
posts: -1, // Unlimited posts
|
|
99
|
+
monthly_views: 100000,
|
|
100
|
+
storage_gb: 10,
|
|
101
|
+
authors: 5,
|
|
102
|
+
},
|
|
103
|
+
// Configure these in Stripe Dashboard
|
|
104
|
+
stripePriceIdMonthly: 'price_blog_pro_monthly',
|
|
105
|
+
stripePriceIdYearly: 'price_blog_pro_yearly',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
slug: 'enterprise',
|
|
109
|
+
name: 'billing.plans.enterprise.name',
|
|
110
|
+
description: 'billing.plans.enterprise.description',
|
|
111
|
+
type: 'enterprise',
|
|
112
|
+
visibility: 'hidden',
|
|
113
|
+
trialDays: 30,
|
|
114
|
+
features: ['*'], // All features
|
|
115
|
+
limits: {
|
|
116
|
+
posts: -1, // Unlimited
|
|
117
|
+
monthly_views: -1,
|
|
118
|
+
storage_gb: -1,
|
|
119
|
+
authors: -1,
|
|
120
|
+
},
|
|
121
|
+
stripePriceIdMonthly: null,
|
|
122
|
+
stripePriceIdYearly: null,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
|
|
126
|
+
// ===========================================
|
|
127
|
+
// ACTION MAPPINGS (Blog-specific)
|
|
128
|
+
// ===========================================
|
|
129
|
+
actionMappings: {
|
|
130
|
+
permissions: {
|
|
131
|
+
'team.billing.manage': 'team.billing.manage',
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
features: {
|
|
135
|
+
'domain.customize': 'custom_domain',
|
|
136
|
+
'seo.advanced': 'advanced_seo',
|
|
137
|
+
'support.priority_access': 'priority_support',
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
limits: {
|
|
141
|
+
'posts.create': 'posts',
|
|
142
|
+
'files.upload': 'storage_gb',
|
|
143
|
+
'authors.invite': 'authors',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
}
|