# WhatsMark SaaS — Custom Module Developer Guide

---

## Legal Terms & Conditions

By creating, distributing, or using custom modules for WhatsMark SaaS, you agree to the following terms:

1. **Developer Responsibility.** You are solely responsible for your module's implementation, testing, security, and maintenance. WhatsMark SaaS provides the module system as-is. Do not request, advise, or expect changes to WhatsMark SaaS core to accommodate your module.

2. **Positive Use Only.** Modules must be used for lawful, constructive purposes only. Modules that facilitate spam, abuse, fraud, harassment, or any activity that violates applicable laws or WhatsApp's terms of service are strictly prohibited.

3. **Reverse Engineering Prohibited.** Reverse engineering, decompiling, disassembling, or attempting to derive source code from WhatsMark SaaS core components is strictly prohibited. Any attempt to bypass, circumvent, or tamper with the module system's security mechanisms, licensing, activation controls, or access restrictions will result in immediate termination and legal action.

4. **Core Changes Without Notice.** WhatsMark SaaS reserves the right to modify, update, refactor, or remove any core functionality, API, helper, hook, or internal behavior at any time without prior notice to any party. It is your responsibility to test your module against new releases.

5. **No Warranty.** The module system is provided without warranty of any kind. WhatsMark SaaS is not liable for any damages arising from module development or usage.

6. **Compatibility.** Maintaining compatibility with WhatsMark SaaS updates is your responsibility. Breaking changes in your module caused by core updates are not the responsibility of WhatsMark SaaS.

---

## 1. Overview

Custom modules extend WhatsMark SaaS with new functionality without modifying core code. A module is a self-contained package that can include:

- Routes (web and API)
- Controllers
- Models
- Views (Blade templates)
- Livewire components
- Translations (with multi-tenant support)
- Configuration
- Database migrations and seeders
- Event hooks (WordPress-style actions and filters)
- Frontend assets (JS, CSS via Vite)

Modules live in the `Modules/` directory at the project root. Each module can be activated, deactivated, and removed independently.

---

## 2. Prerequisites

Before developing modules, you must understand:

- **Laravel 12** — Routing, service providers, migrations, Eloquent ORM
- **Livewire 3** — Component lifecycle, properties, actions
- **Tailwind CSS v3** — Utility-first styling
- **Alpine.js v3** — Frontend interactivity
- **WhatsMark SaaS multi-tenant architecture** — Every data operation must be tenant-aware

---

## 3. Quick Start

### Step 1: Create the Module

```bash
php artisan module:make OrderTracker
```

This generates a complete module structure at `Modules/OrderTracker/`.

### Step 2: Review Generated Files

```
Modules/OrderTracker/
├── Config/
│   └── config.php
├── Console/
├── Database/
│   ├── Migrations/
│   ├── Seeders/
│   └── Factories/
├── Http/
│   ├── Controllers/
│   │   └── OrderTrackerController.php
│   └── Middleware/
├── Livewire/
├── Models/
├── Providers/
│   ├── OrderTrackerServiceProvider.php
│   └── RouteServiceProvider.php
├── resources/
│   ├── assets/
│   │   ├── js/
│   │   └── css/
│   ├── lang/
│   │   ├── en.json
│   │   └── tenant_en.json
│   └── views/
│       └── index.blade.php
├── Routes/
│   ├── web.php
│   └── api.php
├── OrderTracker.php
├── module.json
├── composer.json
├── package.json
├── vite.config.js
└── README.md
```

### Step 3: Activate the Module

```bash
php artisan module:activate OrderTracker
```

When activated:
- Migrations run automatically (if `auto_migrations` is enabled in config)
- Seeders execute after migrations
- Routes become available
- Views and translations are registered

### Step 4: Access in Browser

After activation, visit:

```
https://your-domain.com/order-tracker
```

The route prefix is automatically generated as kebab-case from the module name.

### Step 5: Deactivate (When Needed)

```bash
php artisan module:deactivate OrderTracker
```

This disables the module without deleting files.

---

## 4. Module Structure Reference

### module.json

