feat: implement adaptive multi-pane mailbox with utilities and security gates
- Added fully responsive 3-pane layout for Mailbox - Integrated global cinematic Toast system in app shell - Implemented Copy to Clipboard and QR Code modal utilities - Added diverse mock email data for premium demonstration - Implemented security gates for custom mailboxes and attachments - Fixed critical 500 error for orphaned email selection - Refined landing page hero CTA text
This commit is contained in:
255
app/Livewire/Mailbox.php
Normal file
255
app/Livewire/Mailbox.php
Normal file
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Component;
|
||||
|
||||
#[Layout('components.layouts.app')]
|
||||
class Mailbox extends Component
|
||||
{
|
||||
public $activeMailboxes = [
|
||||
['id' => 1, 'address' => 'idevakk@imail.com', 'expires_at' => '23:59:12', 'progress' => 90],
|
||||
['id' => 2, 'address' => 'tester_99@devmail.ai', 'expires_at' => '45:12:05', 'progress' => 40],
|
||||
];
|
||||
|
||||
public $currentMailboxId = 1;
|
||||
|
||||
public $activeFolder = 'inbox';
|
||||
|
||||
public $selectedEmailId = null;
|
||||
|
||||
public $search = '';
|
||||
|
||||
public $page = 1;
|
||||
|
||||
public $totalPages = 5;
|
||||
|
||||
// Create State
|
||||
public $showCreateModal = false;
|
||||
|
||||
public $createType = 'random'; // random | custom
|
||||
|
||||
public $customUsername = '';
|
||||
|
||||
public $customDomain = 'imail.com';
|
||||
|
||||
public $availableDomains = ['imail.com', 'devmail.ai', 'temp-inbox.net'];
|
||||
|
||||
public function getEmailsProperty()
|
||||
{
|
||||
// Mock emails based on mailbox ID for demonstration
|
||||
$emails = [
|
||||
1 => [
|
||||
[
|
||||
'id' => 1,
|
||||
'from_name' => 'GitHub Security',
|
||||
'from_email' => 'noreply@github.com',
|
||||
'subject' => '[GitHub] A new personal access token was created',
|
||||
'preview' => 'A new personal access token (classic) was recently added to your account.',
|
||||
'content' => '<p>Hi @idevakk,</p><p>A new personal access token (classic) was recently added to your account IDEVAKK.</p><p>If this was you, you can safely ignore this email.</p><p>If this was not you, please visit https://github.com/settings/tokens to revoke the token.</p>',
|
||||
'time' => '10:24 AM',
|
||||
'unread' => true,
|
||||
'flagged' => true,
|
||||
'attachments' => [],
|
||||
],
|
||||
[
|
||||
'id' => 101,
|
||||
'from_name' => 'Linear',
|
||||
'from_email' => 'updates@linear.app',
|
||||
'subject' => 'New issue assigned: [UI-124] Fix sidebar overflow',
|
||||
'preview' => 'You have been assigned to a new issue in the UI project. Please review the details...',
|
||||
'content' => '<p>Hello,</p><p>You have been assigned to <strong>[UI-124] Fix sidebar overflow in mobile view</strong>.</p><p>Priority: High</p><p>Project: Imail Revamp</p><p>View details at https://linear.app/imail/issue/UI-124</p>',
|
||||
'time' => '11:45 AM',
|
||||
'unread' => true,
|
||||
'flagged' => false,
|
||||
'attachments' => [],
|
||||
],
|
||||
[
|
||||
'id' => 102,
|
||||
'from_name' => 'Canva',
|
||||
'from_email' => 'design@canva.com',
|
||||
'subject' => 'Your design "Imail Presentation" is ready',
|
||||
'preview' => 'Collaborate with your team on your latest design for the Imail product launch.',
|
||||
'content' => '<p>Hey there!</p><p>Your team is waiting for your feedback on the <strong>Imail Presentation</strong> design.</p><p>Check the latest comments and approve the final version.</p>',
|
||||
'time' => '9:12 AM',
|
||||
'unread' => false,
|
||||
'flagged' => false,
|
||||
'attachments' => [
|
||||
['name' => 'presentation_v1.pdf', 'size' => '4.2 MB'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 103,
|
||||
'from_name' => 'Figma',
|
||||
'from_email' => 'notifications@figma.com',
|
||||
'subject' => 'Atul Kumar mentioned you in "Mobile App (Draft)"',
|
||||
'preview' => '"@idevakk take a look at the revised QR modal design, let me know if..."',
|
||||
'content' => '<p><strong>Atul Kumar</strong> mentioned you in a comment on <strong>Mobile App (Draft)</strong>:</p><blockquote>"@idevakk take a look at the revised QR modal design, let me know if the proportions look right to you."</blockquote><p>Reply in Figma or view the comment online.</p>',
|
||||
'time' => '8:30 AM',
|
||||
'unread' => false,
|
||||
'flagged' => true,
|
||||
'attachments' => [],
|
||||
],
|
||||
],
|
||||
2 => [
|
||||
[
|
||||
'id' => 2,
|
||||
'from_name' => 'Stripe',
|
||||
'from_email' => 'support@stripe.com',
|
||||
'subject' => 'Your weekly payment report',
|
||||
'preview' => 'Your weekly report for the period of Feb 24 - Mar 2 is now available.',
|
||||
'content' => '<p>Hello,</p><p>Your weekly report for the period of Feb 24 - Mar 2 is now available in your dashboard.</p><p>Total Volume: $12,450.00</p><p>View the full report details online.</p>',
|
||||
'time' => 'Yesterday',
|
||||
'unread' => false,
|
||||
'flagged' => false,
|
||||
'attachments' => [
|
||||
['name' => 'report_mar_02.pdf', 'size' => '1.2 MB'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 201,
|
||||
'from_name' => 'Postmark',
|
||||
'from_email' => 'alerts@postmarkapp.com',
|
||||
'subject' => 'Outbound volume spike detected',
|
||||
'preview' => 'We noticed a sudden increase in outbound emails from your "Production" server.',
|
||||
'content' => '<p>Alert: Outbound volume spike.</p><p>Server: Production</p><p>We detected 5,000+ emails sent in the last hour. Please ensure this is expected activity.</p>',
|
||||
'time' => 'Yesterday',
|
||||
'unread' => true,
|
||||
'flagged' => false,
|
||||
'attachments' => [],
|
||||
],
|
||||
[
|
||||
'id' => 202,
|
||||
'from_name' => 'Vercel',
|
||||
'from_email' => 'deployments@vercel.com',
|
||||
'subject' => 'Team "idevakk" deployment successful',
|
||||
'preview' => 'Production deployment for the imail-frontend project has completed.',
|
||||
'content' => '<p>Your deployment is live!</p><p>Project: imail-frontend</p><p>Command: <code>npm run build</code></p><p>View your deployment here: https://imail.app</p>',
|
||||
'time' => 'Mar 2',
|
||||
'unread' => false,
|
||||
'flagged' => false,
|
||||
'attachments' => [],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $emails[$this->currentMailboxId] ?? [
|
||||
[
|
||||
'id' => 3,
|
||||
'from_name' => 'Slack',
|
||||
'from_email' => 'notifications@slack.com',
|
||||
'subject' => 'You have 12 unread messages from your team',
|
||||
'preview' => 'Atul Kumar: "Did you check the new API endpoints? We need them for..."',
|
||||
'content' => '<p>You have new activity in Slack.</p><ul><li><strong>#dev-chat</strong>: 8 new messages</li><li><strong>#announcements</strong>: 4 new messages</li></ul>',
|
||||
'time' => 'Mar 1',
|
||||
'unread' => true,
|
||||
'flagged' => false,
|
||||
'attachments' => [],
|
||||
],
|
||||
[
|
||||
'id' => 301,
|
||||
'from_name' => 'Zoom',
|
||||
'from_email' => 'no-reply@zoom.us',
|
||||
'subject' => 'Meeting Reminder: "Sprint Planning"',
|
||||
'preview' => 'Your Sprint Planning meeting is scheduled to start in 15 minutes.',
|
||||
'content' => '<p>Friendly reminder that your Sprint Planning call starts soon.</p><p>Link: https://zoom.us/j/123456789</p>',
|
||||
'time' => 'Feb 28',
|
||||
'unread' => false,
|
||||
'flagged' => false,
|
||||
'attachments' => [],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function selectEmail($id)
|
||||
{
|
||||
$this->selectedEmailId = $id;
|
||||
}
|
||||
|
||||
public function switchMailbox($id)
|
||||
{
|
||||
$this->currentMailboxId = $id;
|
||||
$this->selectedEmailId = null;
|
||||
$this->search = '';
|
||||
}
|
||||
|
||||
public function createMailbox()
|
||||
{
|
||||
$newAddress = $this->createType === 'random'
|
||||
? fake()->userName().'_'.rand(10, 99).'@'.$this->availableDomains[array_rand($this->availableDomains)]
|
||||
: $this->customUsername.'@'.$this->customDomain;
|
||||
|
||||
$newId = count($this->activeMailboxes) + 1;
|
||||
$this->activeMailboxes[] = [
|
||||
'id' => $newId,
|
||||
'address' => $newAddress,
|
||||
'expires_at' => '24:00:00',
|
||||
'progress' => 100,
|
||||
];
|
||||
|
||||
$this->currentMailboxId = $newId;
|
||||
$this->showCreateModal = false;
|
||||
$this->customUsername = '';
|
||||
}
|
||||
|
||||
public function deleteMailbox($id)
|
||||
{
|
||||
$this->activeMailboxes = array_filter($this->activeMailboxes, fn ($m) => $m['id'] !== $id);
|
||||
|
||||
if ($this->currentMailboxId === $id) {
|
||||
$this->currentMailboxId = count($this->activeMailboxes) > 0 ? reset($this->activeMailboxes)['id'] : null;
|
||||
$this->selectedEmailId = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function downloadEmail($id)
|
||||
{
|
||||
// Mock download logic
|
||||
$this->js("alert('Downloading email #{$id}... (Mock Action)')");
|
||||
}
|
||||
|
||||
public function printEmail($id)
|
||||
{
|
||||
// Mock print logic
|
||||
$this->js('window.print()');
|
||||
}
|
||||
|
||||
public function deleteEmail($id)
|
||||
{
|
||||
// Mock delete logic
|
||||
$this->js("alert('Email #{$id} deleted successfully! (Mock Action)')");
|
||||
$this->selectedEmailId = null;
|
||||
}
|
||||
|
||||
public function nextPage()
|
||||
{
|
||||
if ($this->page < $this->totalPages) {
|
||||
$this->page++;
|
||||
}
|
||||
}
|
||||
|
||||
public function previousPage()
|
||||
{
|
||||
if ($this->page > 1) {
|
||||
$this->page--;
|
||||
}
|
||||
}
|
||||
|
||||
public function generateQrCode($address)
|
||||
{
|
||||
// Mock QR generation with a slight delay
|
||||
usleep(800000); // 800ms
|
||||
$this->dispatch('qrCodeGenerated', address: $address);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$currentMailbox = collect($this->activeMailboxes)->firstWhere('id', $this->currentMailboxId);
|
||||
|
||||
return view('livewire.mailbox', [
|
||||
'emails' => $this->getEmailsProperty(),
|
||||
'currentMailbox' => $currentMailbox,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -25,14 +25,14 @@
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row items-center gap-4 opacity-0" x-init="gsap.to($el, {opacity: 1, duration: 1, delay: 0.4})">
|
||||
<button wire:click="generateEmail" class="group relative inline-flex items-center justify-center px-8 py-3.5 text-base font-semibold text-white bg-pink-600 rounded-full overflow-hidden transition-all hover:bg-pink-500 hover:shadow-[0_0_20px_rgba(236,72,153,0.5)]">
|
||||
<a href="/mailbox" class="group relative inline-flex items-center justify-center px-8 py-3.5 text-base font-semibold text-white bg-pink-600 rounded-full overflow-hidden transition-all hover:bg-pink-500 hover:shadow-[0_0_20px_rgba(236,72,153,0.5)]">
|
||||
<span class="relative flex items-center gap-2">
|
||||
Start building for free
|
||||
Create mailbox for free
|
||||
<svg class="w-4 h-4 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</a>
|
||||
<a href="/api-docs" class="inline-flex items-center justify-center px-8 py-3.5 text-base font-semibold text-white bg-white/5 border border-white/10 rounded-full transition-all hover:bg-white/10">
|
||||
Imail in 100 seconds <svg class="w-4 h-4 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
</a>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<!-- Right Side: CTA + Mobile Toggle -->
|
||||
<div class="flex items-center gap-4 z-50">
|
||||
<!-- CTA Button (Desktop & Mobile) -->
|
||||
<a href="#hero" class="relative inline-flex items-center justify-center px-5 py-2.5 text-sm font-semibold text-white bg-zinc-900 border border-zinc-700/80 rounded-full overflow-hidden transition-all group hover:border-pink-500 hover:shadow-[0_0_15px_rgba(236,72,153,0.4)]">
|
||||
<a href="/mailbox" class="relative inline-flex items-center justify-center px-5 py-2.5 text-sm font-semibold text-white bg-zinc-900 border border-zinc-700/80 rounded-full overflow-hidden transition-all group hover:border-pink-500 hover:shadow-[0_0_15px_rgba(236,72,153,0.4)]">
|
||||
<span class="absolute inset-0 w-full h-full transition-all duration-300 ease-out opacity-0 bg-gradient-to-r from-pink-500 to-purple-500 group-hover:opacity-20"></span>
|
||||
<span class="relative flex items-center gap-2 text-zinc-100 group-hover:text-white group-hover:drop-shadow-[0_0_8px_rgba(255,255,255,0.5)]">
|
||||
<span class="hidden sm:inline">Get Temporary Email</span>
|
||||
|
||||
@@ -1,5 +1,95 @@
|
||||
<x-layouts.app.sidebar :title="$title ?? null">
|
||||
<flux:main>
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark h-full">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ $title ?? 'Mailbox — Imail' }}</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=inter:400,500,600,700|jetbrains-mono:400,500" rel="stylesheet" />
|
||||
|
||||
<!-- Vite -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
|
||||
<!-- GSAP -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
|
||||
|
||||
<style>
|
||||
body { font-family: 'Inter', sans-serif; }
|
||||
.font-mono { font-family: 'JetBrains Mono', monospace; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-app-bg text-[#FAFAFA] antialiased selection:bg-[#EC4899]/30 h-full overflow-hidden"
|
||||
x-data="{
|
||||
toasts: [],
|
||||
addToast(msg, type = 'success') {
|
||||
const id = Date.now();
|
||||
this.toasts.push({ id, msg, type });
|
||||
setTimeout(() => {
|
||||
this.toasts = this.toasts.filter(t => t.id !== id);
|
||||
}, 4000);
|
||||
}
|
||||
}"
|
||||
@notify.window="addToast($event.detail.message, $event.detail.type)">
|
||||
|
||||
{{ $slot }}
|
||||
</flux:main>
|
||||
</x-layouts.app.sidebar>
|
||||
|
||||
<!-- Global Toast Notifications -->
|
||||
<div class="fixed bottom-6 right-6 z-[100] flex flex-col gap-3 pointer-events-none">
|
||||
<template x-for="toast in toasts" :key="toast.id">
|
||||
<div x-show="true"
|
||||
x-transition:enter="transition ease-out duration-500"
|
||||
x-transition:enter-start="opacity-0 translate-x-12 scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-x-0 scale-100"
|
||||
x-transition:leave="transition ease-in duration-300"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-90"
|
||||
class="pointer-events-auto min-w-[320px] p-4 rounded-2xl border backdrop-blur-xl shadow-2xl flex items-center gap-4 relative overflow-hidden group"
|
||||
:class="{
|
||||
'bg-emerald-500/10 border-emerald-500/20 text-emerald-100': toast.type === 'success',
|
||||
'bg-blue-500/10 border-blue-500/20 text-blue-100': toast.type === 'info',
|
||||
'bg-amber-500/10 border-amber-500/20 text-amber-100': toast.type === 'warning',
|
||||
'bg-rose-500/10 border-rose-500/20 text-rose-100': toast.type === 'danger'
|
||||
}">
|
||||
<!-- Background Glow -->
|
||||
<div class="absolute inset-0 opacity-20 group-hover:opacity-30 transition-opacity"
|
||||
:class="{
|
||||
'bg-emerald-500/10': toast.type === 'success',
|
||||
'bg-blue-400/10': toast.type === 'info',
|
||||
'bg-amber-400/10': toast.type === 'warning',
|
||||
'bg-rose-400/10': toast.type === 'danger'
|
||||
}"></div>
|
||||
|
||||
<!-- Icon -->
|
||||
<div class="w-10 h-10 rounded-xl flex items-center justify-center shrink-0 shadow-lg"
|
||||
:class="{
|
||||
'bg-emerald-500/20 text-emerald-400': toast.type === 'success',
|
||||
'bg-blue-500/20 text-blue-400': toast.type === 'info',
|
||||
'bg-amber-500/20 text-amber-400': toast.type === 'warning',
|
||||
'bg-rose-500/20 text-rose-400': toast.type === 'danger'
|
||||
}">
|
||||
<svg x-show="toast.type === 'success'" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7" /></svg>
|
||||
<svg x-show="toast.type === 'info'" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
<svg x-show="toast.type === 'warning'" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
||||
<svg x-show="toast.type === 'danger'" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-[10px] font-black uppercase tracking-[0.2em] opacity-40 mb-0.5" x-text="toast.type"></div>
|
||||
<div class="text-[11px] font-bold tracking-wide" x-text="toast.msg"></div>
|
||||
</div>
|
||||
|
||||
<button @click="toasts = toasts.filter(t => t.id !== toast.id)"
|
||||
class="p-1.5 rounded-lg hover:bg-white/5 text-zinc-600 hover:text-white transition-all">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Lightweight QR Code Library -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrious/4.0.2/qrious.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
600
resources/views/livewire/mailbox.blade.php
Normal file
600
resources/views/livewire/mailbox.blade.php
Normal file
@@ -0,0 +1,600 @@
|
||||
<div class="flex h-full bg-zinc-950 overflow-hidden relative"
|
||||
x-data="{
|
||||
sidebarOpen: window.innerWidth >= 1280,
|
||||
selectedId: @entangle('selectedEmailId'),
|
||||
mobileView: 'list',
|
||||
qrModal: false,
|
||||
qrAddress: '',
|
||||
generateQR(address) {
|
||||
this.qrAddress = address;
|
||||
this.qrModal = true;
|
||||
this.$nextTick(() => {
|
||||
new QRious({
|
||||
element: document.getElementById('qr-canvas'),
|
||||
value: address,
|
||||
size: 200,
|
||||
background: 'transparent',
|
||||
foreground: '#09090b',
|
||||
level: 'H'
|
||||
});
|
||||
});
|
||||
}
|
||||
}"
|
||||
x-init="$watch('selectedId', value => { if(value && window.innerWidth < 1024) mobileView = 'detail' })"
|
||||
@resize.window="if (window.innerWidth >= 1280) sidebarOpen = true">
|
||||
|
||||
<!-- Mobile Sidebar Backdrop -->
|
||||
<div x-show="sidebarOpen"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="sidebarOpen = false"
|
||||
class="fixed inset-0 bg-zinc-950/60 backdrop-blur-sm z-40 xl:hidden"></div>
|
||||
|
||||
<!-- Component Sidebar -->
|
||||
<aside class="fixed xl:relative inset-y-0 left-0 flex-shrink-0 bg-zinc-900/40 border-r border-white/5 flex flex-col transition-all duration-300 z-50 xl:z-30"
|
||||
:class="{
|
||||
'w-64 translate-x-0 shadow-2xl xl:shadow-none': sidebarOpen,
|
||||
'-translate-x-full xl:translate-x-0': !sidebarOpen,
|
||||
'xl:w-64': sidebarOpen && window.innerWidth >= 1280,
|
||||
'xl:w-20': !sidebarOpen && window.innerWidth >= 1280
|
||||
}">
|
||||
<!-- Sidebar Header -->
|
||||
<div class="h-16 flex items-center justify-between px-6 border-b border-white/5">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-pink-500 flex items-center justify-center text-white shadow-[0_0_15px_rgba(236,72,153,0.3)]">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg>
|
||||
</div>
|
||||
<span class="font-bold text-lg tracking-tight text-white transition-opacity" :class="sidebarOpen ? 'opacity-100' : 'opacity-0 hidden'">Imail</span>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Close Button -->
|
||||
<button @click="sidebarOpen = false" class="xl:hidden p-1.5 rounded-lg hover:bg-white/5 text-zinc-500 hover:text-white transition-all">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar Content -->
|
||||
<div class="flex-1 overflow-y-auto py-6 px-4 space-y-8 scrollbar-hide">
|
||||
<!-- Navigation -->
|
||||
<nav class="space-y-1">
|
||||
<button @click="$wire.set('activeFolder', 'inbox')"
|
||||
class="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all group"
|
||||
:class="$wire.activeFolder === 'inbox' ? 'bg-pink-500/10 text-pink-500 border border-pink-500/20 shadow-[0_0_20px_rgba(236,72,153,0.05)]' : 'text-zinc-500 hover:text-zinc-300 hover:bg-white/5'">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" /></svg>
|
||||
<span class="font-medium text-sm" :class="sidebarOpen ? 'opacity-100' : 'opacity-0 hidden'">Inbox</span>
|
||||
<span class="ml-auto text-[10px] font-bold px-1.5 py-0.5 rounded bg-pink-500/20 text-pink-500" :class="sidebarOpen ? 'opacity-100' : 'opacity-0 hidden'">1</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<!-- Active Mailbox Control -->
|
||||
<div :class="sidebarOpen ? 'opacity-100' : 'opacity-0 hidden'">
|
||||
@if($currentMailbox)
|
||||
<div class="p-4 rounded-2xl bg-zinc-900 border border-white/5 relative overflow-hidden group mb-4">
|
||||
<div class="absolute top-0 right-0 w-24 h-24 bg-pink-500/5 rounded-full blur-2xl -z-10"></div>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></div>
|
||||
<span class="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">Active Mailbox</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button @click="generateQR('{{ $currentMailbox['address'] }}')"
|
||||
class="p-1.5 rounded-lg bg-white/5 text-zinc-500 hover:text-white hover:bg-white/10 transition-all"
|
||||
title="QR Code">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="3" width="7" height="7" /><rect x="14" y="3" width="7" height="7" /><rect x="14" y="14" width="7" height="7" /><rect x="3" y="14" width="7" height="7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button @click="navigator.clipboard.writeText('{{ $currentMailbox['address'] }}'); addToast('Address copied to clipboard', 'success')"
|
||||
class="p-1.5 rounded-lg bg-white/5 text-zinc-500 hover:text-white hover:bg-white/10 transition-all"
|
||||
title="Copy Address">
|
||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg>
|
||||
</button>
|
||||
<button wire:click="deleteMailbox({{ $currentMailbox['id'] }})"
|
||||
wire:confirm="Are you sure you want to delete this session? All emails will be lost."
|
||||
class="p-1.5 rounded-lg bg-rose-500/10 text-rose-500/60 hover:text-rose-500 hover:bg-rose-500/20 transition-all"
|
||||
title="Delete Session">
|
||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[11px] font-mono text-white break-all mb-4">{{ $currentMailbox['address'] }}</div>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between text-[10px]">
|
||||
<span class="text-zinc-500 uppercase font-black tracking-tighter">Expires In</span>
|
||||
<span class="text-pink-500 font-mono">{{ $currentMailbox['expires_at'] }}</span>
|
||||
</div>
|
||||
<div class="h-1 bg-white/5 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-gradient-to-r from-pink-500 to-emerald-500" style="width: {{ $currentMailbox['progress'] }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Controls Layout -->
|
||||
<div class="space-y-6">
|
||||
<!-- Create New Button -->
|
||||
<button @click="$wire.set('showCreateModal', true)"
|
||||
class="w-full py-3 px-4 rounded-2xl bg-white/5 border border-white/10 text-white text-xs font-bold flex items-center justify-center gap-2 hover:bg-white/10 hover:border-pink-500/30 transition-all group">
|
||||
<div class="w-5 h-5 rounded-lg bg-pink-500/20 text-pink-500 flex items-center justify-center group-hover:scale-110 transition-transform">
|
||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 4.5v15m7.5-7.5h-15" /></svg>
|
||||
</div>
|
||||
Create New Mailbox
|
||||
</button>
|
||||
|
||||
<!-- Active List -->
|
||||
<div class="space-y-3">
|
||||
<h4 class="text-[10px] font-bold text-zinc-600 uppercase tracking-[0.2em] px-3 mb-4">Your Sessions</h4>
|
||||
<div class="space-y-2 max-h-48 overflow-y-auto pr-1 scrollbar-hide">
|
||||
@foreach($activeMailboxes as $mailbox)
|
||||
@if($mailbox['id'] !== $currentMailboxId)
|
||||
<button wire:click="switchMailbox({{ $mailbox['id'] }})"
|
||||
class="w-full p-3 rounded-xl bg-zinc-900/40 border border-white/5 text-left group hover:border-white/20 transition-all flex items-center justify-between">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-[10px] font-mono text-zinc-400 truncate group-hover:text-white transition-colors">{{ $mailbox['address'] }}</div>
|
||||
<div class="text-[9px] text-zinc-600 font-bold uppercase mt-1">{{ $mailbox['expires_at'] }} remaining</div>
|
||||
</div>
|
||||
<svg class="w-4 h-4 text-zinc-700 group-hover:text-pink-500 translate-x-2 opacity-0 group-hover:translate-x-0 group-hover:opacity-100 transition-all" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
|
||||
</button>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Sidebar Footer -->
|
||||
<a href="{{ auth()->check() ? route('settings.profile') : route('login') }}"
|
||||
class="p-4 border-t border-white/5 flex items-center gap-4 hover:bg-white/5 transition-all group">
|
||||
<div class="w-10 h-10 rounded-full bg-zinc-800 border border-white/10 flex items-center justify-center text-zinc-400 group-hover:border-pink-500/30 group-hover:bg-pink-500/10 group-hover:text-pink-500 transition-all">
|
||||
@auth
|
||||
<span class="text-xs font-bold">{{ auth()->user()->initials() }}</span>
|
||||
@else
|
||||
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" /></svg>
|
||||
@endauth
|
||||
</div>
|
||||
<div class="flex-1 min-w-0" :class="sidebarOpen ? 'opacity-100' : 'opacity-0 hidden'">
|
||||
<div class="text-xs font-bold text-white truncate group-hover:text-pink-500 transition-colors">
|
||||
@auth
|
||||
{{ auth()->user()->name ?? auth()->user()->email }}
|
||||
@else
|
||||
Anonymous
|
||||
@endauth
|
||||
</div>
|
||||
<div class="text-[9px] text-zinc-500 uppercase font-black tracking-tighter">
|
||||
@auth
|
||||
{{ auth()->user()->hasRole('admin') ? 'ADMIN USER' : (auth()->user()->hasRole('premium') ? 'PREMIUM USER' : 'FREE USER') }}
|
||||
@else
|
||||
GUEST USER
|
||||
@endauth
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</aside>
|
||||
|
||||
<!-- Email List Column -->
|
||||
<div class="w-full lg:w-96 flex-shrink-0 bg-app-bg border-r border-white/5 flex flex-col z-20 transition-all duration-300"
|
||||
:class="{'hidden lg:flex': mobileView === 'detail' && selectedId}">
|
||||
|
||||
<!-- List Header -->
|
||||
<div class="h-16 flex items-center px-4 border-b border-white/5 bg-zinc-950/20 backdrop-blur-3xl sticky top-0 z-10 gap-3">
|
||||
<!-- Sidebar Toggle (Mobile/Tablet) -->
|
||||
<button @click="sidebarOpen = !sidebarOpen"
|
||||
class="xl:hidden p-2 rounded-lg bg-white/5 border border-white/5 text-zinc-400 hover:text-white transition-all">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="flex-1 relative group">
|
||||
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500 group-focus-within:text-pink-500 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
|
||||
<input type="text" placeholder="Search mail..."
|
||||
class="w-full h-10 bg-white/5 border border-white/5 rounded-xl pl-10 pr-4 text-sm focus:outline-none focus:ring-1 focus:ring-pink-500/30 focus:border-pink-500/20 transition-all placeholder:text-zinc-600">
|
||||
</div>
|
||||
<button wire:click="$refresh()"
|
||||
class="w-10 h-10 rounded-xl bg-white/5 border border-white/5 flex items-center justify-center text-zinc-400 hover:text-white hover:bg-white/10 transition-all group overflow-hidden"
|
||||
x-data="{ refreshing: false }"
|
||||
@click="refreshing = true; setTimeout(() => refreshing = false, 750)">
|
||||
<svg class="w-5 h-5"
|
||||
:class="{ 'animate-cinematic-spin': refreshing }"
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- List Content -->
|
||||
<div class="flex-1 overflow-y-auto divide-y divide-white/5 scrollbar-hide">
|
||||
@foreach($emails as $email)
|
||||
<div @click="$wire.selectEmail({{ $email['id'] }}); mobileView = 'detail'"
|
||||
class="p-5 cursor-pointer transition-all relative group"
|
||||
:class="selectedId === {{ $email['id'] }} ? 'bg-pink-500/5' : 'hover:bg-white/[0.02]'">
|
||||
<!-- Active Indicator -->
|
||||
<div class="absolute left-0 top-0 bottom-0 w-1 bg-pink-500 transition-transform duration-300"
|
||||
:class="selectedId === {{ $email['id'] }} ? 'scale-y-100' : 'scale-y-0'"></div>
|
||||
|
||||
<div class="flex items-start justify-between gap-4 mb-2">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
@if($email['unread'])
|
||||
<div class="w-2 h-2 rounded-full bg-pink-500 shadow-[0_0_8px_rgba(236,72,153,0.5)] flex-shrink-0"></div>
|
||||
@endif
|
||||
<h4 class="text-sm font-bold truncate transition-colors" :class="selectedId === {{ $email['id'] }} ? 'text-white' : 'text-zinc-200 group-hover:text-white'">
|
||||
{{ $email['from_name'] }}
|
||||
</h4>
|
||||
</div>
|
||||
<span class="text-[10px] font-bold text-zinc-600 uppercase whitespace-nowrap">{{ $email['time'] }}</span>
|
||||
</div>
|
||||
|
||||
<div class="text-xs font-semibold text-zinc-300 mb-2 truncate group-hover:text-zinc-100 transition-colors">
|
||||
{{ $email['subject'] }}
|
||||
</div>
|
||||
|
||||
<p class="text-[11px] text-zinc-500 line-clamp-2 leading-relaxed font-medium">
|
||||
{{ $email['preview'] }}
|
||||
</p>
|
||||
|
||||
@if(count($email['attachments']) > 0)
|
||||
<div class="mt-3 flex items-center gap-2">
|
||||
<div class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-white/5 border border-white/5 text-[9px] font-mono text-zinc-400">
|
||||
<svg class="w-3 h-3 text-zinc-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"></path></svg>
|
||||
{{ $email['attachments'][0]['name'] }}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<!-- Sticky Pagination -->
|
||||
<div class="h-14 flex items-center justify-between px-4 border-t border-white/5 bg-zinc-950/40 backdrop-blur-xl shrink-0">
|
||||
<button wire:click="previousPage"
|
||||
class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/5 border border-white/5 text-[10px] font-bold text-zinc-400 hover:text-white hover:bg-white/10 transition-all disabled:opacity-30 disabled:pointer-events-none uppercase tracking-widest"
|
||||
{{ $page <= 1 ? 'disabled' : '' }}>
|
||||
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /></svg>
|
||||
Prev
|
||||
</button>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-[10px] font-black text-white/90 uppercase tracking-[0.2em]">{{ $page }}</span>
|
||||
<span class="text-[10px] font-bold text-zinc-600 uppercase tracking-widest">/</span>
|
||||
<span class="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">{{ $totalPages }}</span>
|
||||
</div>
|
||||
|
||||
<button wire:click="nextPage"
|
||||
class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/5 border border-white/5 text-[10px] font-bold text-zinc-400 hover:text-white hover:bg-white/10 transition-all disabled:opacity-30 disabled:pointer-events-none uppercase tracking-widest"
|
||||
{{ $page >= $totalPages ? 'disabled' : '' }}>
|
||||
Next
|
||||
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- QR Code Modal -->
|
||||
<div x-show="qrModal"
|
||||
class="fixed inset-0 z-[100] flex items-center justify-center p-4 lg:p-8"
|
||||
x-cloak>
|
||||
<div x-show="qrModal"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="qrModal = false"
|
||||
class="absolute inset-0 bg-zinc-950/80 backdrop-blur-xl"></div>
|
||||
|
||||
<div x-show="qrModal"
|
||||
x-transition:enter="transition ease-out duration-500"
|
||||
x-transition:enter-start="opacity-0 scale-95 translate-y-8"
|
||||
x-transition:enter-end="opacity-100 scale-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-300"
|
||||
x-transition:leave-start="opacity-100 scale-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 scale-95 translate-y-8"
|
||||
class="w-full max-w-[320px] bg-zinc-900 border border-white/10 rounded-[32px] p-6 relative overflow-hidden shadow-2xl">
|
||||
|
||||
<!-- Glow Background -->
|
||||
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-48 h-48 bg-pink-500/10 rounded-full blur-[60px] -z-10"></div>
|
||||
|
||||
<div class="text-center">
|
||||
<div class="w-12 h-12 rounded-xl bg-pink-500/10 text-pink-500 flex items-center justify-center mx-auto mb-4">
|
||||
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="3" width="7" height="7" /><rect x="14" y="3" width="7" height="7" /><rect x="14" y="14" width="7" height="7" /><rect x="3" y="14" width="7" height="7" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-black text-white mb-1 tracking-tight uppercase">Mailbox QR</h3>
|
||||
<p class="text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-6">Scan to access</p>
|
||||
|
||||
<div class="p-4 bg-white rounded-2xl mb-6 flex justify-center shadow-inner inline-block">
|
||||
<canvas id="qr-canvas"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="p-3 rounded-xl bg-zinc-950/50 border border-white/5 font-mono text-[9px] text-zinc-400 break-all mb-6" x-text="qrAddress"></div>
|
||||
|
||||
<button @click="qrModal = false"
|
||||
class="w-full py-3.5 rounded-xl bg-white text-zinc-950 font-black text-[10px] uppercase tracking-[0.2em] hover:bg-zinc-100 transition-all">
|
||||
Dismiss
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Detail Column -->
|
||||
<main class="flex-1 flex flex-col min-w-0 bg-zinc-950/50 backdrop-blur-md relative z-10 transition-all duration-300"
|
||||
:class="{'hidden lg:flex': mobileView === 'list' || !selectedId}">
|
||||
|
||||
@php $currentEmail = $selectedEmailId ? collect($emails)->firstWhere('id', $selectedEmailId) : null; @endphp
|
||||
@if($currentEmail)
|
||||
|
||||
<!-- Detail Header -->
|
||||
<div class="h-16 flex items-center px-6 border-b border-white/5 bg-zinc-950/20 backdrop-blur-3xl sticky top-0 z-10">
|
||||
<button @click="mobileView = 'list'" class="lg:hidden mr-4 p-2 rounded-lg bg-white/5 border border-white/5 text-zinc-400">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /></svg>
|
||||
</button>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<button wire:click="downloadEmail({{ $selectedEmailId }})"
|
||||
class="p-2 rounded-xl bg-white/5 border border-white/5 text-zinc-400 hover:text-white hover:bg-white/10 transition-all group" title="Download Email">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>
|
||||
</button>
|
||||
<button wire:click="printEmail({{ $selectedEmailId }})"
|
||||
class="p-2 rounded-xl bg-white/5 border border-white/5 text-zinc-400 hover:text-white hover:bg-white/10 transition-all group" title="Print Email">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2m8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="h-6 w-px bg-white/5"></div>
|
||||
<button wire:click="deleteEmail({{ $selectedEmailId }})"
|
||||
class="p-2 rounded-xl bg-white/5 border border-white/5 text-zinc-400 hover:text-rose-500 hover:bg-rose-500/10 transition-all" title="Delete Email">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detail Content -->
|
||||
<div class="flex-1 overflow-y-auto p-8 lg:p-12 scrollbar-hide">
|
||||
<div class="max-w-3xl">
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-12">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-pink-500 to-purple-600 flex items-center justify-center text-xl font-bold text-white shadow-xl">
|
||||
{{ substr($currentEmail['from_name'], 0, 1) }}
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-white mb-1">{{ $currentEmail['from_name'] }}</h2>
|
||||
<div class="text-xs font-mono text-zinc-500 uppercase tracking-widest">{{ $currentEmail['from_email'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[10px] font-bold text-zinc-600 uppercase tracking-widest px-3 py-1.5 rounded-full border border-white/5 bg-white/5">
|
||||
Received {{ $currentEmail['time'] }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="text-2xl md:text-3xl font-black tracking-tight text-white mb-8 leading-tight">
|
||||
{{ $currentEmail['subject'] }}
|
||||
</h1>
|
||||
|
||||
<div class="prose prose-invert max-w-none text-zinc-400 leading-relaxed space-y-4 font-medium mb-12">
|
||||
{!! $currentEmail['content'] !!}
|
||||
</div>
|
||||
|
||||
@if(count($currentEmail['attachments']) > 0)
|
||||
<div class="mt-12 pt-8 border-t border-white/5">
|
||||
<h4 class="text-[10px] font-bold text-white uppercase tracking-[0.2em] mb-4">Attachments ({{ count($currentEmail['attachments']) }})</h4>
|
||||
|
||||
<div class="relative group/attachments">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 @guest blur-[3px] pointer-events-none select-none grayscale opacity-60 @endguest">
|
||||
@foreach($currentEmail['attachments'] as $attachment)
|
||||
<div class="flex items-center justify-between p-4 rounded-2xl bg-white/5 border border-white/5 hover:border-pink-500/30 transition-all group">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-zinc-900 flex items-center justify-center text-pink-500">
|
||||
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs font-bold text-white truncate">{{ $attachment['name'] }}</div>
|
||||
<div class="text-[9px] font-mono text-zinc-500 uppercase">{{ $attachment['size'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@auth
|
||||
<button class="w-8 h-8 rounded-full bg-zinc-800 flex items-center justify-center text-zinc-400 hover:text-white transition-all">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>
|
||||
</button>
|
||||
@endauth
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@guest
|
||||
<div class="absolute inset-0 flex items-center justify-center z-20">
|
||||
<a href="{{ route('login') }}" class="group/btn relative px-6 py-3 rounded-2xl bg-pink-500 text-white font-bold text-xs uppercase tracking-widest shadow-[0_0_20px_rgba(236,72,153,0.3)] hover:scale-105 transition-all">
|
||||
<span class="relative z-10 flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
|
||||
Login to Download
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
@endguest
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<!-- Empty State -->
|
||||
<div class="flex-1 flex flex-col items-center justify-center p-12 text-center">
|
||||
<div class="w-24 h-24 rounded-3xl bg-zinc-900 border border-white/5 flex items-center justify-center text-zinc-700 mb-8 relative">
|
||||
<div class="absolute inset-0 bg-pink-500/5 rounded-3xl blur-2xl"></div>
|
||||
<svg class="w-12 h-12 relative z-10" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-white mb-3">Select an email to read</h3>
|
||||
<p class="text-zinc-500 max-w-xs text-sm leading-relaxed">Choose one of your disposable emails from the list to view its full content and secure attachments.</p>
|
||||
</div>
|
||||
@endif
|
||||
</main>
|
||||
<!-- Create Mailbox Modal -->
|
||||
<div x-show="$wire.showCreateModal"
|
||||
style="display: none;"
|
||||
x-data="{ tab: @entangle('createType') }"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="fixed inset-0 z-[100] flex items-center justify-center p-6 bg-zinc-950/80 backdrop-blur-sm">
|
||||
|
||||
<div @click.away="$wire.set('showCreateModal', false)"
|
||||
class="w-full max-w-md bg-zinc-900 border border-white/10 rounded-[32px] overflow-hidden shadow-2xl relative">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-pink-500/5 to-emerald-500/5 pointer-events-none"></div>
|
||||
|
||||
<div class="p-8 relative z-10">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-white mb-1">New Mailbox</h3>
|
||||
<p class="text-xs text-zinc-500 font-medium">Create a new secure disposable identity.</p>
|
||||
</div>
|
||||
<button @click="$wire.set('showCreateModal', false)" class="text-zinc-500 hover:text-white transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Type Selector -->
|
||||
<div class="flex p-1 bg-zinc-950 rounded-2xl mb-8 border border-white/5">
|
||||
<button @click="tab = 'random'"
|
||||
class="flex-1 py-3 text-xs font-bold rounded-xl transition-all duration-300"
|
||||
:class="tab === 'random' ? 'bg-zinc-800 text-white shadow-lg' : 'text-zinc-500 hover:text-zinc-300'">
|
||||
Random
|
||||
</button>
|
||||
<button @click="tab = 'custom'"
|
||||
class="flex-1 py-3 text-xs font-bold rounded-xl transition-all duration-300 flex items-center justify-center gap-2"
|
||||
:class="tab === 'custom' ? 'bg-zinc-800 text-white shadow-lg' : 'text-zinc-500 hover:text-zinc-300'">
|
||||
Custom
|
||||
@if(!auth()->check())
|
||||
<svg class="w-3.5 h-3.5 text-zinc-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
|
||||
@endif
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab Content Container -->
|
||||
<div class="grid grid-cols-1 grid-rows-1 min-h-[220px]">
|
||||
<div x-show="tab === 'custom'"
|
||||
x-transition:enter="transition ease-out duration-300 delay-150"
|
||||
x-transition:enter-start="opacity-0 translate-y-2"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2"
|
||||
wire:key="tab-custom"
|
||||
class="col-start-1 row-start-1 relative">
|
||||
|
||||
@auth
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="text-[10px] font-bold text-white uppercase tracking-widest mb-3 block">Username</label>
|
||||
<input type="text" wire:model="customUsername" placeholder="e.g. dev-test-01"
|
||||
class="w-full h-12 bg-zinc-950 border border-white/5 rounded-2xl px-4 text-sm focus:outline-none focus:ring-1 focus:ring-pink-500/50 transition-all font-mono">
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-[10px] font-bold text-white uppercase tracking-widest mb-3 block">Domain Selection</label>
|
||||
<div class="relative group">
|
||||
<select wire:model="customDomain"
|
||||
class="w-full h-12 bg-zinc-950 border border-white/5 rounded-2xl px-4 text-sm focus:outline-none focus:ring-1 focus:ring-pink-500/50 transition-all appearance-none text-zinc-300 cursor-pointer">
|
||||
@foreach($availableDomains as $domain)
|
||||
<option value="{{ $domain }}">@ {{ $domain }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<svg class="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="h-full min-h-[240px] flex flex-col items-center justify-center text-center p-8 bg-zinc-950 border border-white/5 rounded-[32px] relative overflow-hidden group">
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-pink-500/5 to-transparent opacity-50"></div>
|
||||
<div class="relative z-10 flex flex-col items-center">
|
||||
<div class="w-16 h-16 rounded-2xl bg-gradient-to-br from-pink-500 to-rose-600 flex items-center justify-center mb-6 shadow-[0_20px_50px_rgba(236,72,153,0.3)]">
|
||||
<svg class="w-8 h-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h4 class="text-lg font-black text-white mb-2 tracking-widest uppercase">Login Required</h4>
|
||||
<p class="text-[11px] text-zinc-500 leading-relaxed font-medium max-w-[220px]">Sign in to create custom aliases and use premium domains.</p>
|
||||
</div>
|
||||
</div>
|
||||
@endauth
|
||||
</div>
|
||||
|
||||
<div x-show="tab === 'random'"
|
||||
x-transition:enter="transition ease-out duration-300 delay-150"
|
||||
x-transition:enter-start="opacity-0 translate-y-2"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2"
|
||||
wire:key="tab-random"
|
||||
class="col-start-1 row-start-1 min-h-[240px] flex flex-col items-center justify-center text-center p-8 bg-zinc-950 border border-white/5 rounded-[32px] relative overflow-hidden group">
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-pink-500/5 to-transparent opacity-50"></div>
|
||||
<div class="relative z-10 flex flex-col items-center">
|
||||
<div class="w-16 h-16 rounded-2xl bg-gradient-to-br from-pink-500 to-rose-600 flex items-center justify-center mb-6 shadow-[0_20px_50px_rgba(236,72,153,0.3)] group-hover:scale-110 transition-transform duration-500">
|
||||
<svg class="w-8 h-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.456-2.454L18 2.25l.259 1.035a3.375 3.375 0 002.455 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h4 class="text-lg font-black text-white mb-2 tracking-widest uppercase">Random Identity</h4>
|
||||
<p class="text-[11px] text-zinc-500 leading-relaxed font-medium max-w-[220px]">We'll generate a secure, high-entropy unique address for you instantly.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Global Action / Login Button -->
|
||||
<div class="mt-8">
|
||||
@if(!auth()->check())
|
||||
<div x-show="tab === 'custom'">
|
||||
<a href="{{ route('login') }}" class="w-full py-4 rounded-2xl bg-white text-zinc-950 font-black text-sm uppercase tracking-widest hover:bg-zinc-100 hover:shadow-[0_0_30px_rgba(255,255,255,0.1)] transition-all flex items-center justify-center gap-3 relative z-10">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
|
||||
Login to Unlock
|
||||
</a>
|
||||
</div>
|
||||
<button wire:click="createMailbox"
|
||||
x-show="tab === 'random'"
|
||||
class="w-full py-4 rounded-2xl bg-pink-600 text-white font-black text-sm uppercase tracking-widest hover:bg-pink-500 hover:shadow-[0_0_30px_rgba(236,72,153,0.3)] transition-all flex items-center justify-center gap-3 relative z-10">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
||||
Generate Mailbox
|
||||
</button>
|
||||
@else
|
||||
<button wire:click="createMailbox"
|
||||
class="w-full py-4 rounded-2xl bg-pink-600 text-white font-black text-sm uppercase tracking-widest hover:bg-pink-500 hover:shadow-[0_0_30px_rgba(236,72,153,0.3)] transition-all flex items-center justify-center gap-3 relative z-10">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
||||
Generate Mailbox
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
@keyframes slide-right {
|
||||
from { opacity: 0; transform: translateX(-10px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
.animate-cinematic-spin {
|
||||
animation: cinematic-spin 0.75s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@keyframes cinematic-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
@@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Route;
|
||||
use Laravel\Fortify\Features;
|
||||
|
||||
Route::get('/', \App\Livewire\LandingPage::class)->name('home');
|
||||
Route::get('/mailbox', \App\Livewire\Mailbox::class)->name('mailbox');
|
||||
Route::get('/privacy-policy', \App\Livewire\PrivacyPolicy::class)->name('privacy');
|
||||
|
||||
Route::view('dashboard', 'dashboard')
|
||||
|
||||
Reference in New Issue
Block a user