Views & Assets
This guide covers creating Blade templates, working with the Metronic theme, and managing CSS/JavaScript assets.
View Structure
Addon views are stored in Resources/views/:
Resources/views/
├── layouts/
│ └── master.blade.php # Optional: addon-specific layout
├── index.blade.php # Main page
├── settings.blade.php # Settings page
├── validate/
│ ├── form.blade.php
│ └── results.blade.php
└── partials/
├── header.blade.php
└── stats-card.blade.php
Extending the Main Layout
Mumara Campaigns uses the Metronic theme. Extend the main layout:
{{-- Resources/views/index.blade.php --}}
@extends('layouts.master2')
@section('title', 'Email Validator')
@section('content')
<div class="kt-container kt-container--fluid kt-grid__item kt-grid__item--fluid">
{{-- Page Header --}}
<div class="kt-subheader kt-grid__item" id="kt_subheader">
<div class="kt-container kt-container--fluid">
<div class="kt-subheader__main">
<h3 class="kt-subheader__title">Email Validator</h3>
<span class="kt-subheader__separator kt-subheader__separator--v"></span>
<span class="kt-subheader__desc">Validate and verify email addresses</span>
</div>
<div class="kt-subheader__toolbar">
<a href="{{ route('emailvalidator.validate.form') }}" class="btn btn-brand btn-elevate btn-icon-sm">
<i class="la la-plus"></i>
New Validation
</a>
</div>
</div>
</div>
{{-- Main Content --}}
<div class="kt-content kt-grid__item kt-grid__item--fluid">
@include('emailvalidator::partials.stats-cards')
<div class="kt-portlet">
<div class="kt-portlet__head">
<div class="kt-portlet__head-label">
<h3 class="kt-portlet__head-title">Recent Validations</h3>
</div>
</div>
<div class="kt-portlet__body">
@include('emailvalidator::partials.recent-table', ['results' => $recentResults])
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
<script>
// Page-specific JavaScript
</script>
@endsection
Metronic UI Components
Portlets (Cards)
<div class="kt-portlet">
<div class="kt-portlet__head">
<div class="kt-portlet__head-label">
<span class="kt-portlet__head-icon">
<i class="flaticon-email"></i>
</span>
<h3 class="kt-portlet__head-title">
Card Title
<small>Subtitle text</small>
</h3>
</div>
<div class="kt-portlet__head-toolbar">
<a href="#" class="btn btn-clean btn-sm btn-icon btn-icon-md">
<i class="la la-refresh"></i>
</a>
</div>
</div>
<div class="kt-portlet__body">
{{-- Card content --}}
</div>
<div class="kt-portlet__foot">
<div class="kt-form__actions">
<button type="submit" class="btn btn-primary">Submit</button>
<button type="reset" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
Data Tables
<table class="table table-striped table-bordered table-hover" id="validationTable">
<thead>
<tr>
<th>Email</th>
<th>Status</th>
<th>Score</th>
<th>Validated At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($results as $result)
<tr>
<td>{{ $result->email }}</td>
<td>
@if($result->status === 'valid')
<span class="kt-badge kt-badge--success kt-badge--inline">Valid</span>
@elseif($result->status === 'invalid')
<span class="kt-badge kt-badge--danger kt-badge--inline">Invalid</span>
@else
<span class="kt-badge kt-badge--warning kt-badge--inline">Unknown</span>
@endif
</td>
<td>{{ $result->score }}%</td>
<td>{{ $result->created_at->format('M j, Y H:i') }}</td>
<td>
<a href="{{ route('emailvalidator.result', $result->id) }}"
class="btn btn-sm btn-clean btn-icon btn-icon-md" title="View Details">
<i class="la la-eye"></i>
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
@push('scripts')
<script>
$('#validationTable').DataTable({
responsive: true,
order: [[3, 'desc']]
});
</script>
@endpush
Forms
<form action="{{ route('emailvalidator.validate') }}" method="POST" class="kt-form">
@csrf
<div class="kt-portlet__body">
<div class="form-group">
<label>Email Addresses</label>
<textarea name="emails" class="form-control @error('emails') is-invalid @enderror"
rows="10" placeholder="Enter one email per line">{{ old('emails') }}</textarea>
@error('emails')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<span class="form-text text-muted">Enter up to 100 emails, one per line</span>
</div>
<div class="form-group row">
<label class="col-lg-3 col-form-label">Validation Type</label>
<div class="col-lg-6">
<select name="type" class="form-control kt-selectpicker">
<option value="syntax">Syntax Only</option>
<option value="mx">MX Record Check</option>
<option value="full" selected>Full Validation</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-lg-3 col-form-label">Options</label>
<div class="col-lg-6">
<div class="kt-checkbox-list">
<label class="kt-checkbox">
<input type="checkbox" name="check_disposable" value="1" checked>
Check for disposable emails
<span></span>
</label>
<label class="kt-checkbox">
<input type="checkbox" name="check_role" value="1" checked>
Check for role-based emails
<span></span>
</label>
</div>
</div>
</div>
</div>
<div class="kt-portlet__foot">
<div class="kt-form__actions">
<button type="submit" class="btn btn-primary">
<i class="la la-check"></i> Validate Emails
</button>
<button type="reset" class="btn btn-secondary">Reset</button>
</div>
</div>
</form>
Alerts
{{-- Success Alert --}}
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
<div class="alert-icon"><i class="flaticon-warning"></i></div>
<div class="alert-text">{{ session('success') }}</div>
<div class="alert-close">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="la la-close"></i></span>
</button>
</div>
</div>
@endif
{{-- Error Alert --}}
@if(session('error'))
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<div class="alert-icon"><i class="flaticon-warning"></i></div>
<div class="alert-text">{{ session('error') }}</div>
<div class="alert-close">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="la la-close"></i></span>
</button>
</div>
</div>
@endif
{{-- Validation Errors --}}
@if($errors->any())
<div class="alert alert-danger">
<div class="alert-icon"><i class="flaticon-warning"></i></div>
<div class="alert-text">
<ul class="mb-0">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
</div>
@endif
Stats Cards
{{-- Resources/views/partials/stats-cards.blade.php --}}
<div class="row">
<div class="col-lg-3">
<div class="kt-portlet kt-portlet--height-fluid-half kt-portlet--border-bottom-brand">
<div class="kt-portlet__body kt-portlet__body--fluid">
<div class="kt-widget26">
<div class="kt-widget26__content">
<span class="kt-widget26__number">{{ number_format($stats['total']) }}</span>
<span class="kt-widget26__desc">Total Validated</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="kt-portlet kt-portlet--height-fluid-half kt-portlet--border-bottom-success">
<div class="kt-portlet__body kt-portlet__body--fluid">
<div class="kt-widget26">
<div class="kt-widget26__content">
<span class="kt-widget26__number kt-font-success">{{ number_format($stats['valid']) }}</span>
<span class="kt-widget26__desc">Valid Emails</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="kt-portlet kt-portlet--height-fluid-half kt-portlet--border-bottom-danger">
<div class="kt-portlet__body kt-portlet__body--fluid">
<div class="kt-widget26">
<div class="kt-widget26__content">
<span class="kt-widget26__number kt-font-danger">{{ number_format($stats['invalid']) }}</span>
<span class="kt-widget26__desc">Invalid Emails</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="kt-portlet kt-portlet--height-fluid-half kt-portlet--border-bottom-warning">
<div class="kt-portlet__body kt-portlet__body--fluid">
<div class="kt-widget26">
<div class="kt-widget26__content">
<span class="kt-widget26__number kt-font-warning">{{ $stats['rate'] }}%</span>
<span class="kt-widget26__desc">Validity Rate</span>
</div>
</div>
</div>
</div>
</div>
</div>
View Namespacing
Access addon views using the namespace:
// In controller
return view('emailvalidator::index');
return view('emailvalidator::settings.general');
return view('emailvalidator::partials.table');
{{-- In Blade templates --}}
@include('emailvalidator::partials.header')
@extends('emailvalidator::layouts.master')
Passing Data to Views
// Single variables
return view('emailvalidator::index', [
'title' => 'Dashboard',
'stats' => $stats,
'recentResults' => $recent
]);
// Using compact
return view('emailvalidator::index', compact('title', 'stats', 'recentResults'));
// Using with()
return view('emailvalidator::index')
->with('title', 'Dashboard')
->with('stats', $stats);
View Composers
Share data across multiple views:
// In service provider boot()
public function boot(): void
{
view()->composer('emailvalidator::*', function ($view) {
$view->with('addonVersion', config('emailvalidator.version'));
});
view()->composer(['emailvalidator::index', 'emailvalidator::dashboard'], function ($view) {
$view->with('quickStats', $this->getQuickStats());
});
}
Asset Management
Directory Structure
Resources/assets/
├── js/
│ ├── app.js # Main JavaScript
│ └── components/
│ └── validator.js
├── sass/
│ ├── app.scss # Main SASS
│ └── _variables.scss
└── css/
└── custom.css # Plain CSS
Laravel Mix Configuration
// webpack.mix.js
const mix = require('laravel-mix');
require('laravel-mix-merge-manifest');
mix.setPublicPath('../../public').mergeManifest();
// Compile JavaScript
mix.js(__dirname + '/Resources/assets/js/app.js', 'js/emailvalidator.js');
// Compile SASS
mix.sass(__dirname + '/Resources/assets/sass/app.scss', 'css/emailvalidator.css');
// Copy static assets
mix.copy(__dirname + '/Resources/assets/images', '../../public/images/emailvalidator');
// Production versioning
if (mix.inProduction()) {
mix.version();
}
Building Assets
cd Addons/EmailValidator
# Install dependencies
npm install
# Development build
npm run dev
# Watch for changes
npm run watch
# Production build (minified, versioned)
npm run production
Including Assets in Views
@section('styles')
<link href="{{ asset('css/emailvalidator.css') }}" rel="stylesheet">
@endsection
@section('scripts')
<script src="{{ asset('js/emailvalidator.js') }}"></script>
@endsection
Inline Scripts and Styles
@push('styles')
<style>
.validation-result { border-left: 3px solid #5d78ff; }
.validation-result.valid { border-left-color: #0abb87; }
.validation-result.invalid { border-left-color: #fd397a; }
</style>
@endpush
@push('scripts')
<script>
$(document).ready(function() {
$('#validateForm').on('submit', function(e) {
e.preventDefault();
// AJAX validation
});
});
</script>
@endpush
AJAX Requests
@push('scripts')
<script>
$(document).ready(function() {
$('#validateBtn').on('click', function() {
var email = $('#emailInput').val();
$.ajax({
url: '{{ route("emailvalidator.validate.ajax") }}',
method: 'POST',
data: {
email: email,
_token: '{{ csrf_token() }}'
},
beforeSend: function() {
KTApp.blockPage({
overlayColor: '#000000',
opacity: 0.1,
state: 'primary',
message: 'Validating...'
});
},
success: function(response) {
KTApp.unblockPage();
if (response.success) {
toastr.success(response.message);
updateResultsTable(response.data);
}
},
error: function(xhr) {
KTApp.unblockPage();
toastr.error('Validation failed. Please try again.');
}
});
});
});
</script>
@endpush
Modals
{{-- Trigger Button --}}
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#confirmModal">
Delete Selected
</button>
{{-- Modal --}}
<div class="modal fade" id="confirmModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Confirm Delete</h5>
<button type="button" class="close" data-dismiss="modal">
<span>×</span>
</button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete the selected items?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirmDelete">Delete</button>
</div>
</div>
</div>
</div>
Publishing Views
Allow users to customize views:
// In service provider
$this->publishes([
module_path($this->moduleName, 'Resources/views')
=> resource_path('views/modules/' . $this->moduleNameLower),
], 'views');
Publish with:
php artisan vendor:publish --tag=emailvalidator-views
Published views in resources/views/modules/emailvalidator/ take precedence over addon views.