```json
{
    "name": "OrderTracker",
    "alias": "order-tracker",
    "namespace": "Modules\\OrderTracker\\",
    "provider": "Modules\\OrderTracker\\Providers\\OrderTrackerServiceProvider",
    "author": "Module Author",
    "url": "",
    "version": "1.0.0",
    "description": "The OrderTracker Module",
    "keywords": [],
    "order": 0,
    "providers": [
        "Modules\\OrderTracker\\Providers\\OrderTrackerServiceProvider"
    ],
    "aliases": [],
    "require": [],
    "requires_at": "2.0.0",
    "conflicts": [],
    "type": "custom"
}
```

| Field | Purpose |
|-------|---------|
| `name` | Module name (PascalCase) |
| `alias` | URL-friendly identifier (kebab-case) |
| `namespace` | PSR-4 namespace root |
| `provider` | Main service provider class |
| `version` | Semantic version (e.g., `1.0.0`) |
| `order` | Load order (lower = earlier) |
| `require` | Module dependencies (other module names) |
| `conflicts` | Modules that conflict with this one |
| `type` | Always `custom` for developer modules |
| `requires_at` | WhatsmarkSaas minimum required version 

### Main Module Class

`OrderTracker.php` extends `Corbital\ModuleManager\Support\Module` and provides lifecycle methods:

```php
class OrderTracker extends Module
{
    public function registerHooks(): void
    {
        // Register action and filter hooks
    }

    public function activate(): void
    {
        // Runs during activation
    }

    public function deactivate(): void
    {
        // Runs during deactivation
    }

    public function activated(): void
    {
        // Runs after activation completes
    }

    public function deactivated(): void
    {
        // Runs after deactivation completes
    }
}
```

---

## 5. Service Provider

The generated `OrderTrackerServiceProvider.php` handles:

- **boot()** — Registers translations, configuration, views, and loads migrations
- **register()** — Registers the `RouteServiceProvider`

### Registering Livewire Components

Add Livewire component registration in the `boot()` method:

```php
public function boot(): void
{
    // ... existing registrations ...

    // Register Livewire components
    Livewire::component('order-tracker::dashboard', \Modules\OrderTracker\Livewire\Dashboard::class);
}
```

Place Livewire component classes in `Modules/OrderTracker/Livewire/`.

### View Loading Order

Views are loaded from two locations (first match wins):

1. `resources/views/modules/ordertracker/` — Override location (for theme customization)
2. `Modules/OrderTracker/resources/views/` — Default module views

### Translation Loading Order

1. `resources/lang/modules/ordertracker/` — Override location
2. `Modules/OrderTracker/resources/lang/` — Default module translations

---

## 6. Routing

### Web Routes

File: `Modules/OrderTracker/Routes/web.php`

```php
use Illuminate\Support\Facades\Route;
use Modules\OrderTracker\Http\Controllers\OrderTrackerController;

Route::middleware('web')->group(function () {
    Route::prefix('order-tracker')->group(function () {
        Route::get('/', [OrderTrackerController::class, 'index'])
            ->name('order-tracker.index');
    });
});
```

### API Routes

File: `Modules/OrderTracker/Routes/api.php`

```php
use Illuminate\Support\Facades\Route;

Route::middleware('api')->prefix('api')->group(function () {
    Route::prefix('order-tracker')->group(function () {
        // Define API routes here
    });
});
```

### Route Conventions

- **Prefix**: Always kebab-case (`order-tracker`, not `OrderTracker`)
- **Names**: Use prefix as namespace (`order-tracker.index`, `order-tracker.show`)
- **Middleware**: Apply `web` for browser routes, `api` for API endpoints
- **Tenant middleware**: Add tenant middleware as needed for your routes (e.g., `auth`, `tenant`)

### Adding Authentication

```php
Route::middleware(['web', 'auth'])->group(function () {
    Route::prefix('order-tracker')->group(function () {
        Route::get('/', [OrderTrackerController::class, 'index'])
            ->name('order-tracker.index');
    });
});
```

---

## 7. Views & Translations

### Blade Views

Place views in `Modules/OrderTracker/resources/views/`.

Reference views in controllers:

```php
return view('ordertracker::index', ['data' => $data]);
```

The view namespace is the lowercase module name followed by `::`.

### Translations

**Standard translations** — `resources/lang/en.json`:

```json
{
    "Order Tracker": "Order Tracker",
    "View Orders": "View Orders"
}
```

**Tenant-specific translations** — `resources/lang/tenant_en.json`:

