- Add Docker configuration (Dockerfile, docker-compose.yml) - Add Nginx and Supervisor configuration - Add deployment documentation (DEPLOYMENT.md) - Complete Phase 3 data migration (63 entries, 63 docs, 28 subs) - Add responsive PDF viewer component - Fix date format to American (MM/DD/YYYY) - Update typography to match v1.0 - Add admin dashboard with full CRUD operations - Configure PostgreSQL with secure password - Connect to caddy_network for reverse proxy - Ready for production deployment
224 lines
11 KiB
Vue
224 lines
11 KiB
Vue
<script setup lang="ts">
|
||
import { Head, Link, router } from '@inertiajs/vue3';
|
||
|
||
interface Subscriber {
|
||
id: number;
|
||
email: string;
|
||
is_active: boolean;
|
||
created_at: string;
|
||
}
|
||
|
||
interface Pagination {
|
||
current_page: number;
|
||
last_page: number;
|
||
per_page: number;
|
||
total: number;
|
||
}
|
||
|
||
interface Stats {
|
||
total: number;
|
||
active: number;
|
||
inactive: number;
|
||
}
|
||
|
||
interface Props {
|
||
subscribers: Subscriber[];
|
||
pagination: Pagination;
|
||
stats: Stats;
|
||
}
|
||
|
||
const props = defineProps<Props>();
|
||
|
||
const deactivateSubscriber = (id: number, email: string) => {
|
||
if (confirm(`Are you sure you want to deactivate ${email}?`)) {
|
||
router.delete(route('admin.subscribers.destroy', id));
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<div class="min-h-screen bg-gray-100">
|
||
<Head title="Manage Subscribers" />
|
||
|
||
<!-- Header -->
|
||
<header class="bg-white shadow">
|
||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||
<div class="flex justify-between items-center">
|
||
<div>
|
||
<h1 class="text-3xl font-bold text-gray-900">
|
||
Manage Subscribers
|
||
</h1>
|
||
<p class="text-sm text-gray-600 mt-1">
|
||
{{ props.stats.active }} active • {{ props.stats.inactive }} inactive • {{ props.stats.total }} total
|
||
</p>
|
||
</div>
|
||
<Link
|
||
:href="route('admin.dashboard')"
|
||
class="px-4 py-2 text-sm text-gray-700 hover:text-gray-900 transition-colors"
|
||
>
|
||
← Back to Dashboard
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Main Content -->
|
||
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||
<!-- Stats Cards -->
|
||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||
<div class="bg-white rounded-lg shadow p-6">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-medium text-gray-600">Total Subscribers</p>
|
||
<p class="text-3xl font-bold text-gray-900 mt-2">
|
||
{{ props.stats.total }}
|
||
</p>
|
||
</div>
|
||
<div class="p-3 bg-blue-100 rounded-full">
|
||
<svg class="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-lg shadow p-6">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-medium text-gray-600">Active Subscribers</p>
|
||
<p class="text-3xl font-bold text-green-600 mt-2">
|
||
{{ props.stats.active }}
|
||
</p>
|
||
</div>
|
||
<div class="p-3 bg-green-100 rounded-full">
|
||
<svg class="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-lg shadow p-6">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-medium text-gray-600">Inactive Subscribers</p>
|
||
<p class="text-3xl font-bold text-gray-400 mt-2">
|
||
{{ props.stats.inactive }}
|
||
</p>
|
||
</div>
|
||
<div class="p-3 bg-gray-100 rounded-full">
|
||
<svg class="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Subscribers Table -->
|
||
<div class="bg-white rounded-lg shadow">
|
||
<div class="overflow-x-auto">
|
||
<table class="min-w-full divide-y divide-gray-200">
|
||
<thead class="bg-gray-50">
|
||
<tr>
|
||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
Email
|
||
</th>
|
||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
Status
|
||
</th>
|
||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
Subscribed On
|
||
</th>
|
||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
Actions
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="bg-white divide-y divide-gray-200">
|
||
<tr
|
||
v-for="subscriber in props.subscribers"
|
||
:key="subscriber.id"
|
||
class="hover:bg-gray-50 transition-colors"
|
||
>
|
||
<td class="px-6 py-4 text-sm font-medium text-gray-900">
|
||
{{ subscriber.email }}
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
||
<span
|
||
v-if="subscriber.is_active"
|
||
class="px-2 py-1 text-xs font-medium text-green-700 bg-green-100 rounded-full"
|
||
>
|
||
Active
|
||
</span>
|
||
<span
|
||
v-else
|
||
class="px-2 py-1 text-xs font-medium text-gray-700 bg-gray-100 rounded-full"
|
||
>
|
||
Inactive
|
||
</span>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
||
{{ subscriber.created_at }}
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||
<button
|
||
v-if="subscriber.is_active"
|
||
@click="deactivateSubscriber(subscriber.id, subscriber.email)"
|
||
class="text-red-600 hover:text-red-900 transition-colors"
|
||
>
|
||
Deactivate
|
||
</button>
|
||
<span v-else class="text-gray-400">
|
||
Deactivated
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
<tr v-if="props.subscribers.length === 0">
|
||
<td colspan="4" class="px-6 py-12 text-center text-gray-500">
|
||
No subscribers found.
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Pagination -->
|
||
<div v-if="props.pagination.last_page > 1" class="px-6 py-4 border-t border-gray-200">
|
||
<div class="flex items-center justify-between">
|
||
<div class="text-sm text-gray-700">
|
||
Showing page {{ props.pagination.current_page }} of {{ props.pagination.last_page }}
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<Link
|
||
v-if="props.pagination.current_page > 1"
|
||
:href="route('admin.subscribers.index', { page: props.pagination.current_page - 1 })"
|
||
class="px-3 py-1 text-sm bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
|
||
>
|
||
Previous
|
||
</Link>
|
||
<Link
|
||
v-if="props.pagination.current_page < props.pagination.last_page"
|
||
:href="route('admin.subscribers.index', { page: props.pagination.current_page + 1 })"
|
||
class="px-3 py-1 text-sm bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
|
||
>
|
||
Next
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Info Box -->
|
||
<div class="mt-6 bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||
<h3 class="text-sm font-medium text-blue-900 mb-2">
|
||
ℹ️ About Subscriber Management
|
||
</h3>
|
||
<p class="text-sm text-blue-700">
|
||
Deactivating a subscriber will prevent them from receiving future email notifications.
|
||
They can resubscribe at any time through the public website.
|
||
</p>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</template>
|