Skip to content
Merged
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
10 changes: 8 additions & 2 deletions docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ Open the URL shown in your terminal. The setup screen will guide you through cho
Using Claude Code, Cursor, or another coding agent? Paste this before you start:

```
I'm building a SaaS with Saucebase — a modular Laravel starter kit.
I'm building a new application with Saucebase — a modular Laravel SaaS starter kit.

Read https://saucebase-dev.github.io/docs/for-agents.md for the project conventions, structure, and patterns. Treat it as the source of truth for how to add features, where files belong, and what commands to run.
Fetch and follow the instructions from https://saucebase-dev.github.io/docs/for-agents.md Treat the returned Markdown as the source of truth for how to install, set up, and build with Saucebase in this session.
```
Comment on lines +31 to 34

[Read the full agent guide →](pathname:///for-agents.md)

:::warning SQLite limitations
The default installation uses SQLite, which is sufficient for basic development but does not support all features. Some Filament admin panel widgets and Billing module charts require MySQL-specific capabilities and may not display correctly with SQLite.

For full functionality, configure the app to use **MySQL** — run it locally, or use [Laravel Herd](https://herd.laravel.com), [Laravel Sail](https://laravel.com/docs/sail), or Docker (`bash bin/setup-env`).
:::

<ModuleGrid
title="Explore the Modules"
subtitle="Browse the available modules and install the ones that fit your product — each one copies directly into your codebase, ready to customize."
Expand Down
45 changes: 45 additions & 0 deletions src/components/ModuleCard.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,51 @@
color: var(--ifm-color-emphasis-700);
}

/* Framework badges */

.frameworkBadges {
display: flex;
gap: 0.375rem;
margin-bottom: 0.75rem;
flex-wrap: wrap;
}

.frameworkBadge {
display: inline-flex;
align-items: center;
border-radius: 9999px;
padding: 0.15rem 0.55rem;
font-size: 0.65rem;
font-weight: 600;
letter-spacing: 0.03em;
text-transform: uppercase;
border: 1px solid transparent;
}

.fw_vue {
background: #dcfce7;
color: #16a34a;
border-color: #bbf7d0;
}

.fw_react {
background: #e0f2fe;
color: #0369a1;
border-color: #bae6fd;
}

html[data-theme='dark'] .fw_vue {
background: rgba(34, 197, 94, 0.15);
color: #4ade80;
border-color: rgba(34, 197, 94, 0.3);
}

html[data-theme='dark'] .fw_react {
background: rgba(14, 165, 233, 0.15);
color: #38bdf8;
border-color: rgba(14, 165, 233, 0.3);
}

/* Features row */

.featuresRow {
Expand Down
17 changes: 17 additions & 0 deletions src/components/ModuleCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface ModuleCardProps {
href: string;
features?: ModuleFeature[];
dependencies?: string[];
frameworks?: string[];
}

const BADGE_LABELS: Record<string, string> = {
Expand All @@ -45,6 +46,11 @@ function resolveDependencyTitle(id: string): string {
return mod ? mod.title : id;
}

const FRAMEWORK_LABELS: Record<string, string> = {
vue: 'Vue',
react: 'React',
};

export default function ModuleCard({
title,
description,
Expand All @@ -55,6 +61,7 @@ export default function ModuleCard({
href,
features = [],
dependencies = [],
frameworks = [],
}: ModuleCardProps): JSX.Element {
const coverStyle = !coverImage
? { background: `linear-gradient(135deg, ${color}88, ${color}33)` }
Expand Down Expand Up @@ -84,6 +91,16 @@ export default function ModuleCard({
<h3 className={styles.title}>{title}</h3>
<p className={styles.description}>{description}</p>

{frameworks.length > 0 && (
<div className={styles.frameworkBadges}>
{frameworks.map(fw => (
<span key={fw} className={`${styles.frameworkBadge} ${styles[`fw_${fw}`] ?? ''}`}>
{FRAMEWORK_LABELS[fw] ?? fw}
</span>
))}
</div>
)}

{features.length > 0 && (
<div className={styles.featuresRow}>
{features.map(feature => (
Expand Down
163 changes: 144 additions & 19 deletions src/data/modules.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"logo": "lucide:lock",
"badge": "free",
"href": "/modules/auth",
"frameworks": ["vue", "react"],
"features": [
{
"icon": "lucide:log-in",
Expand Down Expand Up @@ -112,6 +113,7 @@
"logo": "lucide:settings-2",
"badge": "free",
"href": "/modules/settings",
"frameworks": ["vue"],
"features": [
{
"icon": "lucide:user",
Expand Down Expand Up @@ -148,6 +150,7 @@
"logo": "lucide:credit-card",
"badge": "free",
"href": "/modules/billing",
"frameworks": ["vue"],
"features": [
{
"icon": "lucide:shopping-cart",
Expand All @@ -174,7 +177,7 @@
"details": "Payment events are verified and processed reliably. Duplicate events are safely ignored, and failed payments update subscription status automatically."
}
],
"dependencies": ["auth"]
"dependencies": ["auth", "settings"]
},
{
"id": "roadmap",
Expand All @@ -184,6 +187,7 @@
"logo": "lucide:map",
"badge": "free",
"href": "/modules/roadmap",
"frameworks": ["vue"],
"features": [
{
"icon": "lucide:lightbulb",
Expand Down Expand Up @@ -220,6 +224,7 @@
"logo": "lucide:megaphone",
"badge": "free",
"href": "/modules/announcements",
"frameworks": ["vue"],
"features": [
{
"icon": "lucide:layout-template",
Expand Down Expand Up @@ -262,6 +267,7 @@
"logo": "lucide:palette",
"badge": "free",
"href": "/modules/themes",
"frameworks": ["vue"],
"features": [
{
"icon": "lucide:palette",
Expand Down Expand Up @@ -296,8 +302,9 @@
"description": "Full-featured blog with posts, categories, tags, and a Filament admin panel for content management.",
"color": "#ec4899",
"logo": "lucide:newspaper",
"badge": "coming-soon",
"href": "#",
"badge": "new",
"href": "/modules/blog",
"frameworks": ["vue"],
"features": [
{
"icon": "lucide:file-text",
Expand Down Expand Up @@ -327,34 +334,152 @@
"dependencies": ["auth"]
},
{
"id": "teams",
"title": "Teams",
"description": "Multi-user team collaboration with role-based permissions, invitations, and team switching for B2B SaaS applications.",
"id": "webhooks",
"title": "Webhooks",
"description": "Send reliable HTTP callbacks to external services when events occur in your app, with delivery logs and retry handling.",
"color": "#6b7280",
"logo": "lucide:users",
"logo": "lucide:webhook",
"badge": "coming-soon",
"href": "#",
"frameworks": ["vue"],
"features": [
{
"icon": "lucide:users",
"name": "Team Collaboration",
"description": "Multi-user teams",
"details": "Users can create and switch between multiple teams. All data is automatically scoped to the active team, making it straightforward to build B2B multi-tenant applications."
"icon": "lucide:zap",
"name": "Event Triggers",
"description": "Fire webhooks on any app event",
"details": "Register listeners for any application event and automatically dispatch an HTTP POST to configured endpoints. Supports custom payloads and per-event endpoint routing."
},
{
"icon": "lucide:key",
"name": "Role Permissions",
"description": "Role-based access control",
"details": "Built-in owner, admin, and member roles with permission checks via `$user->hasTeamPermission('billing')`. Role definitions are customizable to match your application's needs."
"icon": "lucide:list",
"name": "Delivery Logs",
"description": "Inspect every webhook attempt",
"details": "Every delivery attempt is logged with the request body, response status, and latency. Browse the full history from the admin panel to diagnose issues."
},
{
"icon": "lucide:refresh-cw",
"name": "Retry Handling",
"description": "Automatic retries with exponential backoff",
"details": "Failed deliveries are retried automatically with exponential backoff. You can also trigger a manual retry from the delivery log for any failed attempt."
},
{
"icon": "lucide:mail-plus",
"name": "Invitations",
"description": "Team invitations by email",
"details": "Invite users to a team by email. Invited users receive an email with an accept link. Pending invitations are managed from the team settings page and expire after 7 days."
"icon": "lucide:bell-off",
"name": "Failure Alerts",
"description": "Get notified when deliveries fail repeatedly",
"details": "After a configurable number of consecutive failures, an alert is sent to the team. Endpoints can be temporarily disabled to prevent noise during known outages."
}
],
"dependencies": ["auth"]
},
{
"id": "integrations",
"title": "Integrations",
"description": "Connect your app with third-party services like Slack, Zapier, and more through a unified integration layer.",
"color": "#6b7280",
"logo": "lucide:blocks",
"badge": "coming-soon",
"href": "#",
"frameworks": ["vue"],
"features": [
{
"icon": "lucide:slack",
"name": "Slack",
"description": "Send notifications to Slack channels",
"details": "Post messages to any Slack channel when events occur in your app. Supports custom message formatting, channel routing per event type, and OAuth app installation."
},
{
"icon": "lucide:webhook",
"name": "Zapier",
"description": "Trigger Zapier workflows from your app",
"details": "Connect to thousands of apps via Zapier triggers. Any event in your app can kick off a Zapier workflow without custom code."
},
{
"icon": "lucide:plug",
"name": "Custom Connections",
"description": "Build your own integration adapters",
"details": "A driver-based integration layer lets you implement custom adapters for any third-party service. Register them alongside the built-in drivers with no framework changes."
},
{
"icon": "lucide:layers",
"name": "Unified Layer",
"description": "One API for all integrations",
"details": "All integrations share a common interface for connecting, sending events, and handling credentials. Switching or adding providers requires no changes to your event code."
}
],
"dependencies": ["auth"]
},
{
"id": "notifications",
"title": "Notifications",
"description": "In-app and email notifications with templates, user preferences, and delivery tracking for every channel.",
"color": "#6b7280",
"logo": "lucide:bell",
"badge": "coming-soon",
"href": "#",
"frameworks": ["vue"],
"features": [
{
"icon": "lucide:bell",
"name": "In-App",
"description": "Real-time notifications inside the app",
"details": "Notifications appear in a dropdown bell menu and are marked read on open. Unread count is updated in real time via broadcasting without a full page reload."
},
{
"icon": "lucide:mail",
"name": "Email",
"description": "Transactional email notifications",
"details": "Each notification type has a corresponding email template. Emails are queued and sent via Laravel's mail system — swap the driver (SMTP, Mailgun, SES) in the config."
},
{
"icon": "lucide:file-text",
"name": "Templates",
"description": "Editable notification templates",
"details": "In-app and email notification content is defined in template classes. Subject, body, and action URL are customizable per notification type without touching core code."
},
{
"icon": "lucide:sliders-horizontal",
"name": "Preferences",
"description": "Per-channel user preferences",
"details": "Users control which notifications they receive and on which channels from a preferences page. Preferences are evaluated before dispatch — muted channels are skipped entirely."
}
],
"dependencies": ["auth"]
},
{
"id": "analytics",
"title": "Analytics",
"description": "Track pageviews, custom events, and user behavior with a privacy-friendly built-in dashboard — no third-party scripts.",
"color": "#6b7280",
"logo": "lucide:bar-chart-3",
"badge": "coming-soon",
"href": "#",
"frameworks": ["vue"],
"features": [
{
"icon": "lucide:eye",
"name": "Pageviews",
"description": "Automatic pageview tracking",
"details": "Every page navigation is tracked automatically via an Inertia event listener. No script tags or manual calls needed — views are recorded server-side with referrer and path."
},
{
"icon": "lucide:mouse-pointer-click",
"name": "Custom Events",
"description": "Track any user action",
"details": "Fire custom events from anywhere in the frontend with a single helper call. Events are batched and sent to a lightweight endpoint that writes them to the analytics store."
},
{
"icon": "lucide:layout-dashboard",
"name": "Dashboard",
"description": "Built-in analytics dashboard",
"details": "A Filament dashboard page visualises pageviews, unique visitors, top pages, and custom event counts over configurable date ranges. No third-party service required."
},
{
"icon": "lucide:shield",
"name": "Privacy First",
"description": "No cookies, no third-party scripts",
"details": "All data is stored in your own database. No cookies are set, no external scripts are loaded, and no personal data leaves your server — fully GDPR-compliant by default."
}
],
"dependencies": []
}
]
}
Loading