```json
{
    "welcome_message": "Welcome to Order Tracker"
}
```

**Using translations in views:**

```blade
{{ t('Order Tracker') }}
```

---

## 8. Helper Functions

| Function | Signature | Description |
|----------|-----------|-------------|
| `module_path` | `module_path($name, $path = '')` | Get absolute path to a module or subdirectory |
| `module_asset` | `module_asset($name, $path)` | Get versioned URL for a module asset (from Vite manifest) |
| `module_config` | `module_config($name, $key, $default = null)` | Get a module config value (dot notation) |
| `module_exists` | `module_exists($name)` | Check if a module directory exists |
| `module_enabled` | `module_enabled($name)` | Check if a module is activated |
| `module_disabled` | `module_disabled($name)` | Check if a module is deactivated |

### Examples

```php
// Get module path
$migrationPath = module_path('OrderTracker', '/Database/Migrations');

// Read module config
$apiLimit = module_config('OrderTracker', 'api.rate_limit', 60);

// Check if another module is available
if (module_enabled('Notifications')) {
    // Integrate with Notifications module
}
```

---

## 9. Hook System

WhatsMark SaaS uses a WordPress-style hook system for module integration.

### Actions

Actions execute code at specific points without returning a value.

**Register an action:**

```php
// In your module's registerHooks() method
public function registerHooks(): void
{
    add_action('after_activate', function ($data) {
        // Run setup tasks after module activation
        Log::info('OrderTracker activated', $data);
    });
}
```

**Fire an action (from your module):**

```php
do_action('order_created', ['order_id' => $order->id]);
```

### Filters

Filters modify and return data through a pipeline.

**Register a filter:**

```php
public function registerHooks(): void
{
    add_filter('dashboard_widgets', function ($widgets) {
        $widgets[] = [
            'name' => 'Order Summary',
            'component' => 'order-tracker::widgets.summary',
        ];
        return $widgets;
    });
}
```

**Apply a filter:**

```php
$widgets = apply_filters('dashboard_widgets', []);
```

### Available Lifecycle Hooks

| Hook | When It Fires |
|------|--------------|
| `before_activate` | Before a module is activated |
| `after_activate` | After a module is activated |
| `before_deactivate` | Before a module is deactivated |
| `after_deactivate` | After a module is deactivated |
| `before_install` | Before a module is installed |
| `after_install` | After a module is installed |
| `before_uninstall` | Before a module is uninstalled |
| `after_uninstall` | After a module is uninstalled |

---

## 10. Multi-Tenant Considerations

WhatsMark SaaS is multi-tenant. All module data must be scoped to the current tenant.

### Tenant Helpers

| Function | Purpose |
|----------|---------|
| `tenant_id()` | Get current tenant ID |
| `tenant_setting($key)` | Get a tenant-specific setting |
| `tenant_check()` | Verify tenant context is active |
| `t($key)` | Tenant-aware translation |

### Models

All models that store tenant data must use the `BelongsToTenant` trait and include a `tenant_id` column:

```php
namespace Modules\OrderTracker\Models;

use Illuminate\Database\Eloquent\Model;
use App\Traits\BelongsToTenant;

class Order extends Model
{
    use BelongsToTenant;

    protected $fillable = ['tenant_id', 'customer_name', 'status'];
}
```

### Migration Example

```php
if (! Schema::hasTable('orders')) {
    Schema::create('orders', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('tenant_id')->index();
        $table->string('customer_name');
        $table->string('status')->default('pending');
        $table->timestamps();

        $table->foreign('tenant_id')->references('id')->on('tenants')->cascadeOnDelete();
    });
}
```

### Cache

Always prefix cache keys with the tenant ID:

```php
$cacheKey = "tenant:" . tenant_id() . ":order-tracker:summary";
Cache::put($cacheKey, $data, now()->addMinutes(30));
```

### Queue Jobs

Preserve tenant context in queued jobs:

```php
class ProcessOrder implements ShouldQueue
{
    public $tenantId;

    public function __construct(int $orderId)
    {
        $this->tenantId = tenant_id();
    }
}
```

---

## 11. Database

### Migrations

Place migration files in `Modules/OrderTracker/Database/Migrations/`.

When the module is activated with `auto_migrations` enabled, migrations run automatically.

To run manually:

```bash
php artisan migrate --path=Modules/OrderTracker/Database/Migrations
```

### Seeders

Place seeders in `Modules/OrderTracker/Database/Seeders/`.

Seeders run automatically after migrations during activation if `auto_migrations.seed` is enabled.

### Factories

Place factories in `Modules/OrderTracker/Database/Factories/` for use in tests.

---

## 12. Configuration

### Module Config

File: `Modules/OrderTracker/Config/config.php`

```php
return [
    'name' => 'OrderTracker',
    'api' => [
        'rate_limit' => 60,
    ],
];
```

Access via:

```php
$value = module_config('OrderTracker', 'api.rate_limit');
```

### Publishing Config

Config is published to `config/ordertracker.php` when the service provider boots. Users can override module config values by editing the published file.

---

## 13. Frontend Assets

### Vite Configuration

The generated `vite.config.js` uses `laravel-vite-plugin`:

```js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';

export default defineConfig({
    plugins: [vue()],
    build: {
        outDir: resolve(__dirname, 'public/build'),
        emptyOutDir: true,
        manifest: 'manifest.json',
        rollupOptions: {
            input: {
                'app': resolve(__dirname, 'resources/assets/js/app.js'),
                'styles': resolve(__dirname, 'resources/assets/css/app.css'),
            }
        }
    }
});
```

### Building Assets

```bash
cd Modules/OrderTracker
npm install
npm run build
```

### Loading Assets in Views

```php
add_action('app_module_headers', function () {
    app()->view->startPush('styles');
    echo module_style($this->moduleName, 'resources/assets/css/app.css');
    app()->view->stopPush();
});

add_action('app_module_footers', function () {
    app()->view->startPush('scripts');
    echo module_script($this->moduleName, 'resources/assets/js/app.js');
    app()->view->stopPush();
});
```

---

## 14. Artisan Commands

| Command | Description |
|---------|-------------|
| `php artisan module:make {Name}` | Create a new module |
| `php artisan module:make {Name} --force` | Recreate module (overwrites existing) |
| `php artisan module:activate {Name}` | Activate a module |
| `php artisan module:deactivate {Name}` | Deactivate a module |

---

## 15. Troubleshooting

### Module routes not showing

1. Verify the module is activated: `php artisan module:activate ModuleName`
2. Clear route cache: `php artisan route:clear`
3. Check route list: `php artisan route:list --path=module-name`

### Views not found

1. Verify view files exist in `Modules/ModuleName/resources/views/`
2. Use the correct namespace: `modulename::viewfile` (lowercase, no hyphens)
3. Clear view cache: `php artisan view:clear`

### Translations not loading

1. Check JSON files exist in `Modules/ModuleName/resources/lang/`
2. Use valid JSON format (no trailing commas)
3. Clear translation cache: `php artisan cache:clear`

### Migration errors on activation

1. Check migration files for syntax errors
2. Verify `tenant_id` column is included where needed
3. Run manually to see detailed errors: `php artisan migrate --path=Modules/ModuleName/Database/Migrations`

### Config not merging

1. Clear config cache: `php artisan config:clear`
2. Verify `Config/config.php` returns an array
3. Check the published config at `config/modulename.php` for overrides

### Module not autoloading

1. Verify `composer.json` in module has correct namespace: `Modules\\ModuleName\\`
2. Run `composer dump-autoload`
3. Check `module.json` has correct `provider` path

---

## 16. User Flow Diagram

```
Developer creates module
        │
        ▼
php artisan module:make ModuleName
        │
        ▼
Module files generated in Modules/ModuleName/
        │
        ▼
Developer writes code (controllers, models, views, routes)
        │
        ▼
php artisan module:activate ModuleName
        │
        ├── Migrations run automatically
        ├── Seeders execute
        ├── Routes registered
        ├── Views loaded
        ├── Translations loaded
        └── Hooks fired (after_activate)
        │
        ▼
Module is live and accessible
        │
        ▼
(Optional) php artisan module:deactivate ModuleName
        │
        ├── Routes unregistered
        ├── Hooks fired (after_deactivate)
        └── Module disabled (files preserved)
```

---

*This documentation is for WhatsMark SaaS custom module development. All terms and conditions apply.*
