diff --git a/.gitignore b/.gitignore
index 153daf0..18f84c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,5 @@ yarn-error.log
/.vscode
/.zed
/stripe.exe
+/public/tmp/attachments/
+/public/tmp/premium/
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..efeeb7b
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,9 @@
+RewriteEngine On
+RewriteCond %{HTTP_HOST} ^www.zemail.me [NC]
+RewriteRule ^(.*)$ https://zemail.me/$1 [L,R=301]
+
+
+ RewriteEngine On
+ RewriteCond %{REQUEST_URI} !^public
+ RewriteRule ^(.*)$ public/$1 [L]
+
diff --git a/app/Filament/Pages/Settings.php b/app/Filament/Pages/Settings.php
index ee5202f..1521e53 100644
--- a/app/Filament/Pages/Settings.php
+++ b/app/Filament/Pages/Settings.php
@@ -4,6 +4,7 @@ namespace App\Filament\Pages;
use App\Models\Setting;
use App\Models\ZEmail;
+use Artisan;
use Ddeboer\Imap\Server;
use Filament\Actions\Action;
use Filament\Forms\Components\Checkbox;
@@ -45,6 +46,8 @@ class Settings extends Page implements HasForms
$this->applyDefaults($imapSettings, [
'protocol' => 'imap',
'default_account' => 'default',
+ 'premium_protocol' => 'imap',
+ 'premium_default_account' => 'default',
]);
$this->applyDefaults($configurationSettings, [
@@ -56,6 +59,10 @@ class Settings extends Page implements HasForms
$transformMap = [
'domains' => 'domain',
'gmailUsernames' => 'gmailUsername',
+ 'premium_domains' => 'premium_domain',
+ 'premium_gmailUsernames' => 'premium_gmailUsername',
+ 'outlookUsernames' => 'outlookUsername',
+ 'premium_outlookUsernames' => 'premium_outlookUsername',
'forbidden_ids' => 'forbidden_id',
'blocked_domains' => 'blocked_domain',
];
@@ -145,7 +152,7 @@ class Settings extends Page implements HasForms
]),
- Section::make('Imap')
+ Section::make('Public Mailbox Imap')
->description('Enter your imap server')
->collapsed()
->schema([
@@ -164,6 +171,25 @@ class Settings extends Page implements HasForms
Checkbox::make('imap_settings.cc_check')->label('Check CC Field')->default(false)->helperText('If enabled, we will check the CC field as well while fetching mails.'),
]),
+ Section::make('Premium Mailbox Imap')
+ ->description('Enter your imap server')
+ ->collapsed()
+ ->schema([
+ TextInput::make('imap_settings.premium_host')->label('Hostname')->required(),
+ TextInput::make('imap_settings.premium_port')->label('Port')->required(),
+ Select::make('imap_settings.premium_encryption')->options([
+ 'none' => 'None',
+ 'ssl' => 'SSL',
+ 'tls' => 'TLS'
+ ]),
+ Checkbox::make('imap_settings.premium_validate_cert')->label('Validate Encryption Certificate')->default(false),
+ TextInput::make('imap_settings.premium_username')->label('Username')->required(),
+ TextInput::make('imap_settings.premium_password')->label('Password')->required(),
+ TextInput::make('imap_settings.premium_default_account')->label('Default Account')->placeholder('default'),
+ TextInput::make('imap_settings.premium_protocol')->label('Protocol')->placeholder('imap'),
+ Checkbox::make('imap_settings.premium_cc_check')->label('Check CC Field')->default(false)->helperText('If enabled, we will check the CC field as well while fetching mails.'),
+ ]),
+
Section::make('Configuration')
->description('Enter your configuration')
->collapsed()
@@ -230,6 +256,42 @@ class Settings extends Page implements HasForms
]),
]),
+ Section::make('Premium Domains & Gmail Usernames')
+ ->collapsed()
+ ->columns(2)
+ ->schema([
+ Repeater::make('configuration_settings.premium_domains')
+ ->statePath('configuration_settings.premium_domains')
+ ->columnSpan(1)
+ ->schema([
+ TextInput::make('premium_domain')->label('Premium Domain')->required()->maxLength(30),
+ ]),
+ Repeater::make('configuration_settings.premium_gmailUsernames')
+ ->statePath('configuration_settings.premium_gmailUsernames')
+ ->columnSpan(1)
+ ->schema([
+ TextInput::make('premium_gmailUsername')->label('Premium Gmail Username')->required()->maxLength(30),
+ ]),
+ ]),
+
+ Section::make('Public & Premium Outlook.com Usernames')
+ ->collapsed()
+ ->columns(2)
+ ->schema([
+ Repeater::make('configuration_settings.outlookUsernames')
+ ->statePath('configuration_settings.outlookUsernames')
+ ->columnSpan(1)
+ ->schema([
+ TextInput::make('outlookUsername')->label('Public Outlook Username')->required()->maxLength(30),
+ ]),
+ Repeater::make('configuration_settings.premium_outlookUsernames')
+ ->statePath('configuration_settings.premium_outlookUsernames')
+ ->columnSpan(1)
+ ->schema([
+ TextInput::make('premium_outlookUsername')->label('Premium Outlook Username')->required()->maxLength(30),
+ ]),
+ ]),
+
Section::make('Mailbox Configuration')
->collapsed()
->schema([
@@ -300,9 +362,31 @@ class Settings extends Page implements HasForms
Action::make('save')
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
->submit('save'),
+ Action::make('flushCache')
+ ->label(__('Flush Cache'))
+ ->color('danger')
+ ->icon('heroicon-m-no-symbol')
+ ->action('flushCache')
+ ->requiresConfirmation(),
];
}
+ public function flushCache(): void
+ {
+ try {
+ Artisan::call('cache:clear');
+ Notification::make()
+ ->title('Cache flushed successfully')
+ ->success()
+ ->send();
+ } catch (\Exception $e) {
+ Notification::make()
+ ->title('Error : '.$e->getMessage())
+ ->danger()
+ ->send();
+ }
+ }
+
public function save(): void
{
try {
@@ -318,14 +402,20 @@ class Settings extends Page implements HasForms
}
foreach ([
'protocol' => 'imap',
- 'default_account' => 'default'
+ 'default_account' => 'default',
+ 'premium_protocol' => 'imap',
+ 'premium_default_account' => 'default',
] as $key => $default) {
$data['imap_settings'][$key] ??= $default;
}
$pluckMap = [
'domains' => 'domain',
+ 'premium_domains' => 'premium_domain',
+ 'premium_gmailUsernames' => 'premium_gmailUsername',
'gmailUsernames' => 'gmailUsername',
+ 'outlookUsernames' => 'outlookUsername',
+ 'premium_outlookUsernames' => 'premium_outlookUsername',
'forbidden_ids' => 'forbidden_id',
'blocked_domains' => 'blocked_domain'
];
diff --git a/app/Filament/Resources/BlogResource.php b/app/Filament/Resources/BlogResource.php
index 66894b4..c1a162b 100644
--- a/app/Filament/Resources/BlogResource.php
+++ b/app/Filament/Resources/BlogResource.php
@@ -79,6 +79,7 @@ class BlogResource extends Resource
FileUpload::make('post_image')
->label('Custom Image (Optional)')
+ ->disk('public')
->directory('media/posts')
->columnSpan(4)
->preserveFilenames()
diff --git a/app/Filament/Resources/PlanResource.php b/app/Filament/Resources/PlanResource.php
new file mode 100644
index 0000000..686309c
--- /dev/null
+++ b/app/Filament/Resources/PlanResource.php
@@ -0,0 +1,101 @@
+schema([
+ Section::make('Plan Information')
+ ->description('Add a new plan')
+ ->schema([
+ TextInput::make('name')->label('Page Name')
+ ->required(),
+ TextInput::make('description'),
+ TextInput::make('product_id')->required(),
+ TextInput::make('pricing_id')->required(),
+ TextInput::make('price')->numeric()->required(),
+ TextInput::make('mailbox_limit')->numeric()->required(),
+ Select::make('monthly_billing')->options([
+ 1 => 'Monthly',
+ 0 => 'Yearly',
+ ])->default(1)->required(),
+ KeyValue::make('details')
+ ->label('Plan Details (Optional)')
+ ->keyPlaceholder('Name')
+ ->valuePlaceholder('Content')
+ ->reorderable(),
+ ]),
+ ]);
+ }
+
+ public static function table(Table $table): Table
+ {
+ return $table
+ ->columns([
+ TextColumn::make('name')->label('Name'),
+ TextColumn::make('product_id')->label('Product'),
+ TextColumn::make('pricing_id')->label('Pricing'),
+ TextColumn::make('price')->label('Price'),
+ BooleanColumn::make('monthly_billing')->label('Monthly Billing'),
+ ])
+ ->filters([
+ //
+ ])
+ ->actions([
+ Tables\Actions\EditAction::make(),
+ ])
+ ->bulkActions([
+ Tables\Actions\BulkActionGroup::make([
+ Tables\Actions\DeleteBulkAction::make(),
+ ]),
+ ]);
+ }
+
+ public static function getRelations(): array
+ {
+ return [
+ //
+ ];
+ }
+
+ public static function getPages(): array
+ {
+ return [
+ 'index' => Pages\ListPlans::route('/'),
+ 'create' => Pages\CreatePlan::route('/create'),
+ 'edit' => Pages\EditPlan::route('/{record}/edit'),
+ ];
+ }
+}
diff --git a/app/Filament/Resources/PlanResource/Pages/CreatePlan.php b/app/Filament/Resources/PlanResource/Pages/CreatePlan.php
new file mode 100644
index 0000000..1d9a4b2
--- /dev/null
+++ b/app/Filament/Resources/PlanResource/Pages/CreatePlan.php
@@ -0,0 +1,21 @@
+success()
+ ->title('Plan created')
+ ->body('Plan created successfully');
+ }
+}
diff --git a/app/Filament/Resources/PlanResource/Pages/EditPlan.php b/app/Filament/Resources/PlanResource/Pages/EditPlan.php
new file mode 100644
index 0000000..0da8091
--- /dev/null
+++ b/app/Filament/Resources/PlanResource/Pages/EditPlan.php
@@ -0,0 +1,32 @@
+getResource()::getUrl('index');
+ }
+
+ protected function getSavedNotification(): ?Notification
+ {
+ return Notification::make()
+ ->success()
+ ->title('Plan updated')
+ ->body('Plan updated successfully');
+ }
+}
diff --git a/app/Filament/Resources/PlanResource/Pages/ListPlans.php b/app/Filament/Resources/PlanResource/Pages/ListPlans.php
new file mode 100644
index 0000000..73ab76d
--- /dev/null
+++ b/app/Filament/Resources/PlanResource/Pages/ListPlans.php
@@ -0,0 +1,19 @@
+getUser()),
+ Stat::make('Paid Users', $this->getUserPaid()),
+ Stat::make('Logs Count', $this->getLogsCount()),
+ Stat::make('Total Mailbox', $this->getTotalMailbox()),
+ Stat::make('Emails Received', $this->getTotalEmailsReceived()),
+ ];
+ }
+ private function getUser(): int
+ {
+ return User::all()->count();
+ }
+ private function getUserPaid(): int
+ {
+ return DB::table('subscriptions')
+ ->where(['stripe_status' => 'active'])
+ ->distinct('user_id')
+ ->count('user_id');
+ }
+ private function getLogsCount(): int
+ {
+ return Log::all()->count();
+ }
+ private function getTotalMailbox(): int
+ {
+ return Meta::select('value')->where(['key' => 'email_ids_created'])->first()->value;
+ }
+ private function getTotalEmailsReceived(): int
+ {
+ return Meta::select('value')->where(['key' => 'messages_received'])->first()->value;
+ }
+}
diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php
index d9330fd..013778c 100644
--- a/app/Http/Controllers/AppController.php
+++ b/app/Http/Controllers/AppController.php
@@ -2,8 +2,10 @@
namespace App\Http\Controllers;
+use App\Models\Premium;
use App\Models\ZEmail;
use Illuminate\Http\Request;
+use Session;
class AppController extends Controller
{
@@ -48,6 +50,33 @@ class AppController extends Controller
}
}
+ public function switchP($email) {
+ if (Session::get('isInboxTypePremium')) {
+ Premium::setEmailP($email);
+ } else {
+ ZEmail::setEmail($email);
+ }
+ if (json_decode(config('app.settings.configuration_settings'))->disable_mailbox_slug) {
+ return redirect()->route('dashboard');
+ }
+ return redirect()->route('dashboard.premium');
+ }
+
+ public function deleteP($email = null) {
+ if ($email) {
+ if (Session::get('isInboxTypePremium')) {
+ $emails = Premium::getEmails();
+ Premium::removeEmail($email);
+ } else {
+ $emails = ZEmail::getEmails();
+ ZEmail::removeEmail($email);
+ }
+ return redirect()->route('dashboard.premium');
+ } else {
+ return redirect()->route('dashboard');
+ }
+ }
+
public function locale($locale) {
if (in_array($locale, config('app.locales'))) {
session(['locale' => $locale]);
diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php
new file mode 100644
index 0000000..a300bfa
--- /dev/null
+++ b/app/Http/Controllers/Auth/VerifyEmailController.php
@@ -0,0 +1,30 @@
+user()->hasVerifiedEmail()) {
+ return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
+ }
+
+ if ($request->user()->markEmailAsVerified()) {
+ /** @var \Illuminate\Contracts\Auth\MustVerifyEmail $user */
+ $user = $request->user();
+
+ event(new Verified($user));
+ }
+
+ return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
+ }
+}
diff --git a/app/Listeners/StripeEventListener.php b/app/Listeners/StripeEventListener.php
new file mode 100644
index 0000000..3c4e568
--- /dev/null
+++ b/app/Listeners/StripeEventListener.php
@@ -0,0 +1,37 @@
+payload['type'] === 'invoice.payment_succeeded') {
+ session()->flash('alert', ['type' => 'success', 'message' => 'Payment completed successfully.']);
+ Log::info('Payment succeeded');
+ }
+
+ if ($event->payload['type'] === 'customer.subscription.deleted') {
+ session()->flash('alert', ['type' => 'error', 'message' => 'Subscription canceled.']);
+ Log::info('Subscription canceled');
+ }
+ }
+}
diff --git a/app/Livewire/Actions/Logout.php b/app/Livewire/Actions/Logout.php
new file mode 100644
index 0000000..45993bb
--- /dev/null
+++ b/app/Livewire/Actions/Logout.php
@@ -0,0 +1,22 @@
+logout();
+
+ Session::invalidate();
+ Session::regenerateToken();
+
+ return redirect('/');
+ }
+}
diff --git a/app/Livewire/AddOn.php b/app/Livewire/AddOn.php
new file mode 100644
index 0000000..31efd78
--- /dev/null
+++ b/app/Livewire/AddOn.php
@@ -0,0 +1,183 @@
+ 'checkIfAnyMessage'];
+
+ public function checkIfAnyMessage()
+ {
+ $email = ZEmail::getEmail();
+ $messages = ZEmail::getMessages($email);
+ if (count($messages['data']) > 0) {
+ return redirect()->route('mailbox');
+ }
+ }
+
+ public function mount()
+ {
+ $this->faqs = json_decode(file_get_contents(public_path('addOnFAQs.json')), true) ?? [];
+ $route = request()->route()->getName();
+ Session::put('addOn-route', $this->route);
+ if ($route == 'disposable-email') {
+ $this->title = 'Free Disposable Email | Temporary & Anonymous Email Service';
+ $this->description = 'Use our free disposable email service to stay private and spam-free. Instantly create a secure, anonymous, and temporary email address—no registration needed.';
+ $this->keywords = 'disposable email, free disposable email, temporary email, anonymous disposable email, temporary disposable email, secure disposable email, fake email, temp inbox, throwaway email';
+ $this->route = $route;
+ $this->faqs = $this->faqs[$route];
+ $this->faqSchema = [
+ "@context" => "https://schema.org",
+ "@type" => "FAQPage",
+ "mainEntity" => collect($this->faqs)->map(function ($faq) {
+ return [
+ "@type" => "Question",
+ "name" => $faq['title'],
+ "acceptedAnswer" => [
+ "@type" => "Answer",
+ "text" => $faq['content']
+ ]
+ ];
+ })->toArray()
+ ];
+ } elseif ($route == 'disposable-gmail') {
+ $this->title = 'Disposable Gmail Email | Free Temporary Gmail Inbox Online';
+ $this->description = 'Create a free disposable Gmail email address. Use a temporary Gmail inbox for sign-ups, testing, or staying spam-free. No login or registration required.';
+ $this->keywords = 'disposable Gmail, temporary Gmail inbox, Gmail disposable email, free disposable Gmail email address, Gmail temp email, anonymous Gmail email';
+ $this->route = $route;
+ $this->faqs = $this->faqs[$route];
+ $this->faqSchema = [
+ "@context" => "https://schema.org",
+ "@type" => "FAQPage",
+ "mainEntity" => collect($this->faqs)->map(function ($faq) {
+ return [
+ "@type" => "Question",
+ "name" => $faq['title'],
+ "acceptedAnswer" => [
+ "@type" => "Answer",
+ "text" => $faq['content']
+ ]
+ ];
+ })->toArray()
+ ];
+
+ } elseif ($route == 'disposable-outlook') {
+ $this->title = 'Disposable Outlook Email | Free Temporary Outlook Inbox';
+ $this->description = 'Get a free disposable Outlook email address instantly. Use a temporary Outlook inbox to avoid spam and protect your main account—no registration required.';
+ $this->keywords = 'disposable Outlook, Outlook disposable email, temporary Outlook inbox, free Outlook temp email, anonymous Outlook address, Outlook email for sign-ups';
+ $this->route = $route;
+ $this->faqs = $this->faqs[$route];
+ $this->faqSchema = [
+ "@context" => "https://schema.org",
+ "@type" => "FAQPage",
+ "mainEntity" => collect($this->faqs)->map(function ($faq) {
+ return [
+ "@type" => "Question",
+ "name" => $faq['title'],
+ "acceptedAnswer" => [
+ "@type" => "Answer",
+ "text" => $faq['content']
+ ]
+ ];
+ })->toArray()
+ ];
+ } elseif ($route == 'disposable-yahoo') {
+ $this->title = 'Disposable Yahoo Mail | Free Temporary Yahoo Inbox';
+ $this->description = 'Create a disposable Yahoo Mail address instantly. Use a free, temporary Yahoo inbox to stay private, filter spam, and protect your real Yahoo account.';
+ $this->keywords = 'disposable Yahoo mail, Yahoo mail disposable email, temporary Yahoo inbox, free Yahoo temp email, Yahoo throwaway email';
+ $this->route = $route;
+ $this->faqs = $this->faqs[$route];
+ $this->faqSchema = [
+ "@context" => "https://schema.org",
+ "@type" => "FAQPage",
+ "mainEntity" => collect($this->faqs)->map(function ($faq) {
+ return [
+ "@type" => "Question",
+ "name" => $faq['title'],
+ "acceptedAnswer" => [
+ "@type" => "Answer",
+ "text" => $faq['content']
+ ]
+ ];
+ })->toArray()
+ ];
+ } elseif ($route == 'gmailnator') {
+ $this->title = 'Gmailnator - Free Temporary Gmail Email Address Generator';
+ $this->description = 'Use Gmailnator to get a disposable Gmail address instantly. Perfect for one-time use, signups, or testing. No account needed. Try Gmailnator now.';
+ $this->keywords = 'gmailnator, gmailnator temp mail, gmailnator gmail, free gmailnator, gmailnator email address, gmailnator online, gmail temp mail, gmail fake email';
+ $this->route = $route;
+ $this->faqs = $this->faqs[$route];
+ $this->faqSchema = [
+ "@context" => "https://schema.org",
+ "@type" => "FAQPage",
+ "mainEntity" => collect($this->faqs)->map(function ($faq) {
+ return [
+ "@type" => "Question",
+ "name" => $faq['title'],
+ "acceptedAnswer" => [
+ "@type" => "Answer",
+ "text" => $faq['content']
+ ]
+ ];
+ })->toArray()
+ ];
+ } elseif ($route == 'emailnator') {
+ $this->title = 'Emailnator - Free Disposable Temporary Email Service';
+ $this->description = 'Use Emailnator to create a free temporary email address instantly. No sign-up needed. Secure, fast, and spam-free inboxes for signups or testing.';
+ $this->keywords = 'emailnator, emailnator temp mail, temporary email, disposable email, free temp inbox, temp mail generator, receive email online';
+ $this->route = $route;
+ $this->faqs = $this->faqs[$route];
+ $this->faqSchema = [
+ "@context" => "https://schema.org",
+ "@type" => "FAQPage",
+ "mainEntity" => collect($this->faqs)->map(function ($faq) {
+ return [
+ "@type" => "Question",
+ "name" => $faq['title'],
+ "acceptedAnswer" => [
+ "@type" => "Answer",
+ "text" => $faq['content']
+ ]
+ ];
+ })->toArray()
+ ];
+ } elseif ($route == 'temp-gmail') {
+ $this->title = 'Temp Gmail | Free Temporary Gmail Inbox for Instant Use';
+ $this->description = 'Use Temp Gmail to get a temporary Gmail address fast. No registration. Secure, spam-free inboxes for signups, testing, or quick email verification.';
+ $this->keywords = 'temp gmail, temporary gmail, disposable gmail, gmail temp email, gmail fake email, temporary gmail address, gmail for verification';
+ $this->route = $route;
+ $this->faqs = $this->faqs[$route];
+ $this->faqSchema = [
+ "@context" => "https://schema.org",
+ "@type" => "FAQPage",
+ "mainEntity" => collect($this->faqs)->map(function ($faq) {
+ return [
+ "@type" => "Question",
+ "name" => $faq['title'],
+ "acceptedAnswer" => [
+ "@type" => "Answer",
+ "text" => $faq['content']
+ ]
+ ];
+ })->toArray()
+ ];
+ }
+
+ }
+
+ public function render()
+ {
+ return view('livewire.add-on');
+ }
+}
diff --git a/app/Livewire/Auth/ConfirmPassword.php b/app/Livewire/Auth/ConfirmPassword.php
new file mode 100644
index 0000000..9a89db0
--- /dev/null
+++ b/app/Livewire/Auth/ConfirmPassword.php
@@ -0,0 +1,37 @@
+validate([
+ 'password' => ['required', 'string'],
+ ]);
+
+ if (! Auth::guard('web')->validate([
+ 'email' => Auth::user()->email,
+ 'password' => $this->password,
+ ])) {
+ throw ValidationException::withMessages([
+ 'password' => __('auth.password'),
+ ]);
+ }
+
+ session(['auth.password_confirmed_at' => time()]);
+
+ $this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);
+ }
+}
diff --git a/app/Livewire/Auth/ForgotPassword.php b/app/Livewire/Auth/ForgotPassword.php
new file mode 100644
index 0000000..7f3b681
--- /dev/null
+++ b/app/Livewire/Auth/ForgotPassword.php
@@ -0,0 +1,27 @@
+validate([
+ 'email' => ['required', 'string', 'email'],
+ ]);
+
+ Password::sendResetLink($this->only('email'));
+
+ session()->flash('status', __('A reset link will be sent if the account exists.'));
+ }
+}
diff --git a/app/Livewire/Auth/Login.php b/app/Livewire/Auth/Login.php
new file mode 100644
index 0000000..9925f63
--- /dev/null
+++ b/app/Livewire/Auth/Login.php
@@ -0,0 +1,77 @@
+validate();
+
+ $this->ensureIsNotRateLimited();
+
+ if (! Auth::attempt(['email' => $this->email, 'password' => $this->password], $this->remember)) {
+ RateLimiter::hit($this->throttleKey());
+
+ throw ValidationException::withMessages([
+ 'email' => __('auth.failed'),
+ ]);
+ }
+
+ RateLimiter::clear($this->throttleKey());
+ Session::regenerate();
+
+ $this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);
+ }
+
+ /**
+ * Ensure the authentication request is not rate limited.
+ */
+ protected function ensureIsNotRateLimited(): void
+ {
+ if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
+ return;
+ }
+
+ event(new Lockout(request()));
+
+ $seconds = RateLimiter::availableIn($this->throttleKey());
+
+ throw ValidationException::withMessages([
+ 'email' => __('auth.throttle', [
+ 'seconds' => $seconds,
+ 'minutes' => ceil($seconds / 60),
+ ]),
+ ]);
+ }
+
+ /**
+ * Get the authentication rate limiting throttle key.
+ */
+ protected function throttleKey(): string
+ {
+ return Str::transliterate(Str::lower($this->email).'|'.request()->ip());
+ }
+}
diff --git a/app/Livewire/Auth/Register.php b/app/Livewire/Auth/Register.php
new file mode 100644
index 0000000..c32bc7a
--- /dev/null
+++ b/app/Livewire/Auth/Register.php
@@ -0,0 +1,43 @@
+validate([
+ 'name' => ['required', 'string', 'max:255'],
+ 'email' => ['required', 'string', 'lowercase', 'email:rfc,dns', 'max:255', 'indisposable', 'unique:'.User::class],
+ 'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()],
+ ]);
+
+ $validated['password'] = Hash::make($validated['password']);
+
+ event(new Registered(($user = User::create($validated))));
+
+ Auth::login($user);
+
+ $this->redirect(route('dashboard', absolute: false), navigate: true);
+ }
+}
diff --git a/app/Livewire/Auth/ResetPassword.php b/app/Livewire/Auth/ResetPassword.php
new file mode 100644
index 0000000..28b3940
--- /dev/null
+++ b/app/Livewire/Auth/ResetPassword.php
@@ -0,0 +1,76 @@
+token = $token;
+
+ $this->email = request()->string('email');
+ }
+
+ /**
+ * Reset the password for the given user.
+ */
+ public function resetPassword(): void
+ {
+ $this->validate([
+ 'token' => ['required'],
+ 'email' => ['required', 'string', 'email'],
+ 'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()],
+ ]);
+
+ // Here we will attempt to reset the user's password. If it is successful we
+ // will update the password on an actual user model and persist it to the
+ // database. Otherwise we will parse the error and return the response.
+ $status = Password::reset(
+ $this->only('email', 'password', 'password_confirmation', 'token'),
+ function ($user) {
+ $user->forceFill([
+ 'password' => Hash::make($this->password),
+ 'remember_token' => Str::random(60),
+ ])->save();
+
+ event(new PasswordReset($user));
+ }
+ );
+
+ // If the password was successfully reset, we will redirect the user back to
+ // the application's home authenticated view. If there is an error we can
+ // redirect them back to where they came from with their error message.
+ if ($status != Password::PasswordReset) {
+ $this->addError('email', __($status));
+
+ return;
+ }
+
+ Session::flash('status', __($status));
+
+ $this->redirectRoute('login', navigate: true);
+ }
+}
diff --git a/app/Livewire/Auth/VerifyEmail.php b/app/Livewire/Auth/VerifyEmail.php
new file mode 100644
index 0000000..ba11d73
--- /dev/null
+++ b/app/Livewire/Auth/VerifyEmail.php
@@ -0,0 +1,39 @@
+hasVerifiedEmail()) {
+ $this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);
+
+ return;
+ }
+
+ Auth::user()->sendEmailVerificationNotification();
+
+ Session::flash('status', 'verification-link-sent');
+ }
+
+ /**
+ * Log the current user out of the application.
+ */
+ public function logout(Logout $logout): void
+ {
+ $logout();
+
+ $this->redirect('/', navigate: true);
+ }
+}
diff --git a/app/Livewire/Dashboard/Bulk.php b/app/Livewire/Dashboard/Bulk.php
new file mode 100644
index 0000000..2740ad8
--- /dev/null
+++ b/app/Livewire/Dashboard/Bulk.php
@@ -0,0 +1,142 @@
+user()->subscribedToProduct(config('app.plans')[0]['product_id']);
+ Session::put('isSubscribed', $subscriptionCheck);
+ }
+ public function generateBulk()
+ {
+ $this->validate([
+ 'bulkCount' => 'required|integer|min:1|max:500',
+ ]);
+
+ if (count($this->bulkEmails) > 0) {
+ $this->bulkEmails = [];
+ }
+
+ for ($i = 0; $i < $this->bulkCount; $i++) {
+ $this->bulkEmails[] = $this->randomEmail();
+ }
+ }
+
+ public function downloadBulk()
+ {
+ // Ensure there's something to download
+ if (empty($this->bulkEmails) || !is_array($this->bulkEmails)) {
+ return;
+ }
+
+ $filename = 'bulk_emails_' . now()->format('Ymd_His') . '.txt';
+ $content = implode(PHP_EOL, $this->bulkEmails);
+
+ return response()->streamDownload(function () use ($content) {
+ echo $content;
+ }, $filename);
+ }
+
+ private function randomEmail(): string
+ {
+ $domain = $this->getRandomDomain();
+ if ($domain == "gmail.com" || $domain == "googlemail.com") {
+ $uname = $this->getRandomGmailUser();
+ $uname_len = strlen($uname);
+ $len_power = $uname_len - 1;
+ $combination = pow(2,$len_power);
+ $rand_comb = mt_rand(1,$combination);
+ $formatted = implode(' ',str_split($uname));
+ $uname_exp = explode(' ', $formatted);
+
+ $bin = intval("");
+ for($i=0; $i<$len_power; $i++) {
+ $bin .= mt_rand(0,1);
+ }
+ $bin = explode(' ', implode(' ',str_split(strval($bin))));
+
+ $email = "";
+ for($i=0; $i<$len_power; $i++) {
+ $email .= $uname_exp[$i];
+ if($bin[$i]) {
+ $email .= ".";
+ }
+ }
+ $email .= $uname_exp[$i];
+ $gmail_rand = mt_rand(1,10);
+ if($gmail_rand > 5) {
+ $email .= "@gmail.com";
+ } else {
+ $email .= "@googlemail.com";
+ }
+ return $email;
+ } else {
+ return $this->generateRandomUsername().'@'.$domain;
+ }
+ }
+ private function generateRandomUsername(): string
+ {
+ $start = json_decode(config('app.settings.configuration_settings'))->random_username_length_min ?? 0;
+ $end = json_decode(config('app.settings.configuration_settings'))->random_username_length_max ?? 0;
+ if ($start == 0 && $end == 0) {
+ return $this->generatePronounceableWord();
+ }
+ return $this->generatedRandomBetweenLength($start, $end);
+ }
+ private function generatedRandomBetweenLength($start, $end): string
+ {
+ $length = rand($start, $end);
+ return $this->generateRandomString($length);
+ }
+ private function getRandomDomain() {
+ $domains = json_decode(config('app.settings.configuration_settings'))->premium_domains ?? [];
+ $count = count($domains);
+ return $count > 0 ? $domains[rand(1, $count) - 1] : '';
+ }
+ private function getRandomGmailUser() {
+ $gmailusername = json_decode(config('app.settings.configuration_settings'))->premium_gmailUsernames ?? [];
+ $count = count($gmailusername);
+ return $count > 0 ? $gmailusername[rand(1, $count) - 1] : '';
+ }
+ private function generatePronounceableWord(): string
+ {
+ $c = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones
+ $v = 'aeiou'; //vowels
+ $a = $c . $v; //both
+ $random = '';
+ for ($j = 0; $j < 2; $j++) {
+ $random .= $c[rand(0, strlen($c) - 1)];
+ $random .= $v[rand(0, strlen($v) - 1)];
+ $random .= $a[rand(0, strlen($a) - 1)];
+ }
+ return $random;
+ }
+ private function generateRandomString($length = 10): string
+ {
+ $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
+ $charactersLength = strlen($characters);
+ $randomString = '';
+ for ($i = 0; $i < $length; $i++) {
+ $randomString .= $characters[rand(0, $charactersLength - 1)];
+ }
+ return $randomString;
+ }
+
+ public function render()
+ {
+ if (Session::get('isSubscribed')) {
+ return view('livewire.dashboard.bulk')->layout('components.layouts.dashboard');
+ } else {
+ return view('livewire.dashboard.not-subscribed')->layout('components.layouts.dashboard');
+ }
+ }
+}
diff --git a/app/Livewire/Dashboard/BulkGmail.php b/app/Livewire/Dashboard/BulkGmail.php
new file mode 100644
index 0000000..0debfe3
--- /dev/null
+++ b/app/Livewire/Dashboard/BulkGmail.php
@@ -0,0 +1,118 @@
+user()->subscribedToProduct(config('app.plans')[0]['product_id']);
+ Session::put('isSubscribed', $subscriptionCheck);
+ }
+
+ public function generateBulk(): void
+ {
+ $this->validate([
+ 'email' => [
+ 'required',
+ 'email',
+ 'regex:/^[^@\s]+@gmail\.com$/i',
+ ],
+ 'quantity' => 'required|integer|min:10|max:500',
+ ]);
+
+ if (count($this->bulkEmails) > 0) {
+ $this->bulkEmails = [];
+ }
+
+ $this->bulkEmails = $this->generateDotVariants(explode("@", $this->email)[0], $this->quantity);
+ }
+
+ public function downloadBulk()
+ {
+ // Ensure there's something to download
+ if (empty($this->bulkEmails) || !is_array($this->bulkEmails)) {
+ return;
+ }
+
+ $filename = 'bulk_gmails_' . now()->format('Ymd_His') . '.txt';
+ $content = implode(PHP_EOL, $this->bulkEmails);
+
+ return response()->streamDownload(function () use ($content) {
+ echo $content;
+ }, $filename);
+ }
+
+ private function generateDotVariants($uname, $quantity)
+ {
+ $length = strlen($uname);
+ $positions = range(1, $length - 1);
+ $results = [];
+
+ // From 1 dot up to max possible
+ for ($dotCount = 1; $dotCount < $length; $dotCount++) {
+ $combinations = $this->combinations($positions, $dotCount);
+
+ // Reverse each combination for right-to-left priority
+ foreach (array_reverse($combinations) as $combo) {
+ $dotted = '';
+ $lastPos = 0;
+
+ foreach ($combo as $pos) {
+ $dotted .= substr($uname, $lastPos, $pos - $lastPos) . '.';
+ $lastPos = $pos;
+ }
+ $dotted .= substr($uname, $lastPos);
+ $results[] = $dotted . '@gmail.com';
+
+ if (count($results) >= $quantity) {
+ return $results;
+ }
+ }
+ }
+
+ return $results;
+ }
+
+ private function combinations(array $items, int $size): array
+ {
+ if ($size === 0) {
+ return [[]];
+ }
+
+ if (empty($items)) {
+ return [];
+ }
+
+ $combinations = [];
+ $head = $items[0];
+ $tail = array_slice($items, 1);
+
+ foreach ($this->combinations($tail, $size - 1) as $c) {
+ array_unshift($c, $head);
+ $combinations[] = $c;
+ }
+
+ foreach ($this->combinations($tail, $size) as $c) {
+ $combinations[] = $c;
+ }
+
+ return $combinations;
+ }
+
+ public function render()
+ {
+ if (Session::get('isSubscribed')) {
+ return view('livewire.dashboard.bulk-gmail')->layout('components.layouts.dashboard');
+ } else {
+ return view('livewire.dashboard.not-subscribed')->layout('components.layouts.dashboard');
+ }
+ }
+}
diff --git a/app/Livewire/Dashboard/Dashboard.php b/app/Livewire/Dashboard/Dashboard.php
new file mode 100644
index 0000000..efc0dbf
--- /dev/null
+++ b/app/Livewire/Dashboard/Dashboard.php
@@ -0,0 +1,207 @@
+route('status');
+ $currentUrl = $request->fullUrl();
+ if ($status == 'success') {
+ $this->syncSubscription();
+ return redirect()->route('dashboard')->with('status', 'success');
+ } elseif ($status == 'cancel') {
+ return redirect()->route('dashboard')->with('status', 'cancel');
+ }
+ }
+
+ private function checkForSubscriptionStatus(): bool
+ {
+ $redirect = false;
+ $user = auth()->user();
+ $userId = $user->id;
+ if ($user->subscribed()) {
+ $subscription = $user->subscriptions()->where(['stripe_status' => 'active'])->orderByDesc('updated_at')->first();
+ if ($subscription !== null) {
+ $subscriptionId = $subscription->stripe_id;
+ $cacheKey = "stripe_check_executed_user_{$userId}_{$subscriptionId}";
+ if (!Cache::has($cacheKey)) {
+ try {
+ $stripe = new \Stripe\StripeClient(config('cashier.secret'));
+ $subscriptionData = $stripe->subscriptions->retrieve($subscriptionId, []);
+ if ($subscriptionData !== null) {
+ $items = $subscriptionData->items->data[0];
+ if ($items !== null) {
+ $cancel_at_period_end = $subscriptionData->cancel_at_period_end;
+ $ends_at = $items->current_period_end;
+ $cancel_at = $subscriptionData->cancel_at;
+ $canceled_at = $subscriptionData->canceled_at;
+ $status = $subscriptionData->status;
+ if ($cancel_at_period_end) {
+ $final_ends_at = Carbon::createFromTimestamp($cancel_at)->toDateTimeString();
+ } else {
+ if ($cancel_at === null && $canceled_at !== null && $status === "canceled" && $cancel_at_period_end === false) {
+ //$final_ends_at = Carbon::createFromTimestamp($canceled_at)->toDateTimeString();
+ $final_ends_at = Carbon::now()->subDays(2)->toDateTimeString();
+ $redirect = true;
+ } elseif($status === "active" && $cancel_at !== null) {
+ $final_ends_at = Carbon::createFromTimestamp($cancel_at)->toDateTimeString();
+ } else {
+ $final_ends_at = null;
+ }
+ }
+
+ DB::table('subscriptions')
+ ->where('stripe_id', $subscriptionId)
+ ->update([
+ 'stripe_status' => $status,
+ 'ends_at' => $final_ends_at,
+ 'updated_at' => Carbon::now()->toDateTimeString(),
+ ]);
+ }
+ }
+ Cache::put($cacheKey, true, now()->addHour());
+ } catch (Exception $exception) {
+ \Log::error($exception->getMessage());
+ }
+ }
+
+ }
+ }
+ return $redirect;
+ }
+
+ private function syncSubscription(): void
+ {
+ $user = auth()->user();
+ $userId = $user->id;
+ if ($user->hasStripeId()) {
+ $stripe = new \Stripe\StripeClient(config('cashier.secret'));
+ $subscriptions = $stripe->subscriptions->all(['limit' => 1]);
+ if (!$subscriptions->isEmpty()) {
+ $data = $subscriptions->data[0];
+ $items = $subscriptions->data[0]->items->data[0];
+
+ $type = 'default';
+ $subscriptionId = $items->subscription;
+ $status = $data->status;
+ $cancel_at_period_end = $data->cancel_at_period_end;
+ $quantity = $items->quantity;
+ $stripe_price = $items->price->id;
+ $stripe_product = $items->price->product;
+ $ends_at = $items->current_period_end;
+ $subscriptionItemId = $items->id;
+ if ($cancel_at_period_end) {
+ $final_ends_at = Carbon::createFromTimestamp($ends_at)->toDateTimeString();
+ } else {
+ $final_ends_at = null;
+ }
+
+ try {
+ if ($status === "active") {
+ $subscriptionsTable = DB::table('subscriptions')->where(['stripe_id' => $subscriptionId])->first();
+ if ($subscriptionsTable == null) {
+ $subscriptionsTable = DB::table('subscriptions')->insert([
+ 'user_id' => $userId,
+ 'type' => $type,
+ 'stripe_id' => $subscriptionId,
+ 'stripe_status' => $status,
+ 'stripe_price' => $stripe_price,
+ 'quantity' => $quantity,
+ 'ends_at' => $final_ends_at,
+ 'created_at' => Carbon::now()->toDateTimeString(),
+ 'updated_at' => Carbon::now()->toDateTimeString(),
+ ]);
+
+ $subscriptionsTable = DB::table('subscriptions')->where(['stripe_id' => $subscriptionId])->first();
+ $subID = $subscriptionsTable->id;
+ $subscriptionItemsTable = DB::table('subscription_items')->where(['stripe_id' => $subscriptionItemId])->first();
+ if ($subscriptionItemsTable == null) {
+ $subscriptionItemsTable = DB::table('subscription_items')->insert([
+ 'subscription_id' => $subID,
+ 'stripe_id' => $subscriptionItemId,
+ 'stripe_product' => $stripe_product,
+ 'stripe_price' => $stripe_price,
+ 'quantity' => $quantity,
+ 'created_at' => Carbon::now()->toDateTimeString(),
+ 'updated_at' => Carbon::now()->toDateTimeString(),
+ ]);
+ }
+ }
+ }
+ } catch (Exception $exception) {
+ \Log::error($exception->getMessage());
+ }
+ }
+ }
+ }
+
+ public function mount(Request $request)
+ {
+ if($this->checkForSubscriptionStatus()) {
+ return redirect()->route('dashboard');
+ };
+ try {
+ $status = $request->session()->get('status');
+ if (isset($status)) {
+ if ($status == 'success') {
+ $this->message = ['type' => 'success', 'message' => 'Order completed successfully.'];
+ } else {
+ $this->message = ['type' => 'error', 'message' => 'Order cancelled.'];
+ }
+ $request->session()->forget('status');
+ }
+ } catch (\Exception $exception) {
+
+ }
+
+ if (auth()->user()->subscribedToProduct(config('app.plans')[0]['product_id'])) {
+ try {
+ $result = auth()->user()->subscriptions()->where(['stripe_status' => 'active'])->orderByDesc('updated_at')->first();
+ if ($result != null) {
+ $userPriceID = $result['items'][0]['stripe_price'];
+ $subscriptionEnd = $result['ends_at'];
+
+ $planName = null; // Default value if not found
+
+ foreach (config('app.plans') as $plan) {
+ if ($plan['pricing_id'] === $userPriceID) {
+ $planName = $plan['name'];
+ break;
+ }
+ }
+ $this->subscription['name'] = $planName;
+ $this->subscription['ends_at'] = $subscriptionEnd;
+ }
+
+ } catch (\Exception $e) {
+ \Log::error($e->getMessage());
+ }
+ }
+
+ $usageLog = UsageLog::where('user_id', auth()->user()->id)->first();
+ $this->usageLog = [
+ 'emails_created_count' => $usageLog->emails_created_count ?? 0,
+ 'emails_received_count' => $usageLog->emails_received_count ?? 0,
+ ];
+ }
+
+ public function render()
+ {
+ return view('livewire.dashboard.dashboard')->layout('components.layouts.dashboard')->with('message', $this->message);
+ }
+}
diff --git a/app/Livewire/Dashboard/Mailbox/Inbox.php b/app/Livewire/Dashboard/Mailbox/Inbox.php
new file mode 100644
index 0000000..839df59
--- /dev/null
+++ b/app/Livewire/Dashboard/Mailbox/Inbox.php
@@ -0,0 +1,444 @@
+ 'syncEmail', 'getEmail' => 'generateEmail', 'fetchMessages' => 'fetch', 'setMessageId' => 'setMessageId'];
+
+
+ public function mount(): void
+ {
+ $this->premium = Session::get('isInboxTypePremium', true);
+
+ if ($this->premium) {
+ $this->domains = json_decode(config('app.settings.configuration_settings'))->premium_domains ?? [];
+ $this->email = Premium::getEmail();
+ $this->emails = Premium::getEmails();
+ $this->initial = false;
+ $this->checkMultipleEmails();
+ $this->validateDomainInEmail();
+ } else {
+ $this->domains = json_decode(config('app.settings.configuration_settings'))->domains ?? [];
+ $this->email = ZEmail::getEmail();
+ $this->emails = ZEmail::getEmails();
+ $this->initial = false;
+ $this->checkMultipleEmails();
+ $this->validateDomainInEmail();
+ }
+
+ $this->emailsHistory = array_reverse(PremiumEmail::parseEmail(\auth()->user()->id)['data']) ?? [];
+
+ $subscriptionCheck = auth()->user()->subscribedToProduct(config('app.plans')[0]['product_id']);
+ Session::put('isSubscribed', $subscriptionCheck);
+ if($subscriptionCheck) {
+ try {
+ $result = auth()->user()->subscriptions()->where(['stripe_status' => 'active'])->orderByDesc('updated_at')->first();
+ $userPriceID = $result['items'][0]['stripe_price'];
+
+ $mailboxLimit = $this->email_limit;
+ foreach (config('app.plans') as $plan) {
+ if ($plan['pricing_id'] === $userPriceID) {
+ $mailboxLimit = $plan['mailbox_limit'];
+ break;
+ }
+ }
+ $this->email_limit = $mailboxLimit;
+
+ } catch (\Exception $e) {
+ \Log::error($e->getMessage());
+ }
+ }
+
+ if ($this->premium) {
+ $mailboxHistory = UsageLog::where(['user_id' => auth()->user()->id])->first();
+ $this->mailboxHistory = $mailboxHistory->emails_created_history ?? [];
+ } else {
+ $this->mailboxHistory = ZEmail::getEmails() ?? [];
+ }
+
+ }
+
+ private function checkMultipleEmails(): void
+ {
+ if (count($this->emails) == 0) {
+ $this->emails = [$this->email];
+ }
+ if (count($this->emails) > 1) {
+ $this->list = true;
+ } else {
+ $this->list = false;
+ }
+ }
+
+ public function switchEmail($email)
+ {
+ try {
+ if ($email != null) {
+ $data = explode('@', $email);
+ if (isset($data[1])) {
+ $domain = $data[1];
+ if ($this->premium) {
+ $domains = json_decode(config('app.settings.configuration_settings'))->premium_domains ?? [];
+ } else {
+ $domains = json_decode(config('app.settings.configuration_settings'))->domains ?? [];
+ }
+ if (!in_array($domain, $domains)) {
+ return $this->showAlert('error', __('This mailbox can not be recovered.'));
+ }
+ }
+ }
+ } catch (\Exception $exception) {
+ \Log::error($exception->getMessage());
+ }
+
+ return redirect()->route('switchP', ['email' => $email]);
+ }
+
+ public function syncEmail(): void
+ {
+ $this->email = Premium::getEmail();
+ $this->emails = Premium::getEmails();
+ if (count($this->emails) == 0) {
+ $this->dispatch('getEmail');
+ }
+ $this->checkMultipleEmails();
+ $this->dispatch('syncMailbox', $this->email);
+ $this->dispatch('fetchMessages');
+ }
+
+ public function generateEmail(): void
+ {
+ if ($this->email == null) {
+ Premium::generateRandomEmail();
+ }
+ $this->checkMultipleEmails();
+ $this->dispatch('updateEmail');
+ }
+
+ /* Actions */
+ public function create() {
+ if (!$this->username) {
+ return $this->showAlert('error', __('Please enter Username'));
+ }
+ $this->checkDomainInUsername();
+ if (strlen($this->username) < json_decode(config('app.settings.configuration_settings'))->custom_username_length_min || strlen($this->username) > json_decode(config('app.settings.configuration_settings'))->custom_username_length_max) {
+ return $this->showAlert('error', __('Username length cannot be less than') . ' ' . json_decode(config('app.settings.configuration_settings'))->custom_username_length_min . ' ' . __('and greater than') . ' ' . json_decode(config('app.settings.configuration_settings'))->custom_username_length_max);
+ }
+ if (!$this->domain) {
+ return $this->showAlert('error', __('Please Select a Domain'));
+ }
+ if (in_array($this->username, json_decode(config('app.settings.configuration_settings'))->forbidden_ids)) {
+ return $this->showAlert('error', __('Username not allowed'));
+ }
+ if (!$this->checkEmailLimit()) {
+ return $this->showAlert('error', __('You have reached daily limit of MAX ') . $this->email_limit . __(' temp mail'));
+ }
+ if (!$this->checkUsedEmail()) {
+ return $this->showAlert('error', __('Sorry! That email is already been used by someone else. Please try a different email address.'));
+ }
+ if ($this->premium) {
+ $this->email = Premium::createCustomEmail($this->username, $this->domain);
+ } else {
+ $this->email = ZEmail::createCustomEmail($this->username, $this->domain);
+ }
+ return redirect()->route('dashboard.premium');
+
+ }
+ public function random() {
+
+ if (!$this->checkEmailLimit()) {
+ return $this->showAlert('error', __('You have reached daily limit of maximum ') . $this->email_limit . __(' temp mail addresses.'));
+ }
+ if ($this->premium) {
+ $this->email = Premium::generateRandomEmail();
+ } else {
+ $this->email = ZEmail::generateRandomEmail();
+ }
+ return redirect()->route('dashboard.premium');
+ }
+ public function gmail() {
+ if (!$this->checkEmailLimit()) {
+ return $this->showAlert('error', __('You have reached daily limit of maximum ') . $this->email_limit . __(' temp mail addresses.'));
+ }
+ if ($this->premium) {
+ $this->email = Premium::generateRandomGmail();
+ } else {
+ $this->email = ZEmail::generateRandomGmail();
+ }
+ return redirect()->route('dashboard.premium');
+ }
+
+ public function outlook() {
+ if (!$this->checkEmailLimit()) {
+ return $this->showAlert('error', __('You have reached daily limit of maximum ') . $this->email_limit . __(' temp mail addresses.'));
+ }
+ if ($this->premium) {
+ $this->email = Premium::generateRandomOutlook();
+ } else {
+ $this->email = ZEmail::generateRandomOutlook();
+ }
+ return redirect()->route('dashboard.premium');
+ }
+
+ public function deleteEmail()
+ {
+ return redirect()->route('deleteP', ['email' => $this->email]);
+ }
+
+ private function showAlert($type, $message): void
+ {
+ $this->dispatch('showAlert', ['type' => $type, 'message' => $message]);
+ }
+
+ private function checkEmailLimit(): bool
+ {
+ $logs = Log::select('ip', 'email')->where('user_id', auth()->user()->id)->where('created_at', '>', Carbon::now()->subDay())->groupBy('email')->groupBy('ip')->get();
+ if (count($logs) >= $this->email_limit) {
+ return false;
+ }
+ return true;
+ }
+
+ private function checkUsedEmail(): bool
+ {
+ if (json_decode(config('app.settings.configuration_settings'))->disable_used_email) {
+ $check = Log::where('email', $this->user . '@' . $this->domain)->where('ip', '<>', request()->ip())->count();
+ if ($check > 0) {
+ return false;
+ }
+ return true;
+ }
+ return true;
+ }
+
+ private function checkDomainInUsername(): void
+ {
+ $parts = explode('@', $this->username);
+ if (isset($parts[1])) {
+ if (in_array($parts[1], $this->domains)) {
+ $this->domain = $parts[1];
+ }
+ $this->username = $parts[0];
+ }
+ }
+
+ private function validateDomainInEmail(): void
+ {
+ try {
+ if ($this->email != null) {
+ $data = explode('@', $this->email);
+ if (isset($data[1])) {
+ $domain = $data[1];
+ if ($this->premium) {
+ $domains = json_decode(config('app.settings.configuration_settings'))->premium_domains ?? [];
+ } else {
+ $domains = json_decode(config('app.settings.configuration_settings'))->domains ?? [];
+ }
+ if (!in_array($domain, $domains)) {
+ $key = array_search($this->email, $this->emails);
+ if ($this->premium) {
+ Premium::removeEmail($this->email);
+ } else {
+ ZEmail::removeEmail($this->email);
+ }
+ if ($key == 0 && count($this->emails) == 1 && json_decode(config('app.settings.configuration_settings'))->after_last_email_delete == 'redirect_to_homepage') {
+ redirect()->route('dashboard.premium');
+ } else {
+ redirect()->route('dashboard.premium');
+ }
+ }
+ }
+ }
+ } catch (\Exception $exception) {
+ \Log::error($exception->getMessage());
+ }
+ }
+
+ /* Mailbox */
+
+
+ public function fetch(): void
+ {
+ try {
+ $count = count($this->messages);
+ if ($count > 0) {
+ $this->messages = [];
+ }
+ $responses = [];
+ if ($this->premium) {
+ if (config('app.beta_feature') || !json_decode(config('app.settings.imap_settings'))->premium_cc_check) {
+ $responses = [
+ 'to' => Premium::getMessages($this->email, 'to', $this->deleted),
+ 'cc' => [
+ 'data' => [],
+ 'notifications' => []
+ ]
+ ];
+ } else {
+ $responses = [
+ 'to' => Premium::getMessages($this->email, 'to', $this->deleted),
+ 'cc' => Premium::getMessages($this->email, 'cc', $this->deleted)
+ ];
+ }
+ } else {
+ if (config('app.beta_feature') || !json_decode(config('app.settings.imap_settings'))->cc_check) {
+ $responses = [
+ 'to' => ZEmail::getMessages($this->email, 'to', $this->deleted),
+ 'cc' => [
+ 'data' => [],
+ 'notifications' => []
+ ]
+ ];
+ } else {
+ $responses = [
+ 'to' => ZEmail::getMessages($this->email, 'to', $this->deleted),
+ 'cc' => ZEmail::getMessages($this->email, 'cc', $this->deleted)
+ ];
+ }
+ }
+
+ $this->deleted = [];
+ $this->messages = array_merge($responses['to']['data'], $responses['cc']['data']);
+ $notifications = array_merge($responses['to']['notifications'], $responses['cc']['notifications']);
+
+ if (count($notifications)) {
+ if (!$this->overflow && count($this->messages) == $count) {
+ $this->overflow = true;
+ }
+ } else {
+ $this->overflow = false;
+ }
+
+ foreach ($notifications as $notification) {
+ $this->dispatch('showNewMailNotification', $notification);
+ }
+ Premium::incrementMessagesStats(count($notifications));
+
+ if ($this->email != null && count($this->messages) > 0) {
+ foreach ($this->messages as $message) {
+ PremiumEmail::createEmail($message, $this->email);
+ }
+ }
+
+ $this->emailsHistory = array_reverse(PremiumEmail::parseEmail(\auth()->user()->id)['data']) ?? [];
+
+ } catch (\Exception $e) {
+ if (Auth::check() && Auth::user()->level == 9) {
+ $this->error = $e->getMessage();
+ } else {
+ $this->error = 'Not able to connect to Mail Server';
+ }
+ }
+ $this->dispatch('stopLoader');
+ $this->initial = true;
+ }
+
+ public function delete($messageId) {
+
+ try {
+ $this->deleted[] = $messageId;
+ foreach ($this->messages as $key => $message) {
+ if ($message['id'] == $messageId) {
+ if ($this->premium) {
+ $directory = public_path('tmp/premium/attachments/') . $messageId;
+ } else {
+ $directory = public_path('tmp/attachments/') . $messageId;
+ }
+ $this->rrmdir($directory);
+ unset($this->messages[$key]);
+ }
+ }
+
+ } catch (
+ \Exception $exception
+ ) {
+ \Illuminate\Support\Facades\Log::error($exception->getMessage());
+ }
+
+ }
+
+ public function toggleMode()
+ {
+ $this->premium = !$this->premium;
+ Session::put('isInboxTypePremium', $this->premium);
+ if ($this->premium) {
+ $this->domains = json_decode(config('app.settings.configuration_settings'))->premium_domains ?? [];
+ $this->email = Premium::getEmail();
+ $this->emails = Premium::getEmails();
+ $this->initial = false;
+ $this->checkMultipleEmails();
+ $this->validateDomainInEmail();
+ $mailboxHistory = UsageLog::where(['user_id' => auth()->user()->id])->first();
+ $this->mailboxHistory = $mailboxHistory->emails_created_history ?? [];
+ $this->messages = [];
+
+ } else {
+ $this->domains = json_decode(config('app.settings.configuration_settings'))->domains ?? [];
+ $this->email = ZEmail::getEmail();
+ $this->emails = ZEmail::getEmails();
+ $this->initial = false;
+ $this->checkMultipleEmails();
+ $this->validateDomainInEmail();
+ $this->mailboxHistory = array_reverse(ZEmail::getEmails()) ?? [];
+ $this->messages = [];
+ }
+ }
+
+ public function render()
+ {
+ if (Session::get('isSubscribed')) {
+ return view('livewire.dashboard.mailbox.inbox')->layout('components.layouts.dashboard');
+ } else {
+ return view('livewire.dashboard.not-subscribed')->layout('components.layouts.dashboard');
+ }
+ }
+
+ private function rrmdir($dir): void
+ {
+ if (is_dir($dir)) {
+ $objects = scandir($dir);
+ foreach ($objects as $object) {
+ if ($object != "." && $object != "..") {
+ if (is_dir($dir . DIRECTORY_SEPARATOR . $object) && !is_link($dir . "/" . $object))
+ $this->rrmdir($dir . DIRECTORY_SEPARATOR . $object);
+ else
+ unlink($dir . DIRECTORY_SEPARATOR . $object);
+ }
+ }
+ rmdir($dir);
+ }
+ }
+}
diff --git a/app/Livewire/Dashboard/NotSubscribed.php b/app/Livewire/Dashboard/NotSubscribed.php
new file mode 100644
index 0000000..91b9fa5
--- /dev/null
+++ b/app/Livewire/Dashboard/NotSubscribed.php
@@ -0,0 +1,13 @@
+layout('components.layouts.dashboard');
+ }
+}
diff --git a/app/Livewire/Dashboard/Pricing.php b/app/Livewire/Dashboard/Pricing.php
new file mode 100644
index 0000000..80e0e43
--- /dev/null
+++ b/app/Livewire/Dashboard/Pricing.php
@@ -0,0 +1,25 @@
+plans = config('app.plans');
+ }
+
+ public function choosePlan($pricing_id): void
+ {
+ $this->redirect(route('checkout', $pricing_id));
+ }
+
+ public function render()
+ {
+ return view('livewire.dashboard.pricing');
+ }
+}
diff --git a/app/Livewire/Frontend/Action.php b/app/Livewire/Frontend/Action.php
index 3c68e13..fccd712 100644
--- a/app/Livewire/Frontend/Action.php
+++ b/app/Livewire/Frontend/Action.php
@@ -10,8 +10,6 @@ use Livewire\Component;
class Action extends Component
{
public $username, $email, $emails, $domain, $domains, $action, $initial;
-
-
public function mount() {
$this->domains = json_decode(config('app.settings.configuration_settings'))->domains ?? [];
$this->email = ZEmail::getEmail();
@@ -61,6 +59,14 @@ class Action extends Component
return redirect()->route('mailbox');
}
+ public function outlook() {
+ if (!$this->checkEmailLimit()) {
+ return $this->showAlert('error', __('You have reached daily limit of maximum ') . json_decode(config('app.settings.configuration_settings'))->email_limit . __(' temp mail addresses.'));
+ }
+ $this->email = ZEmail::generateRandomOutlook();
+ return redirect()->route('mailbox');
+ }
+
public function deleteEmail()
{
return redirect()->route('delete', ['email' => $this->email]);
@@ -68,6 +74,10 @@ class Action extends Component
private function showAlert($type, $message): void
{
+ $check = json_decode(config('app.settings.configuration_settings'))->email_limit;
+ if (strpos($message, $check) !== false) {
+ $this->dispatch('promotePremium');
+ }
$this->dispatch('showAlert', ['type' => $type, 'message' => $message]);
}
diff --git a/app/Livewire/Settings/Appearance.php b/app/Livewire/Settings/Appearance.php
new file mode 100644
index 0000000..be0927a
--- /dev/null
+++ b/app/Livewire/Settings/Appearance.php
@@ -0,0 +1,12 @@
+validate([
+ 'password' => ['required', 'string', 'current_password'],
+ ]);
+
+ tap(Auth::user(), $logout(...))->delete();
+
+ $this->redirect('/', navigate: true);
+ }
+}
diff --git a/app/Livewire/Settings/Password.php b/app/Livewire/Settings/Password.php
new file mode 100644
index 0000000..9fb0d26
--- /dev/null
+++ b/app/Livewire/Settings/Password.php
@@ -0,0 +1,45 @@
+validate([
+ 'current_password' => ['required', 'string', 'current_password'],
+ 'password' => ['required', 'string', PasswordRule::defaults(), 'confirmed'],
+ ]);
+ } catch (ValidationException $e) {
+ $this->reset('current_password', 'password', 'password_confirmation');
+
+ throw $e;
+ }
+
+ Auth::user()->update([
+ 'password' => Hash::make($validated['password']),
+ ]);
+
+ $this->reset('current_password', 'password', 'password_confirmation');
+
+ $this->dispatch('password-updated');
+ }
+}
diff --git a/app/Livewire/Settings/Profile.php b/app/Livewire/Settings/Profile.php
new file mode 100644
index 0000000..d114ad2
--- /dev/null
+++ b/app/Livewire/Settings/Profile.php
@@ -0,0 +1,76 @@
+name = Auth::user()->name;
+ $this->email = Auth::user()->email;
+ }
+
+ /**
+ * Update the profile information for the currently authenticated user.
+ */
+ public function updateProfileInformation(): void
+ {
+ $user = Auth::user();
+
+ $validated = $this->validate([
+ 'name' => ['required', 'string', 'max:255'],
+
+ 'email' => [
+ 'required',
+ 'string',
+ 'lowercase',
+ 'email',
+ 'max:255',
+ Rule::unique(User::class)->ignore($user->id),
+ ],
+ ]);
+
+ $user->fill($validated);
+
+ if ($user->isDirty('email')) {
+ $user->email_verified_at = null;
+ }
+
+ $user->save();
+
+ $this->dispatch('profile-updated', name: $user->name);
+ }
+
+ /**
+ * Send an email verification notification to the current user.
+ */
+ public function resendVerificationNotification(): void
+ {
+ $user = Auth::user();
+
+ if ($user->hasVerifiedEmail()) {
+ $this->redirectIntended(default: route('dashboard', absolute: false));
+
+ return;
+ }
+
+ $user->sendEmailVerificationNotification();
+
+ Session::flash('status', 'verification-link-sent');
+ }
+}
diff --git a/app/Models/Log.php b/app/Models/Log.php
index ce1ec70..52438f0 100644
--- a/app/Models/Log.php
+++ b/app/Models/Log.php
@@ -16,6 +16,7 @@ class Log extends Model
* @var array
*/
protected $fillable = [
+ 'user_id',
'ip',
'email',
];
diff --git a/app/Models/Plan.php b/app/Models/Plan.php
new file mode 100644
index 0000000..d8af905
--- /dev/null
+++ b/app/Models/Plan.php
@@ -0,0 +1,24 @@
+ 'json',
+ 'monthly_billing' => 'boolean',
+ ];
+}
diff --git a/app/Models/Premium.php b/app/Models/Premium.php
new file mode 100644
index 0000000..5ade493
--- /dev/null
+++ b/app/Models/Premium.php
@@ -0,0 +1,535 @@
+ $imapDB['premium_host'],
+ 'port' => $imapDB['premium_port'],
+ 'encryption' => $imapDB['premium_encryption'],
+ 'validate_cert' => $imapDB['premium_validate_cert'],
+ 'username' => $imapDB['premium_username'],
+ 'password' => $imapDB['premium_password'],
+ 'default_account' => $imapDB['premium_default_account'],
+ 'protocol' => $imapDB['premium_protocol'],
+ 'cc_check' => $imapDB['premium_cc_check']
+ ];
+
+ return ZEmail::connectMailBox($imap);
+ }
+
+ public static function fetchMessages($email, $type = 'to', $deleted = []): array
+ {
+ if ($email == null) {
+ return [
+ "data" => [],
+ "notifications" => []
+ ];
+ }
+ $allowed = explode(',', 'doc,docx,xls,xlsx,ppt,pptx,xps,pdf,dxf,ai,psd,eps,ps,svg,ttf,zip,rar,tar,gzip,mp3,mpeg,wav,ogg,jpeg,jpg,png,gif,bmp,tif,webm,mpeg4,3gpp,mov,avi,mpegs,wmv,flx,txt');
+ $connection = self::connectMailBox();
+
+ $mailbox = $connection->getMailbox('INBOX');
+ $search = new SearchExpression();
+ if ($type == 'cc') {
+ $search->addCondition(new Cc($email));
+ } else {
+ $search->addCondition(new To($email));
+ }
+ $messages = $mailbox->getMessages($search, \SORTDATE, true);
+ $limit = json_decode(config('app.settings.configuration_settings'))->fetch_messages_limit ?? 15;
+ $count = 1;
+ $response = [
+ 'data' => [],
+ 'notifications' => []
+ ];
+ foreach ($messages as $message) {
+ if (in_array($message->getNumber(), $deleted)) {
+ $message->delete();
+ continue;
+ }
+ $blocked = false;
+ $sender = $message->getFrom();
+ $date = $message->getDate();
+ if (!$date) {
+ $date = new \DateTime();
+ if ($message->getHeaders()->get('udate')) {
+ $date->setTimestamp($message->getHeaders()->get('udate'));
+ }
+ }
+ $datediff = new Carbon($date);
+ $content = '';
+ $contentText = '';
+ $html = $message->getBodyHtml();
+ $text = $message->getBodyText();
+ if ($text) {
+ $contentText = str_replace('', $text));
+ }
+ if (json_decode(config('app.settings.configuration_settings'))->enable_masking_external_link) {
+ $content = str_replace('href="', 'href="http://href.li/?', $content);
+ }
+
+ $obj = [];
+ $obj['subject'] = $message->getSubject();
+ $obj['sender_name'] = $sender->getName();
+ $obj['sender_email'] = $sender->getAddress();
+ $obj['timestamp'] = $message->getDate();
+ $obj['date'] = $date->format(json_decode(config('app.settings.configuration_settings'))->date_format ?? 'd M Y h:i A');
+ $obj['datediff'] = $datediff->diffForHumans();
+ $obj['id'] = $message->getNumber();
+ $obj['size'] = $message->getSize();
+ $obj['content'] = $content;
+ $obj['contentText'] = $contentText;
+ $obj['attachments'] = [];
+ $obj['is_seen'] = true;
+ $obj['sender_photo'] = self::chooseColor(strtoupper(substr($sender->getName() ?: $sender->getAddress(), 0, 1) ));
+
+ //Checking if Sender is Blocked
+ $domain = explode('@', $obj['sender_email'])[1];
+ $blocked = in_array($domain, json_decode(config('app.settings.configuration_settings'))->blocked_domains);
+ if ($blocked) {
+ $obj['subject'] = __('Blocked');
+ $obj['content'] = __('Emails from') . ' ' . $domain . ' ' . __('are blocked by Admin');
+ $obj['contentText'] = __('Emails from') . ' ' . $domain . ' ' . __('are blocked by Admin');
+ }
+ if ($message->hasAttachments() && !$blocked) {
+ $attachments = $message->getAttachments();
+ $directory = './tmp/premium/attachments/' . $obj['id'] . '/';
+ is_dir($directory) || mkdir($directory, 0777, true);
+ foreach ($attachments as $attachment) {
+ $filenameArray = explode('.', $attachment->getFilename());
+ $extension = $filenameArray[count($filenameArray) - 1];
+ if (in_array($extension, $allowed)) {
+ if (!file_exists($directory . $attachment->getFilename())) {
+ try {
+ file_put_contents(
+ $directory . $attachment->getFilename(),
+ $attachment->getDecodedContent()
+ );
+ } catch (\Exception $e) {
+ \Illuminate\Support\Facades\Log::error($e->getMessage());
+ }
+ }
+ if ($attachment->getFilename() !== 'undefined') {
+ $url = config('app.settings.app_base_url') . str_replace('./', '/', $directory . $attachment->getFilename());
+ $structure = $attachment->getStructure();
+ if (isset($structure->id) && str_contains($obj['content'], trim($structure->id, '<>'))) {
+ $obj['content'] = str_replace('cid:' . trim($structure->id, '<>'), $url, $obj['content']);
+ }
+ $obj['attachments'][] = [
+ 'file' => $attachment->getFilename(),
+ 'url' => $url
+ ];
+ }
+ }
+ }
+ }
+ $response['data'][] = $obj;
+ if (!$message->isSeen()) {
+ $response['notifications'][] = [
+ 'subject' => $obj['subject'],
+ 'sender_name' => $obj['sender_name'],
+ 'sender_email' => $obj['sender_email']
+ ];
+ if (config('app.zemail_log')) {
+ file_put_contents(storage_path('logs/zemail.csv'), request()->ip() . "," . date("Y-m-d h:i:s a") . "," . $obj['sender_email'] . "," . $email . PHP_EOL, FILE_APPEND);
+ }
+ }
+ $message->markAsSeen();
+ if (++$count > $limit) {
+ break;
+ }
+ }
+
+ $response['data'] = array_reverse($response['data']);
+ $connection->expunge();
+ return $response;
+ }
+
+ public static function getMessages($email, $type = 'to', $deleted = []): array
+ {
+ return self::fetchMessages($email, $type, $deleted);
+ }
+ public static function deleteMessage($id): void
+ {
+ $connection = self::connectMailBox();
+ $mailbox = $connection->getMailbox('INBOX');
+ $mailbox->getMessage($id)->delete();
+ $connection->expunge();
+ }
+ public static function getEmail($generate = false) {
+ if (Cookie::has('p_email')) {
+ return Cookie::get('p_email');
+ } else {
+ return $generate ? self::generateRandomEmail() : null;
+ }
+ }
+ public static function getEmails() {
+ if (Cookie::has('p_emails')) {
+ return unserialize(Cookie::get('p_emails'));
+ } else {
+ return [];
+ }
+ }
+ public static function setEmail($email): void
+ {
+ $emails = unserialize(Cookie::get('p_emails'));
+ if (is_array($emails) && in_array($email, $emails)) {
+ Cookie::queue('p_email', $email, 43800);
+ }
+ }
+
+ public static function setEmailP($email): void
+ {
+ $usageLogs = UsageLog::where(['user_id' => auth()->user()->id])->first();
+ $emails = $usageLogs->emails_created_history;
+ if (is_array($emails) && in_array($email, $emails)) {
+ Cookie::queue('p_email', $email, 43800);
+ }
+ }
+ public static function removeEmail($email): void
+ {
+ $emails = self::getEmails();
+ $key = array_search($email, $emails);
+ if ($key !== false) {
+ array_splice($emails, $key, 1);
+ }
+ if (count($emails) > 0) {
+ self::setEmail($emails[0]);
+ Cookie::queue('p_emails', serialize($emails), 43800);
+ } else {
+ Cookie::queue('p_email', '', -1);
+ Cookie::queue('p_emails', serialize([]), -1);
+ }
+ }
+ public static function createCustomEmailFull($email): string
+ {
+ $data = explode('@', $email);
+ $username = $data[0];
+ if (strlen($username) < json_decode(config('app.settings.configuration_settings'))->custom_username_length_min || strlen($username) > json_decode(config('app.settings.configuration_settings'))->custom_username_length_max) {
+ $zemail = new Premium();
+ $username = $zemail->generateRandomUsername();
+ }
+ $domain = $data[1];
+ return self::createCustomEmail($username, $domain);
+ }
+ public static function createCustomEmail($username, $domain): string
+ {
+ $username = preg_replace('/[^a-zA-Z0-9+.]/', '', strtolower($username));
+
+ $settings = json_decode(config('app.settings.configuration_settings'), true);
+ $forbidden_ids = $settings['forbidden_ids'] ?? [];
+ $gmail_usernames = $settings['premium_gmailUsernames'] ?? [];
+ $outlook_usernames = $settings['premium_outlookUsernames'] ?? [];
+ $domains = $settings['premium_domains'] ?? [];
+
+ if (in_array($username, $forbidden_ids)) {
+ return self::generateRandomEmail(true);
+ }
+
+ if ($username === '' && in_array($domain, ['gmail.com', 'googlemail.com'])) {
+ return self::generateRandomGmail(true);
+ }
+
+ if ($username === '' && in_array($domain, ['outlook.com'])) {
+ return self::generateRandomOutlook(true);
+ }
+
+ $zemail = new Premium();
+
+ if ($username === '' && in_array($domain, $domains)) {
+ return $zemail->generateRandomUsername().'@'.$domain;
+ }
+
+ if (in_array($domain, ['outlook.com'])) {
+ if (str_contains($username, '+')) {
+ [$check_username, $post_username] = explode('+', $username, 2);
+
+ if (in_array($check_username, $outlook_usernames)) {
+ $email = $username . '@' . $domain;
+ } else {
+ $email = $zemail->getRandomOutlookUser() . '+' . $post_username . '@' . $domain;
+ }
+ } else {
+ $email = $zemail->getRandomOutlookUser() . '+' . $username . '@' . $domain;
+ }
+ self::storeEmail($email);
+ return $email;
+ }
+
+ if (in_array($domain, ['gmail.com', 'googlemail.com'])) {
+ if (str_contains($username, '+')) {
+ [$check_username, $post_username] = explode('+', $username, 2);
+
+ if (in_array($check_username, $gmail_usernames)) {
+ $email = $username . '@' . $domain;
+ } else {
+ $email = $zemail->getRandomGmailUser() . '+' . $post_username . '@' . $domain;
+ }
+
+ } elseif (str_contains($username, '.')) {
+ $check_username = str_replace('.', '', $username);
+
+ if (in_array($check_username, $gmail_usernames)) {
+ $email = $username . '@' . $domain;
+ } else {
+ $email = $zemail->generateRandomGmail() . '@' . $domain;
+ }
+
+ } else {
+ $email = $zemail->getRandomGmailUser() . '+' . $username . '@' . $domain;
+ }
+
+ self::storeEmail($email);
+ return $email;
+ }
+
+ // Handle other custom domains
+ if (!in_array($domain, $domains)) {
+ return self::generateRandomEmail(true);
+ }
+
+ $finalDomain = in_array($domain, $domains) ? $domain : ($domains[0] ?? 'example.com');
+ $email = $username . '@' . $finalDomain;
+
+ self::storeEmail($email);
+ return $email;
+ }
+ public static function generateRandomEmail($store = true): string
+ {
+ $zemail = new Premium();
+ $domain = $zemail->getRandomDomain();
+ if ($domain == "gmail.com") {
+ $rd = mt_rand(0,1);
+ if ($rd == 0) {
+ $email = $zemail->generateRandomGmail(false);
+ } else {
+ $email = $zemail->getRandomGmailUser().'+'.$zemail->generateRandomUsername().'@gmail.com';
+ }
+ } elseif ($domain == "googlemail.com") {
+ $rd = mt_rand(0,1);
+ if ($rd == 0) {
+ $email = $zemail->generateRandomGmail(false);
+ } else {
+ $email = $zemail->getRandomGmailUser().'+'.$zemail->generateRandomUsername().'@googlemail.com';
+ }
+ } elseif ($domain == "outlook.com") {
+ $email = $zemail->getRandomOutlookUser().'+'.$zemail->generateRandomUsername().'@outlook.com';
+ }
+ else {
+ $email = $zemail->generateRandomUsername() . '@' . $domain;
+ }
+ if ($store) {
+ self::storeEmail($email);
+ }
+ return $email;
+ }
+ public static function generateRandomGmail($store = true): string
+ {
+ $zemail = new Premium();
+ $uname = $zemail->getRandomGmailUser();
+ $uname_len = strlen($uname);
+ $len_power = $uname_len - 1;
+ $combination = pow(2,$len_power);
+ $rand_comb = mt_rand(1,$combination);
+ $formatted = implode(' ',str_split($uname));
+ $uname_exp = explode(' ', $formatted);
+
+ $bin = intval("");
+ for($i=0; $i<$len_power; $i++) {
+ $bin .= mt_rand(0,1);
+ }
+ $bin = explode(' ', implode(' ',str_split(strval($bin))));
+
+ $email = "";
+ for($i=0; $i<$len_power; $i++) {
+ $email .= $uname_exp[$i];
+ if($bin[$i]) {
+ $email .= ".";
+ }
+ }
+ $email .= $uname_exp[$i];
+ $gmail_rand = mt_rand(1,10);
+ if($gmail_rand > 5) {
+ $email .= "@gmail.com";
+ } else {
+ $email .= "@googlemail.com";
+ }
+ if ($store) {
+ self::storeEmail($email);
+ }
+ return $email;
+ }
+
+ public static function generateRandomOutlook($store = true): string
+ {
+ $zemail = new Premium();
+ $email = $zemail->getRandomOutlookUser().'+'.$zemail->generateRandomUsername().'@outlook.com';
+ if ($store) {
+ self::storeEmail($email);
+ }
+ return $email;
+ }
+ private static function storeEmail($email): void
+ {
+ Log::create([
+ 'user_id' => auth()->user()->id,
+ 'ip' => request()->ip(),
+ 'email' => $email
+ ]);
+
+ self::storeUsageLog($email);
+
+ Cookie::queue('p_email', $email, 43800);
+ $emails = Cookie::has('p_emails') ? unserialize(Cookie::get('p_emails')) : [];
+ if (!in_array($email, $emails)) {
+ self::incrementEmailStats();
+ $emails[] = $email;
+ Cookie::queue('p_emails', serialize($emails), 43800);
+ }
+ }
+ public static function incrementEmailStats($count = 1): void
+ {
+ Meta::incrementEmailIdsCreated($count);
+ self::incrementEmailIdsCreated($count);
+ }
+ public static function incrementMessagesStats($count = 1): void
+ {
+ Meta::incrementMessagesReceived($count);
+ self::incrementMessagesReceived($count);
+ }
+ private function generateRandomUsername(): string
+ {
+ $start = json_decode(config('app.settings.configuration_settings'))->random_username_length_min ?? 0;
+ $end = json_decode(config('app.settings.configuration_settings'))->random_username_length_max ?? 0;
+ if ($start == 0 && $end == 0) {
+ return $this->generatePronounceableWord();
+ }
+ return $this->generatedRandomBetweenLength($start, $end);
+ }
+ protected function generatedRandomBetweenLength($start, $end): string
+ {
+ $length = rand($start, $end);
+ return $this->generateRandomString($length);
+ }
+ private function getRandomDomain() {
+ $domains = json_decode(config('app.settings.configuration_settings'))->premium_domains ?? [];
+ $count = count($domains);
+ return $count > 0 ? $domains[rand(1, $count) - 1] : '';
+ }
+ private function getRandomGmailUser() {
+ $gmailusername = json_decode(config('app.settings.configuration_settings'))->premium_gmailUsernames ?? [];
+ $count = count($gmailusername);
+ return $count > 0 ? $gmailusername[rand(1, $count) - 1] : '';
+ }
+ private function getRandomOutlookUser() {
+ $outlook_username = json_decode(config('app.settings.configuration_settings'))->premium_outlookUsernames ?? [];
+ $count = count($outlook_username);
+ return $count > 0 ? $outlook_username[rand(1, $count) - 1] : '';
+ }
+ private function generatePronounceableWord(): string
+ {
+ $c = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones
+ $v = 'aeiou'; //vowels
+ $a = $c . $v; //both
+ $random = '';
+ for ($j = 0; $j < 2; $j++) {
+ $random .= $c[rand(0, strlen($c) - 1)];
+ $random .= $v[rand(0, strlen($v) - 1)];
+ $random .= $a[rand(0, strlen($a) - 1)];
+ }
+ return $random;
+ }
+ private function generateRandomString($length = 10): string
+ {
+ $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
+ $charactersLength = strlen($characters);
+ $randomString = '';
+ for ($i = 0; $i < $length; $i++) {
+ $randomString .= $characters[rand(0, $charactersLength - 1)];
+ }
+ return $randomString;
+ }
+
+ /**
+ * Stats Handling Functions
+ */
+
+
+ public static function incrementEmailIdsCreated($count = 1): void
+ {
+ $user = Auth::user();
+ if (!$user) {
+ return;
+ }
+
+ $usageLog = UsageLog::firstOrCreate(
+ ['user_id' => $user->id],
+ ['ip_address' => request()->ip()]
+ );
+
+ $usageLog->increment('emails_created_count', $count);
+ }
+
+ public static function incrementMessagesReceived($count = 1): void
+ {
+ $user = Auth::user();
+ if (!$user) {
+ return;
+ }
+
+ $usageLog = UsageLog::firstOrCreate(
+ ['user_id' => $user->id],
+ ['ip_address' => request()->ip()]
+ );
+
+ $usageLog->increment('emails_received_count', $count);
+ }
+
+ public static function storeUsageLog($email): void
+ {
+ try {
+ $user = Auth::user();
+ if (!$user) {
+ return;
+ }
+
+ $ip = request()->ip();
+ $usageLog = UsageLog::firstOrCreate(
+ ['user_id' => $user->id],
+ ['ip_address' => $ip]
+ );
+
+ $history = $usageLog->emails_created_history ?? [];
+ if (!in_array($email, $history)) {
+ $history[] = $email;
+ //$usageLog->emails_created_count += 1;
+ $usageLog->emails_created_history = $history;
+ $usageLog->save();
+ }
+ } catch (\Exception $exception) {
+ \Log::error($exception->getMessage());
+ }
+
+ }
+}
diff --git a/app/Models/PremiumEmail.php b/app/Models/PremiumEmail.php
new file mode 100644
index 0000000..4d2e74b
--- /dev/null
+++ b/app/Models/PremiumEmail.php
@@ -0,0 +1,160 @@
+ 'array',
+ 'cc' => 'array',
+ 'bcc' => 'array',
+ 'attachments' => 'array',
+ 'timestamp' => 'datetime'
+ ];
+
+ public static function createEmail($message, $email): void
+ {
+ $initialData = $message;
+ $utcTime = CarbonImmutable::instance($message['timestamp'])->setTimezone('UTC')->toDateTimeString();
+ $messageId = Carbon::parse($utcTime)->format('Ymd').$initialData['id'];
+ $userId = \auth()->user()->id;
+ $exists = PremiumEmail::where('user_id', $userId)->where('message_id', $messageId)->exists();
+
+ $data = [
+ 'user_id' => $userId,
+ 'message_id' => $messageId,
+ 'subject' => $initialData['subject'],
+ 'from_name' => $initialData['sender_name'],
+ 'from_email' => $initialData['sender_email'],
+ 'to' => ["$email"],
+ 'cc' => [],
+ 'bcc' => [],
+ 'timestamp' => $utcTime,
+ 'body_text' => $initialData['contentText'],
+ 'body_html' => $initialData['content'],
+ 'is_seen' => true,
+ 'is_flagged' => false,
+ 'size' => $initialData['size'] ?? 0,
+ 'mailbox' => 'INBOX',
+ 'raw_headers' => null,
+ 'raw_body' => null,
+ 'attachments' => $initialData['attachments'],
+ ];
+
+ if (!$exists) {
+ PremiumEmail::create($data);
+ }
+ }
+
+ public static function fetchEmailFromDB($userId) {
+
+ $validator = Validator::make(['user_id' => $userId], [
+ 'user_id' => 'required|integer'
+ ]);
+
+ if ($validator->fails()) {
+ return [];
+ }
+ return self::whereJsonContains('user_id', $userId)->orderBy('timestamp', 'desc')->get();
+ }
+
+ public static function parseEmail($userId, $deleted = []): array
+ {
+ $messages = self::fetchEmailFromDB($userId);
+ $limit = 50;
+ $count = 1;
+ $response = [
+ 'data' => [],
+ 'notifications' => []
+ ];
+
+ foreach ($messages as $message) {
+
+ if (in_array($message['message_id'], $deleted)) {
+ // If it exists, delete the matching record from the 'emails' table
+ Email::where('message_id', $message['message_id'])->delete();
+ continue;
+ }
+
+ $blocked = false;
+
+ $timestamp = $message['timestamp'];
+ $carbonTimestamp = Carbon::parse($timestamp, 'UTC');
+ $obj = [];
+ $obj['subject'] = $message['subject'];
+ $obj['to'] = $message['to'];
+ $obj['sender_name'] = $message['from_name'];
+ $obj['sender_email'] = $message['from_email'];
+ $obj['timestamp'] = $message['timestamp'];
+ $obj['date'] = $carbonTimestamp->format('d M Y h:i A');
+ $obj['datediff'] = $carbonTimestamp->diffForHumans(Carbon::now('UTC'));
+ $obj['id'] = $message['message_id'];
+ $obj['content'] = $message['body_html'];
+ $obj['contentText'] = $message['body_text'];
+ $obj['attachments'] = [];
+ $obj['is_seen'] = $message['is_seen'];
+ $obj['sender_photo'] = self::chooseColor(strtoupper(substr($message['from_name'] ?: $message['from_email'], 0, 1) ));
+
+
+ $domain = explode('@', $obj['sender_email'])[1];
+ $blocked = in_array($domain, json_decode(config('app.settings.configuration_settings'))->blocked_domains);
+ if ($blocked) {
+ $obj['subject'] = __('Blocked');
+ $obj['content'] = __('Emails from') . ' ' . $domain . ' ' . __('are blocked by Admin');
+ $obj['contentText'] = __('Emails from') . ' ' . $domain . ' ' . __('are blocked by Admin');
+ }
+
+ if (count($message['attachments']) > 0 && !$blocked) {
+ $obj['attachments'] = $message['attachments'];
+
+ }
+
+ $response['data'][] = $obj;
+ if (!$message['is_seen']) {
+ $response['notifications'][] = [
+ 'subject' => $obj['subject'],
+ 'sender_name' => $obj['sender_name'],
+ 'sender_email' => $obj['sender_email']
+ ];
+ if (config('app.zemail_log')) {
+ file_put_contents(storage_path('logs/zemail.csv'), request()->ip() . "," . date("Y-m-d h:i:s a") . "," . $obj['sender_email'] . "," . $email . PHP_EOL, FILE_APPEND);
+ }
+ }
+ PremiumEmail::where('message_id', $message['message_id'])->update(['is_seen' => true]);
+ if (++$count > $limit) {
+ break;
+ }
+ }
+ return $response;
+ }
+
+}
diff --git a/app/Models/UsageLog.php b/app/Models/UsageLog.php
new file mode 100644
index 0000000..5d8d9e6
--- /dev/null
+++ b/app/Models/UsageLog.php
@@ -0,0 +1,23 @@
+ 'array',
+ 'emails_received_history' => 'array',
+ ];
+
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index 81e5ffe..92958c1 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -8,11 +8,14 @@ use Filament\Panel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
+use Illuminate\Contracts\Auth\MustVerifyEmail;
+use Illuminate\Support\Str;
+use Laravel\Cashier\Billable;
-class User extends Authenticatable implements FilamentUser
+class User extends Authenticatable implements FilamentUser, MustVerifyEmail
{
/** @use HasFactory<\Database\Factories\UserFactory> */
- use HasFactory, Notifiable;
+ use HasFactory, Notifiable, Billable;
/**
* The attributes that are mass assignable.
@@ -48,6 +51,14 @@ class User extends Authenticatable implements FilamentUser
];
}
+ public function initials(): string
+ {
+ return Str::of($this->name)
+ ->explode(' ')
+ ->map(fn (string $name) => Str::of($name)->substr(0, 1))
+ ->implode('');
+ }
+
public function canAccessPanel(Panel $panel): bool
{
return str_ends_with($this->email, '@zemail.me') && $this->level === 9 && $this->hasVerifiedEmail();
diff --git a/app/Models/ZEmail.php b/app/Models/ZEmail.php
index 577c082..f35e168 100644
--- a/app/Models/ZEmail.php
+++ b/app/Models/ZEmail.php
@@ -114,6 +114,7 @@ class ZEmail extends Model
$settings = json_decode(config('app.settings.configuration_settings'), true);
$forbidden_ids = $settings['forbidden_ids'] ?? [];
$gmail_usernames = $settings['gmailUsernames'] ?? [];
+ $outlook_usernames = $settings['outlookUsernames'] ?? [];
$domains = $settings['domains'] ?? [];
if (in_array($username, $forbidden_ids)) {
@@ -124,13 +125,31 @@ class ZEmail extends Model
return ZEmail::generateRandomGmail(true);
}
+ if ($username === '' && in_array($domain, ['outlook.com'])) {
+ return ZEmail::generateRandomOutlook(true);
+ }
+
$zemail = new ZEmail();
if ($username === '' && in_array($domain, $domains)) {
return $zemail->generateRandomUsername().'@'.$domain;
}
+ if (in_array($domain, ['outlook.com'])) {
+ if (str_contains($username, '+')) {
+ [$check_username, $post_username] = explode('+', $username, 2);
+ if (in_array($check_username, $outlook_usernames)) {
+ $email = $username . '@' . $domain;
+ } else {
+ $email = $zemail->getRandomOutlookUser() . '+' . $post_username . '@' . $domain;
+ }
+ } else {
+ $email = $zemail->getRandomOutlookUser() . '+' . $username . '@' . $domain;
+ }
+ ZEmail::storeEmail($email);
+ return $email;
+ }
if (in_array($domain, ['gmail.com', 'googlemail.com'])) {
if (str_contains($username, '+')) {
@@ -190,7 +209,10 @@ class ZEmail extends Model
} else {
$email = $zemail->getRandomGmailUser().'+'.$zemail->generateRandomUsername().'@googlemail.com';
}
- } else {
+ } elseif ($domain == "outlook.com") {
+ $email = $zemail->getRandomOutlookUser().'+'.$zemail->generateRandomUsername().'@outlook.com';
+ }
+ else {
$email = $zemail->generateRandomUsername() . '@' . $domain;
}
if ($store) {
@@ -235,6 +257,15 @@ class ZEmail extends Model
return $email;
}
+ public static function generateRandomOutlook($store = true): string
+ {
+ $zemail = new ZEmail();
+ $email = $zemail->getRandomOutlookUser().'+'.$zemail->generateRandomUsername().'@outlook.com';
+ if ($store) {
+ ZEmail::storeEmail($email);
+ }
+ return $email;
+ }
private static function storeEmail($email): void
{
Log::create([
@@ -292,6 +323,11 @@ class ZEmail extends Model
$count = count($gmailusername);
return $count > 0 ? $gmailusername[rand(1, $count) - 1] : '';
}
+ private function getRandomOutlookUser() {
+ $outlook_username = json_decode(config('app.settings.configuration_settings'))->outlookUsernames ?? [];
+ $count = count($outlook_username);
+ return $count > 0 ? $outlook_username[rand(1, $count) - 1] : '';
+ }
private function generatePronounceableWord(): string
{
@@ -318,100 +354,3 @@ class ZEmail extends Model
}
}
-
-
-// public static function createCustomEmail($username, $domain): string
-// {
-// $username = str_replace('[^a-zA-Z0-9]', '', strtolower($username));
-// $forbidden_ids = json_decode(config('app.settings.configuration_settings'))->forbidden_ids ?? [];
-// $gmail_usernames = json_decode(config('app.settings.configuration_settings'))->gmailUsernames ?? [];
-// if (in_array($username, $forbidden_ids)) {
-// return ZEmail::generateRandomEmail(true);
-// }
-// $zemail = new ZEmail();
-// if($domain == "gmail.com") {
-//
-// if(str_contains($username, "+")){
-// $check_username = explode("+", $username)[0];
-// $post_username = explode("+", $username)[1];
-// if (in_array($check_username, $gmail_usernames)) {
-// $email = $username . '@gmail.com';
-// ZEmail::storeEmail($email);
-// return $email;
-// } else {
-//
-// $email = $zemail->getRandomGmailUser().'+'.$post_username.'@gmail.com';
-// ZEmail::storeEmail($email);
-// return $email;
-// }
-// }
-// elseif(str_contains($username, ".")){
-// $check_username = str_replace(".","",$username);
-//
-// if (in_array($check_username, $gmail_usernames)) {
-// $email = $username . '@gmail.com';
-// ZEmail::storeEmail($email);
-// return $email;
-// } else {
-//
-// $email = $zemail->generateRandomGmail();
-// ZEmail::storeEmail($email);
-// return $email;
-// }
-//
-// }
-// else {
-//
-// $email = $zemail->getRandomGmailUser().'+'.$username.'@gmail.com';
-// ZEmail::storeEmail($email);
-// return $email;
-// }
-// } elseif($domain == "googlemail.com") {
-// if(str_contains($username, "+")){
-// $check_username = explode("+", $username)[0];
-// $post_username = explode("+", $username)[1];
-//
-// if (in_array($check_username, $gmail_usernames)) {
-// $email = $username . '@googlemail.com';
-// ZEmail::storeEmail($email);
-// return $email;
-// } else {
-//
-// $email = $zemail->getRandomGmailUser().'+'.$post_username.'@googlemail.com';
-// ZEmail::storeEmail($email);
-// return $email;
-// }
-// }
-// elseif(str_contains($username, ".")){
-// $check_username = str_replace(".","",$username);
-//
-// if (in_array($check_username, $gmail_usernames)) {
-// $email = $username . '@googlemail.com';
-// ZEmail::storeEmail($email);
-// return $email;
-// } else {
-//
-// $email = $zemail->generateRandomGmail();
-// ZEmail::storeEmail($email);
-// return $email;
-// }
-// }
-// else {
-// $email = $zemail->getRandomGmailUser().'+'.$username.'@googlemail.com';
-// ZEmail::storeEmail($email);
-// return $email;
-// }
-// } else {
-// $domains = json_decode(config('app.settings.configuration_settings'))->domains ?? [];
-// if (in_array($domain, $domains)) {
-// $email = $username . '@' . $domain;
-// ZEmail::storeEmail($email);
-// return $email;
-// } else {
-// $email = $username . '@' . $domains[0];
-// ZEmail::storeEmail($email);
-// return $email;
-// }
-//
-// }
-// }
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 42c815c..236bec2 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -4,8 +4,10 @@ namespace App\Providers;
use App\Models\Blog;
use App\Models\Menu;
+use App\Models\Plan;
use DB;
use Illuminate\Support\ServiceProvider;
+use Laravel\Cashier\Cashier;
class AppServiceProvider extends ServiceProvider
{
@@ -33,8 +35,15 @@ class AppServiceProvider extends ServiceProvider
return Blog::where('is_published', 1)->get();
});
+ $plans = cache()->remember('app_plans', now()->addHours(6), function () {
+ return Plan::all();
+ });
+
config(['app.settings' => (array) $settings]);
config(['app.menus' => $menus]);
config(['app.blogs' => $blogs]);
+ config(['app.plans' => $plans]);
+
+ Cashier::calculateTaxes();
}
}
diff --git a/app/Providers/Filament/DashPanelProvider.php b/app/Providers/Filament/DashPanelProvider.php
index 6c5fe00..da68cae 100644
--- a/app/Providers/Filament/DashPanelProvider.php
+++ b/app/Providers/Filament/DashPanelProvider.php
@@ -24,8 +24,8 @@ class DashPanelProvider extends PanelProvider
{
return $panel
->default()
- ->id('dash')
- ->path('dash')
+ ->id('0xdash')
+ ->path('0xdash')
->login()
->font('Poppins')
->colors([
@@ -38,8 +38,8 @@ class DashPanelProvider extends PanelProvider
])
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
->widgets([
- Widgets\AccountWidget::class,
- Widgets\FilamentInfoWidget::class,
+ //Widgets\AccountWidget::class,
+ //Widgets\FilamentInfoWidget::class,
])
->middleware([
EncryptCookies::class,
diff --git a/bootstrap/app.php b/bootstrap/app.php
index 94f56e3..8ea7f1a 100644
--- a/bootstrap/app.php
+++ b/bootstrap/app.php
@@ -14,6 +14,9 @@ return Application::configure(basePath: dirname(__DIR__))
$middleware->web(append: [
\App\Http\Middleware\Locale::class,
]);
+ $middleware->validateCsrfTokens(except: [
+ 'stripe/*',
+ ]);
})
->withExceptions(function (Exceptions $exceptions) {
//
diff --git a/cleanCron.php b/cleanCron.php
new file mode 100644
index 0000000..ffa763d
--- /dev/null
+++ b/cleanCron.php
@@ -0,0 +1,24 @@
+make(Illuminate\Contracts\Console\Kernel::class);
+
+try {
+ // Run the Artisan command 'ping'
+ $exitCode = $kernel->call('cleanMail');
+
+ // Get the output of the command
+ $output = $kernel->output();
+
+ echo "Artisan command 'schedule:run' executed successfully. Exit code: $exitCode\n";
+ echo "Output:\n$output";
+
+} catch (\Exception $e) {
+ echo "Error running Artisan command: " . $e->getMessage();
+}
diff --git a/composer.json b/composer.json
index 73264b9..4be86c4 100644
--- a/composer.json
+++ b/composer.json
@@ -7,13 +7,15 @@
"license": "MIT",
"require": {
"php": "^8.2",
+ "ext-imap": "*",
"ddeboer/imap": "^1.14",
"filament/filament": "3.3",
+ "laravel/cashier": "^15.6",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1",
"livewire/flux": "^2.1",
"livewire/livewire": "^3.6",
- "ext-imap": "*"
+ "propaganistas/laravel-disposable-email": "^2.4"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.15",
diff --git a/composer.lock b/composer.lock
index 122a729..f87f7f7 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "b4dcaf149eb8c0291c1de5ccd42c8f94",
+ "content-hash": "972e884837f3870524619dc37aa08d0f",
"packages": [
{
"name": "anourvalar/eloquent-serialize",
@@ -2100,6 +2100,94 @@
},
"time": "2025-04-01T14:41:56+00:00"
},
+ {
+ "name": "laravel/cashier",
+ "version": "v15.6.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/cashier-stripe.git",
+ "reference": "8fe60cc71161ef06b6a1b23cffe886abf2a49b29"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/cashier-stripe/zipball/8fe60cc71161ef06b6a1b23cffe886abf2a49b29",
+ "reference": "8fe60cc71161ef06b6a1b23cffe886abf2a49b29",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "illuminate/console": "^10.0|^11.0|^12.0",
+ "illuminate/contracts": "^10.0|^11.0|^12.0",
+ "illuminate/database": "^10.0|^11.0|^12.0",
+ "illuminate/http": "^10.0|^11.0|^12.0",
+ "illuminate/log": "^10.0|^11.0|^12.0",
+ "illuminate/notifications": "^10.0|^11.0|^12.0",
+ "illuminate/pagination": "^10.0|^11.0|^12.0",
+ "illuminate/routing": "^10.0|^11.0|^12.0",
+ "illuminate/support": "^10.0|^11.0|^12.0",
+ "illuminate/view": "^10.0|^11.0|^12.0",
+ "moneyphp/money": "^4.0",
+ "nesbot/carbon": "^2.0|^3.0",
+ "php": "^8.1",
+ "stripe/stripe-php": "^16.2",
+ "symfony/console": "^6.0|^7.0",
+ "symfony/http-kernel": "^6.0|^7.0",
+ "symfony/polyfill-intl-icu": "^1.22.1"
+ },
+ "require-dev": {
+ "dompdf/dompdf": "^2.0",
+ "mockery/mockery": "^1.0",
+ "orchestra/testbench": "^8.18|^9.0|^10.0",
+ "phpstan/phpstan": "^1.10",
+ "phpunit/phpunit": "^10.4|^11.5"
+ },
+ "suggest": {
+ "dompdf/dompdf": "Required when generating and downloading invoice PDF's using Dompdf (^1.0.1|^2.0).",
+ "ext-intl": "Allows for more locales besides the default \"en\" when formatting money values."
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Laravel\\Cashier\\CashierServiceProvider"
+ ]
+ },
+ "branch-alias": {
+ "dev-master": "15.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\Cashier\\": "src/",
+ "Laravel\\Cashier\\Database\\Factories\\": "database/factories/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ },
+ {
+ "name": "Dries Vints",
+ "email": "dries@laravel.com"
+ }
+ ],
+ "description": "Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.",
+ "keywords": [
+ "billing",
+ "laravel",
+ "stripe"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/cashier/issues",
+ "source": "https://github.com/laravel/cashier"
+ },
+ "time": "2025-04-22T13:59:36+00:00"
+ },
{
"name": "laravel/framework",
"version": "v12.10.2",
@@ -2503,16 +2591,16 @@
},
{
"name": "league/commonmark",
- "version": "2.6.2",
+ "version": "2.7.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
- "reference": "06c3b0bf2540338094575612f4a1778d0d2d5e94"
+ "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/06c3b0bf2540338094575612f4a1778d0d2d5e94",
- "reference": "06c3b0bf2540338094575612f4a1778d0d2d5e94",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
+ "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
"shasum": ""
},
"require": {
@@ -2549,7 +2637,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "2.7-dev"
+ "dev-main": "2.8-dev"
}
},
"autoload": {
@@ -2606,7 +2694,7 @@
"type": "tidelift"
}
],
- "time": "2025-04-18T21:09:27+00:00"
+ "time": "2025-05-05T12:20:28+00:00"
},
{
"name": "league/config",
@@ -3349,6 +3437,96 @@
},
"time": "2024-03-31T07:05:07+00:00"
},
+ {
+ "name": "moneyphp/money",
+ "version": "v4.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/moneyphp/money.git",
+ "reference": "af048f0206d3b39b8fad9de6a230cedf765365fa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/moneyphp/money/zipball/af048f0206d3b39b8fad9de6a230cedf765365fa",
+ "reference": "af048f0206d3b39b8fad9de6a230cedf765365fa",
+ "shasum": ""
+ },
+ "require": {
+ "ext-bcmath": "*",
+ "ext-filter": "*",
+ "ext-json": "*",
+ "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
+ },
+ "require-dev": {
+ "cache/taggable-cache": "^1.1.0",
+ "doctrine/coding-standard": "^12.0",
+ "doctrine/instantiator": "^1.5.0 || ^2.0",
+ "ext-gmp": "*",
+ "ext-intl": "*",
+ "florianv/exchanger": "^2.8.1",
+ "florianv/swap": "^4.3.0",
+ "moneyphp/crypto-currencies": "^1.1.0",
+ "moneyphp/iso-currencies": "^3.4",
+ "php-http/message": "^1.16.0",
+ "php-http/mock-client": "^1.6.0",
+ "phpbench/phpbench": "^1.2.5",
+ "phpstan/extension-installer": "^1.4",
+ "phpstan/phpstan": "^2.1.9",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^10.5.9",
+ "psr/cache": "^1.0.1 || ^2.0 || ^3.0",
+ "ticketswap/phpstan-error-formatter": "^1.1"
+ },
+ "suggest": {
+ "ext-gmp": "Calculate without integer limits",
+ "ext-intl": "Format Money objects with intl",
+ "florianv/exchanger": "Exchange rates library for PHP",
+ "florianv/swap": "Exchange rates library for PHP",
+ "psr/cache-implementation": "Used for Currency caching"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Money\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mathias Verraes",
+ "email": "mathias@verraes.net",
+ "homepage": "http://verraes.net"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com"
+ },
+ {
+ "name": "Frederik Bosch",
+ "email": "f.bosch@genkgo.nl"
+ }
+ ],
+ "description": "PHP implementation of Fowler's Money pattern",
+ "homepage": "http://moneyphp.org",
+ "keywords": [
+ "Value Object",
+ "money",
+ "vo"
+ ],
+ "support": {
+ "issues": "https://github.com/moneyphp/money/issues",
+ "source": "https://github.com/moneyphp/money/tree/v4.7.0"
+ },
+ "time": "2025-04-03T08:26:36+00:00"
+ },
{
"name": "monolog/monolog",
"version": "3.9.0",
@@ -4019,6 +4197,82 @@
],
"time": "2024-07-20T21:41:07+00:00"
},
+ {
+ "name": "propaganistas/laravel-disposable-email",
+ "version": "2.4.14",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Propaganistas/Laravel-Disposable-Email.git",
+ "reference": "a1d50a51cb8ec13596a477e2a1bf35f47fa6b88d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Propaganistas/Laravel-Disposable-Email/zipball/a1d50a51cb8ec13596a477e2a1bf35f47fa6b88d",
+ "reference": "a1d50a51cb8ec13596a477e2a1bf35f47fa6b88d",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "illuminate/cache": "^10.0|^11.0|^12.0",
+ "illuminate/config": "^10.0|^11.0|^12.0",
+ "illuminate/console": "^10.0|^11.0|^12.0",
+ "illuminate/contracts": "^10.0|^11.0|^12.0",
+ "illuminate/support": "^10.0|^11.0|^12.0",
+ "illuminate/validation": "^10.0|^11.0|^12.0",
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "laravel/pint": "^1.14",
+ "mockery/mockery": "^1.4.2",
+ "orchestra/testbench": "*",
+ "phpunit/phpunit": "^10.5|^11.5.3"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Propaganistas\\LaravelDisposableEmail\\DisposableEmailServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Propaganistas\\LaravelDisposableEmail\\": "src/",
+ "Propaganistas\\LaravelDisposableEmail\\Tests\\": "tests/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Propaganistas",
+ "email": "Propaganistas@users.noreply.github.com"
+ }
+ ],
+ "description": "Disposable email validator",
+ "keywords": [
+ "disposable",
+ "email",
+ "laravel",
+ "mail",
+ "temporary",
+ "throwaway",
+ "validator"
+ ],
+ "support": {
+ "issues": "https://github.com/Propaganistas/Laravel-Disposable-Email/issues",
+ "source": "https://github.com/Propaganistas/Laravel-Disposable-Email/tree/2.4.14"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Propaganistas",
+ "type": "github"
+ }
+ ],
+ "time": "2025-05-01T00:56:00+00:00"
+ },
{
"name": "psr/cache",
"version": "3.0.0",
@@ -5028,6 +5282,65 @@
],
"time": "2025-04-11T15:27:14+00:00"
},
+ {
+ "name": "stripe/stripe-php",
+ "version": "v16.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/stripe/stripe-php.git",
+ "reference": "d6de0a536f00b5c5c74f36b8f4d0d93b035499ff"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/stripe/stripe-php/zipball/d6de0a536f00b5c5c74f36b8f4d0d93b035499ff",
+ "reference": "d6de0a536f00b5c5c74f36b8f4d0d93b035499ff",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "php": ">=5.6.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "3.5.0",
+ "phpstan/phpstan": "^1.2",
+ "phpunit/phpunit": "^5.7 || ^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Stripe\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Stripe and contributors",
+ "homepage": "https://github.com/stripe/stripe-php/contributors"
+ }
+ ],
+ "description": "Stripe PHP Library",
+ "homepage": "https://stripe.com/",
+ "keywords": [
+ "api",
+ "payment processing",
+ "stripe"
+ ],
+ "support": {
+ "issues": "https://github.com/stripe/stripe-php/issues",
+ "source": "https://github.com/stripe/stripe-php/tree/v16.6.0"
+ },
+ "time": "2025-02-24T22:35:29+00:00"
+ },
{
"name": "symfony/clock",
"version": "v7.2.0",
@@ -6204,6 +6517,90 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
+ {
+ "name": "symfony/polyfill-intl-icu",
+ "version": "v1.32.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-icu.git",
+ "reference": "763d2a91fea5681509ca01acbc1c5e450d127811"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/763d2a91fea5681509ca01acbc1c5e450d127811",
+ "reference": "763d2a91fea5681509ca01acbc1c5e450d127811",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance and support of other locales than \"en\""
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Icu\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ],
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's ICU-related data and classes",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "icu",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.32.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-12-21T18:38:29+00:00"
+ },
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.31.0",
diff --git a/config/cashier.php b/config/cashier.php
new file mode 100644
index 0000000..4a9b024
--- /dev/null
+++ b/config/cashier.php
@@ -0,0 +1,127 @@
+ env('STRIPE_KEY'),
+
+ 'secret' => env('STRIPE_SECRET'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cashier Path
+ |--------------------------------------------------------------------------
+ |
+ | This is the base URI path where Cashier's views, such as the payment
+ | verification screen, will be available from. You're free to tweak
+ | this path according to your preferences and application design.
+ |
+ */
+
+ 'path' => env('CASHIER_PATH', 'stripe'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Stripe Webhooks
+ |--------------------------------------------------------------------------
+ |
+ | Your Stripe webhook secret is used to prevent unauthorized requests to
+ | your Stripe webhook handling controllers. The tolerance setting will
+ | check the drift between the current time and the signed request's.
+ |
+ */
+
+ 'webhook' => [
+ 'secret' => env('STRIPE_WEBHOOK_SECRET'),
+ 'tolerance' => env('STRIPE_WEBHOOK_TOLERANCE', 300),
+ 'events' => WebhookCommand::DEFAULT_EVENTS,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Currency
+ |--------------------------------------------------------------------------
+ |
+ | This is the default currency that will be used when generating charges
+ | from your application. Of course, you are welcome to use any of the
+ | various world currencies that are currently supported via Stripe.
+ |
+ */
+
+ 'currency' => env('CASHIER_CURRENCY', 'usd'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Currency Locale
+ |--------------------------------------------------------------------------
+ |
+ | This is the default locale in which your money values are formatted in
+ | for display. To utilize other locales besides the default en locale
+ | verify you have the "intl" PHP extension installed on the system.
+ |
+ */
+
+ 'currency_locale' => env('CASHIER_CURRENCY_LOCALE', 'en'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Payment Confirmation Notification
+ |--------------------------------------------------------------------------
+ |
+ | If this setting is enabled, Cashier will automatically notify customers
+ | whose payments require additional verification. You should listen to
+ | Stripe's webhooks in order for this feature to function correctly.
+ |
+ */
+
+ 'payment_notification' => env('CASHIER_PAYMENT_NOTIFICATION'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Invoice Settings
+ |--------------------------------------------------------------------------
+ |
+ | The following options determine how Cashier invoices are converted from
+ | HTML into PDFs. You're free to change the options based on the needs
+ | of your application or your preferences regarding invoice styling.
+ |
+ */
+
+ 'invoices' => [
+ 'renderer' => env('CASHIER_INVOICE_RENDERER', DompdfInvoiceRenderer::class),
+
+ 'options' => [
+ // Supported: 'letter', 'legal', 'A4'
+ 'paper' => env('CASHIER_PAPER', 'letter'),
+
+ 'remote_enabled' => env('CASHIER_REMOTE_ENABLED', false),
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Stripe Logger
+ |--------------------------------------------------------------------------
+ |
+ | This setting defines which logging channel will be used by the Stripe
+ | library to write log messages. You are free to specify any of your
+ | logging channels listed inside the "logging" configuration file.
+ |
+ */
+
+ 'logger' => env('CASHIER_LOGGER'),
+
+];
diff --git a/config/disposable-email.php b/config/disposable-email.php
new file mode 100644
index 0000000..b140e47
--- /dev/null
+++ b/config/disposable-email.php
@@ -0,0 +1,100 @@
+ [
+ 'https://cdn.jsdelivr.net/gh/disposable/disposable-email-domains@master/domains.json',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Fetch class
+ |--------------------------------------------------------------------------
+ |
+ | The class responsible for fetching the contents of the source url.
+ | The default implementation makes use of file_get_contents and
+ | json_decode and will probably suffice for most applications.
+ |
+ | If your application has different needs (e.g. behind a proxy) then you
+ | can define a custom fetch class here that carries out the fetching.
+ | Your custom class should implement the Fetcher contract.
+ |
+ */
+
+ 'fetcher' => \Propaganistas\LaravelDisposableEmail\Fetcher\DefaultFetcher::class,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Storage Path
+ |--------------------------------------------------------------------------
+ |
+ | The location where the retrieved domains list should be stored locally.
+ | The path should be accessible and writable by the web server. A good
+ | place for storing the list is in the framework's own storage path.
+ |
+ */
+
+ 'storage' => storage_path('framework/disposable_domains.json'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Whitelist Configuration
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define a list of whitelist domains that should be allowed.
+ | These domains will be removed from the list of disposable domains.
+ |
+ | Insert as "mydomain.com", without the @ symbol.
+ |
+ */
+
+ 'whitelist' => [],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Include Subdomains
+ |--------------------------------------------------------------------------
+ |
+ | Determines whether subdomains should be validated based on the disposability
+ | status of their parent domains. Enabling this will treat any subdomain of
+ | a disposable domain as disposable too (e.g., 'temp.abc.com' if 'abc.com'
+ | is disposable).
+ |
+ */
+
+ 'include_subdomains' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cache Configuration
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define whether the disposable domains list should be cached.
+ | If you disable caching or when the cache is empty, the list will be
+ | fetched from local storage instead.
+ |
+ | You can optionally specify an alternate cache connection or modify the
+ | cache key as desired.
+ |
+ */
+
+ 'cache' => [
+ 'enabled' => true,
+ 'store' => 'default',
+ 'key' => 'disposable_email:domains',
+ ],
+
+];
diff --git a/database/migrations/2025_05_02_144314_create_customer_columns.php b/database/migrations/2025_05_02_144314_create_customer_columns.php
new file mode 100644
index 0000000..974b381
--- /dev/null
+++ b/database/migrations/2025_05_02_144314_create_customer_columns.php
@@ -0,0 +1,40 @@
+string('stripe_id')->nullable()->index();
+ $table->string('pm_type')->nullable();
+ $table->string('pm_last_four', 4)->nullable();
+ $table->timestamp('trial_ends_at')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->dropIndex([
+ 'stripe_id',
+ ]);
+
+ $table->dropColumn([
+ 'stripe_id',
+ 'pm_type',
+ 'pm_last_four',
+ 'trial_ends_at',
+ ]);
+ });
+ }
+};
diff --git a/database/migrations/2025_05_02_144315_create_subscriptions_table.php b/database/migrations/2025_05_02_144315_create_subscriptions_table.php
new file mode 100644
index 0000000..ccbcc6d
--- /dev/null
+++ b/database/migrations/2025_05_02_144315_create_subscriptions_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->foreignId('user_id');
+ $table->string('type');
+ $table->string('stripe_id')->unique();
+ $table->string('stripe_status');
+ $table->string('stripe_price')->nullable();
+ $table->integer('quantity')->nullable();
+ $table->timestamp('trial_ends_at')->nullable();
+ $table->timestamp('ends_at')->nullable();
+ $table->timestamps();
+
+ $table->index(['user_id', 'stripe_status']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('subscriptions');
+ }
+};
diff --git a/database/migrations/2025_05_02_144316_create_subscription_items_table.php b/database/migrations/2025_05_02_144316_create_subscription_items_table.php
new file mode 100644
index 0000000..420e23f
--- /dev/null
+++ b/database/migrations/2025_05_02_144316_create_subscription_items_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->foreignId('subscription_id');
+ $table->string('stripe_id')->unique();
+ $table->string('stripe_product');
+ $table->string('stripe_price');
+ $table->integer('quantity')->nullable();
+ $table->timestamps();
+
+ $table->index(['subscription_id', 'stripe_price']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('subscription_items');
+ }
+};
diff --git a/database/migrations/2025_05_02_215351_create_plans_table.php b/database/migrations/2025_05_02_215351_create_plans_table.php
new file mode 100644
index 0000000..a84ea17
--- /dev/null
+++ b/database/migrations/2025_05_02_215351_create_plans_table.php
@@ -0,0 +1,35 @@
+id();
+ $table->string('name');
+ $table->text('description')->nullable();
+ $table->string('product_id')->collation('utf8_bin');
+ $table->string('pricing_id')->collation('utf8_bin');
+ $table->integer('price');
+ $table->integer('mailbox_limit')->default(15);
+ $table->boolean('monthly_billing')->default(true);
+ $table->longText('details')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('plans');
+ }
+};
diff --git a/database/migrations/2025_05_03_082522_create_usage_logs_table.php b/database/migrations/2025_05_03_082522_create_usage_logs_table.php
new file mode 100644
index 0000000..2221f5b
--- /dev/null
+++ b/database/migrations/2025_05_03_082522_create_usage_logs_table.php
@@ -0,0 +1,36 @@
+bigIncrements('id');
+ $table->unsignedBigInteger('user_id');
+ $table->ipAddress('ip_address');
+ $table->integer('emails_created_count')->default(0);
+ $table->integer('emails_received_count')->default(0);
+ $table->json('emails_created_history')->nullable();
+ $table->json('emails_received_history')->nullable();
+ $table->timestamps();
+
+ $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
+
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('usage_logs');
+ }
+};
diff --git a/database/migrations/2025_05_03_200503_add_user_id_to_logs_table.php b/database/migrations/2025_05_03_200503_add_user_id_to_logs_table.php
new file mode 100644
index 0000000..1436082
--- /dev/null
+++ b/database/migrations/2025_05_03_200503_add_user_id_to_logs_table.php
@@ -0,0 +1,30 @@
+unsignedBigInteger('user_id')->nullable()->after('id');
+ $table->foreign('user_id')->references('id')->on('users');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('logs', function (Blueprint $table) {
+ $table->dropForeign(['user_id']);
+ $table->dropColumn('user_id');
+ });
+ }
+};
diff --git a/database/migrations/2025_05_05_212255_create_premium_emails_table.php b/database/migrations/2025_05_05_212255_create_premium_emails_table.php
new file mode 100644
index 0000000..85b6f95
--- /dev/null
+++ b/database/migrations/2025_05_05_212255_create_premium_emails_table.php
@@ -0,0 +1,47 @@
+id();
+ $table->unsignedBigInteger('user_id');
+ $table->string('message_id')->unique()->index();
+ $table->string('subject')->nullable();
+ $table->string('from_name')->nullable();
+ $table->string('from_email');
+ $table->text('to');
+ $table->text('cc')->nullable();
+ $table->text('bcc')->nullable();
+ $table->dateTime('timestamp')->nullable();
+ $table->longText('body_text')->nullable();
+ $table->longText('body_html')->nullable();
+ $table->boolean('is_seen')->default(false);
+ $table->boolean('is_flagged')->default(false);
+ $table->unsignedBigInteger('size')->nullable();
+ $table->string('mailbox')->default('INBOX');
+ $table->longText('raw_headers')->nullable();
+ $table->longText('raw_body')->nullable();
+ $table->json('attachments')->nullable();
+ $table->timestamps();
+
+ $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('premium_emails');
+ }
+};
diff --git a/dropattach.php b/dropattach.php
new file mode 100644
index 0000000..bb8f51f
--- /dev/null
+++ b/dropattach.php
@@ -0,0 +1,41 @@
+";
+ } else {
+ echo "Error deleting file '$file'.
";
+ }
+ }
+ }
+ }
+ // Close the directory handle
+ closedir($handle);
+ // Attempt to remove the directory itself
+ if (rmdir($dir)) {
+ echo "Directory '$dir' deleted successfully.
";
+ } else {
+ echo "Error deleting directory '$dir'.
";
+ }
+ }
+}
+
+$folderPath = '/home/u146541135/domains/zemail.me/public_html/public/tmp/attachments/';
+
+// Ensure the folder path exists and is a directory
+if (is_dir($folderPath)) {
+ deleteFiles($folderPath);
+} else {
+ echo "Folder '$folderPath' does not exist or is not a directory.";
+}
+?>
diff --git a/dropattachP.php b/dropattachP.php
new file mode 100644
index 0000000..a5fbb0f
--- /dev/null
+++ b/dropattachP.php
@@ -0,0 +1,41 @@
+";
+ } else {
+ echo "Error deleting file '$file'.
";
+ }
+ }
+ }
+ }
+ // Close the directory handle
+ closedir($handle);
+ // Attempt to remove the directory itself
+ if (rmdir($dir)) {
+ echo "Directory '$dir' deleted successfully.
";
+ } else {
+ echo "Error deleting directory '$dir'.
";
+ }
+ }
+}
+
+$folderPath = '/home/u146541135/domains/zemail.me/public_html/public/tmp/premium/attachments/';
+
+// Ensure the folder path exists and is a directory
+if (is_dir($folderPath)) {
+ deleteFiles($folderPath);
+} else {
+ echo "Folder '$folderPath' does not exist or is not a directory.";
+}
+?>
diff --git a/dropmail.php b/dropmail.php
new file mode 100644
index 0000000..2adc3d4
--- /dev/null
+++ b/dropmail.php
@@ -0,0 +1,80 @@
+make(Illuminate\Contracts\Console\Kernel::class);
+$kernel->bootstrap();
+
+set_time_limit(0);
+
+$newTimezone = 'Europe/London';
+date_default_timezone_set($newTimezone);
+
+$imapDB = json_decode(config('app.settings.imap_settings'), true);
+
+// Mailbox credentials
+$hostname = '{'.$imapDB['host'].':'.$imapDB['port'].'/ssl}INBOX';
+$username = $imapDB['username'];
+$password = $imapDB['password'];
+
+// Connect to mailbox
+$inbox = imap_open($hostname, $username, $password);
+
+// Check for connection errors
+if (!$inbox) {
+ die('Could not connect to mailbox: ' . imap_last_error());
+}
+
+// Get current time in Unix timestamp
+$current_time = time();
+
+// Search for messages older than one day
+//$search_criteria = 'BEFORE "' . date('d-M-Y', strtotime('-3 hours', $current_time)) . '"';
+//$messages = imap_search($inbox, $search_criteria);
+
+$messages = imap_search($inbox, 'ALL');
+
+$batch_size = 10;
+$deleted_count = 0;
+
+//if ($messages) {
+// $chunks = array_chunk($messages, $batch_size);
+// foreach ($chunks as $chunk) {
+// foreach ($chunk as $message_number) {
+// imap_delete($inbox, $message_number);
+// }
+// imap_expunge($inbox);
+// $deleted_count += count($chunk);
+// }
+// echo $deleted_count . ' messages older than specified time have been deleted.';
+//} else {
+// echo 'No messages older than specified time found in mailbox.';
+//}
+
+if ($messages) {
+ $chunks = array_chunk($messages, $batch_size);
+ foreach ($chunks as $chunk) {
+ foreach ($chunk as $message_number) {
+ // Get message header to fetch internal date
+ $header = imap_headerinfo($inbox, $message_number);
+ $date_str = $header->date;
+ $msg_time = strtotime($date_str);
+
+ // Check if message is older than 3 hours
+ if ($msg_time !== false && ($current_time - $msg_time) > 2 * 3600) {
+ imap_delete($inbox, $message_number);
+ $deleted_count++;
+ }
+ }
+ imap_expunge($inbox);
+ }
+ echo $deleted_count . ' messages older than 2 hours have been deleted.';
+} else {
+ echo 'No messages found in mailbox.';
+}
+
+// Close mailbox connection
+imap_close($inbox);
diff --git a/dropmailP.php b/dropmailP.php
new file mode 100644
index 0000000..83d130d
--- /dev/null
+++ b/dropmailP.php
@@ -0,0 +1,57 @@
+make(Illuminate\Contracts\Console\Kernel::class);
+$kernel->bootstrap();
+
+set_time_limit(0);
+
+$newTimezone = 'Europe/London';
+date_default_timezone_set($newTimezone);
+
+$imapDB = json_decode(config('app.settings.imap_settings'), true);
+
+// Mailbox credentials
+$hostname = '{'.$imapDB['premium_host'].':'.$imapDB['premium_port'].'/ssl}INBOX';
+$username = $imapDB['premium_username'];
+$password = $imapDB['premium_password'];
+
+// Connect to mailbox
+$inbox = imap_open($hostname, $username, $password);
+
+// Check for connection errors
+if (!$inbox) {
+ die('Could not connect to mailbox: ' . imap_last_error());
+}
+
+// Get current time in Unix timestamp
+$current_time = time();
+
+// Search for messages older than one day
+$search_criteria = 'BEFORE "' . date('d-M-Y', strtotime('-3 hours', $current_time)) . '"';
+$messages = imap_search($inbox, $search_criteria);
+
+$batch_size = 10;
+$deleted_count = 0;
+
+if ($messages) {
+ $chunks = array_chunk($messages, $batch_size);
+ foreach ($chunks as $chunk) {
+ foreach ($chunk as $message_number) {
+ imap_delete($inbox, $message_number);
+ }
+ imap_expunge($inbox);
+ $deleted_count += count($chunk);
+ }
+ echo $deleted_count . ' messages older than specified time have been deleted.';
+} else {
+ echo 'No messages older than specified time found in mailbox.';
+}
+
+// Close mailbox connection
+imap_close($inbox);
+
diff --git a/inCron.php b/inCron.php
index 05837e9..138f564 100644
--- a/inCron.php
+++ b/inCron.php
@@ -11,12 +11,12 @@ $kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
try {
// Run the Artisan command 'ping'
- $exitCode = $kernel->call('ping');
+ $exitCode = $kernel->call('schedule:run');
// Get the output of the command
$output = $kernel->output();
- echo "Artisan command 'ping' executed successfully. Exit code: $exitCode\n";
+ echo "Artisan command 'schedule:run' executed successfully. Exit code: $exitCode\n";
echo "Output:\n$output";
} catch (\Exception $e) {
diff --git a/indisposableCron.php b/indisposableCron.php
new file mode 100644
index 0000000..357418f
--- /dev/null
+++ b/indisposableCron.php
@@ -0,0 +1,24 @@
+make(Illuminate\Contracts\Console\Kernel::class);
+
+try {
+ // Run the Artisan command 'ping'
+ $exitCode = $kernel->call('disposable:update');
+
+ // Get the output of the command
+ $output = $kernel->output();
+
+ echo "Artisan command 'schedule:run' executed successfully. Exit code: $exitCode\n";
+ echo "Output:\n$output";
+
+} catch (\Exception $e) {
+ echo "Error running Artisan command: " . $e->getMessage();
+}
diff --git a/public/addOnFAQs.json b/public/addOnFAQs.json
new file mode 100644
index 0000000..d8cf406
--- /dev/null
+++ b/public/addOnFAQs.json
@@ -0,0 +1,198 @@
+{
+ "disposable-email": [
+ {
+ "title": "What is a disposable email?",
+ "content": "A disposable email is a temporary email address that can be used for short-term purposes like sign-ups, testing, or avoiding spam. It automatically expires after a set time."
+ },
+ {
+ "title": "Why should I use a disposable email address?",
+ "content": "Using a disposable email helps protect your personal inbox from spam, phishing, or unwanted marketing emails. It’s a privacy-first solution."
+ },
+ {
+ "title": "Are disposable email services free?",
+ "content": "Yes, most disposable email services are free to use. They don’t require sign-ups or personal information."
+ },
+ {
+ "title": "Can I send emails from a disposable address?",
+ "content": "Some services allow sending emails, but most focus on receiving emails only to reduce spam and abuse."
+ },
+ {
+ "title": "How long does a disposable email address last?",
+ "content": "Typically, disposable email addresses last from a few minutes to 24 hours, depending on the service provider."
+ },
+ {
+ "title": "Is it legal to use disposable email addresses?",
+ "content": "Yes, it is legal to use disposable emails for legitimate purposes like testing or protecting privacy. It's not intended for fraudulent use."
+ }
+ ],
+
+ "disposable-gmail": [
+ {
+ "title": "What is a disposable Gmail email?",
+ "content": "A disposable Gmail email is a temporary Gmail address that lets you receive messages without needing to log in to Gmail or sign up."
+ },
+ {
+ "title": "Can I use a disposable Gmail for sign-ups?",
+ "content": "Yes, you can use it for sign-ups, app trials, or websites that require email verification but you don't want to share your real Gmail."
+ },
+ {
+ "title": "How do disposable Gmail addresses work?",
+ "content": "They simulate Gmail inboxes through public domain inboxes. You receive emails in a shared inbox that’s cleared frequently."
+ },
+ {
+ "title": "Do I need a Google account for disposable Gmail?",
+ "content": "No, you don’t need a Google account. These addresses are managed by third-party services that mimic Gmail inboxes."
+ },
+ {
+ "title": "Is using a temporary Gmail safe?",
+ "content": "Yes, as long as you use it for legal and ethical reasons. The inbox is public, so avoid receiving sensitive data."
+ }
+ ],
+
+ "disposable-outlook": [
+ {
+ "title": "What is a disposable Outlook email?",
+ "content": "A disposable Outlook email provides a temporary email address that mimics an Outlook.com address without needing to create a Microsoft account."
+ },
+ {
+ "title": "Can I get a free temporary Outlook inbox?",
+ "content": "Yes, several services offer free access to Outlook-like temporary inboxes for quick sign-ups and verification emails."
+ },
+ {
+ "title": "Is disposable Outlook email secure?",
+ "content": "These inboxes are typically public and auto-deleted. They are secure for one-time use, but not for private communication."
+ },
+ {
+ "title": "Where can I use a disposable Outlook email?",
+ "content": "Use it for registering on forums, testing apps, or any place where you don’t want to use your real Outlook address."
+ },
+ {
+ "title": "Can I receive attachments in a temporary Outlook email?",
+ "content": "Some disposable Outlook services support attachments, but many limit functionality for safety and storage reasons."
+ }
+ ],
+
+ "disposable-yahoo": [
+ {
+ "title": "What is a disposable Yahoo email?",
+ "content": "A disposable Yahoo email is a temporary email address formatted like a Yahoo inbox, used for short-term purposes."
+ },
+ {
+ "title": "Is there a free way to get a Yahoo-style temporary email?",
+ "content": "Yes, you can use services that provide Yahoo-like disposable email addresses for receiving emails online."
+ },
+ {
+ "title": "Why use a disposable Yahoo email?",
+ "content": "It helps you avoid giving your personal Yahoo email to websites that may spam or sell your data."
+ },
+ {
+ "title": "Can I use a disposable Yahoo email for email verification?",
+ "content": "Yes, it’s commonly used to receive verification links for one-time use without exposing your real Yahoo account."
+ },
+ {
+ "title": "Do I need a Yahoo account to use these addresses?",
+ "content": "No, these are provided by third-party services and don’t require a Yahoo login."
+ }
+ ],
+
+ "gmailnator": [
+ {
+ "title": "What is Gmailnator?",
+ "content": "Gmailnator is a free service that gives you a temporary Gmail email address for receiving emails online without signing up."
+ },
+ {
+ "title": "Is Gmailnator safe to use?",
+ "content": "Yes, Gmailnator provides disposable inboxes that are automatically deleted after use, protecting your identity and email privacy."
+ },
+ {
+ "title": "Can I use Gmailnator for account verification?",
+ "content": "Yes, many users use Gmailnator to verify accounts, sign up for trials, or test web apps without revealing their personal Gmail address."
+ },
+ {
+ "title": "How long does a Gmailnator inbox last?",
+ "content": "Inbox lifespan varies, but most Gmailnator inboxes exist for a short duration and are cleaned regularly."
+ },
+ {
+ "title": "Is Gmailnator private?",
+ "content": "Gmailnator is not fully private as inboxes may be accessible to others. Avoid using it for confidential information."
+ },
+ {
+ "title": "What types of emails can I receive with Gmailnator?",
+ "content": "You can receive verification emails, sign-up confirmations, and basic transactional emails through Gmailnator."
+ }
+ ],
+
+ "emailnator": [
+ {
+ "title": "What is Emailnator?",
+ "content": "Emailnator is a free disposable email service that lets you generate temporary email addresses for sign-ups, verification codes, or spam control. No registration required."
+ },
+ {
+ "title": "Is Emailnator safe to use?",
+ "content": "Yes, Emailnator is safe for temporary usage. It keeps your personal email private by offering short-lived, anonymous inboxes."
+ },
+ {
+ "title": "Can I use Emailnator for verification?",
+ "content": "Absolutely. Many users rely on Emailnator to receive verification emails from websites, apps, and newsletters."
+ },
+ {
+ "title": "How long do Emailnator inboxes last?",
+ "content": "Emailnator inboxes are active temporarily, usually lasting between 10 minutes and a few hours. They're automatically deleted after that to ensure privacy."
+ },
+ {
+ "title": "Do I need to sign up to use Emailnator?",
+ "content": "No. Emailnator is completely free and doesn't require any account registration. Just open the site, and your inbox is ready."
+ },
+ {
+ "title": "Does Emailnator support Gmail addresses?",
+ "content": "Yes, Emailnator uses Gmail domains that work with most sign-up forms, making it more reliable than traditional temp mail tools."
+ }
+ ],
+
+ "temp-gmail": [
+ {
+ "title": "What is a temporary Gmail disposable email service?",
+ "content": "It is a service that generates temporary, throwaway email addresses resembling Gmail addresses, allowing users to receive emails such as sign-up verifications or newsletters without exposing their real email. These addresses are short-lived and automatically deleted after a set time to protect privacy."
+ },
+ {
+ "title": "How does Zemail work?",
+ "content": "Zemail provides free disposable Gmail email addresses instantly without requiring any registration. Users generate a temporary email address, use it to receive verification or other emails, and the inbox remains active for a limited time before automatic deletion."
+ },
+ {
+ "title": "Is it safe to use temporary email services like Zemail?",
+ "content": "Yes, these services are generally safe for temporary use as they keep personal emails private by offering short-lived, anonymous inboxes, but they should be used responsibly."
+ },
+ {
+ "title": "How long do temporary inboxes last?",
+ "content": "Typically, inboxes last from 10 minutes up to a few hours, sometimes longer depending on the service. After that, emails and inboxes are automatically deleted to maintain privacy."
+ },
+ {
+ "title": "Do I need to sign up to use these services?",
+ "content": "No, most disposable email services, including Zemail, do not require any registration or account creation. Temporary email addresses can be generated and used instantly."
+ },
+ {
+ "title": "Can I use these temporary emails to receive verification codes?",
+ "content": "Yes, they are widely used to receive verification emails from websites, apps, and newsletters, especially to avoid spam or protect your main email."
+ },
+ {
+ "title": "Do these services support sending emails?",
+ "content": "Most disposable email services primarily support receiving emails only. Some services allow sending emails, but many restrict outgoing mail to prevent abuse."
+ },
+ {
+ "title": "Are Gmail domains better than traditional disposable email domains?",
+ "content": "Gmail domains tend to bypass filters on websites that block typical disposable email domains, making them more reliable for sign-ups."
+ },
+ {
+ "title": "What are common use cases for temporary email addresses?",
+ "content": "Use cases include protecting your real inbox from spam, signing up for trials or newsletters without giving your real email, testing email workflows for developers, receiving one-time verification codes, and avoiding long-term tracking."
+ },
+ {
+ "title": "Can temporary emails be traced back to me?",
+ "content": "While temporary emails provide anonymity, service providers may keep logs and your IP or metadata might be traceable depending on access methods. Additional privacy tools like VPNs are recommended for full anonymity."
+ },
+ {
+ "title": "Are there premium versions of these services?",
+ "content": "Yes, some services like Zemail offer premium plans with features such as longer inbox duration, multiple inboxes, and real Gmail addresses for enhanced reliability and spam control."
+ }
+ ]
+}
diff --git a/public/images/crown.webp b/public/images/crown.webp
new file mode 100644
index 0000000..30862ee
Binary files /dev/null and b/public/images/crown.webp differ
diff --git a/public/sitemap.xml b/public/sitemap.xml
new file mode 100644
index 0000000..4f9978c
--- /dev/null
+++ b/public/sitemap.xml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+ https://zemail.me/
+ 2025-05-13T19:24:37+00:00
+ 1.00
+
+
+ https://zemail.me/faq
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/blog
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/cookies-policy
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/privacy-policy
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/terms-and-conditions
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/refund-policy
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/login
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/blog/creating-unlimited-disposable-gmail-addresses
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/blog/email-privacy-and-security
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/blog/email-signature
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/blog/email-marketing
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/blog/prevent-spam-and-protect-your-privacy-with-temporary-email-generators
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/blog/the-forever-free-disposable-email-service
+ 2025-05-13T19:24:37+00:00
+ 0.80
+
+
+ https://zemail.me/blog/best-services-for-temporary-gmail-accounts
+ 2025-05-13T19:24:37+00:00
+ 0.64
+
+
+ https://zemail.me/forgot-password
+ 2025-05-13T19:24:37+00:00
+ 0.64
+
+
+ https://zemail.me/register
+ 2025-05-13T19:24:37+00:00
+ 0.64
+
+
+ https://zemail.me/disposable-gmail
+ 2025-05-13T19:24:37+00:00
+ 0.51
+
+
+ https://zemail.me/disposable-email
+ 2025-05-13T19:24:37+00:00
+ 0.51
+
+
+ https://zemail.me/disposable-outlook
+ 2025-05-13T19:24:37+00:00
+ 0.51
+
+
+ https://zemail.me/disposable-yahoo
+ 2025-05-13T19:24:37+00:00
+ 0.51
+
+
+ https://zemail.me/emailnator
+ 2025-05-13T19:24:37+00:00
+ 0.51
+
+
+ https://zemail.me/gmailnator
+ 2025-05-13T19:24:37+00:00
+ 0.51
+
+
+ https://zemail.me/temp-gmail
+ 2025-05-13T19:24:37+00:00
+ 0.51
+
+
+
+
\ No newline at end of file
diff --git a/resources/css/boil.css b/resources/css/boil.css
index 919f863..4f1167e 100644
--- a/resources/css/boil.css
+++ b/resources/css/boil.css
@@ -8,6 +8,14 @@
background-color: #f72a25;
}
+.app-primary-bg {
+ background-color: #F14743;
+}
+
+.app-primary {
+ color: #F14743;
+}
+
.btn-primary {
color: white;
background-color: #4361ee;
@@ -28,6 +36,30 @@
background-color: #00AB55;
}
+.btn-outlook {
+ background-color: #0078D4; /* Outlook blue */
+ color: white;
+ border: 2px solid #0078D4;
+ transition: background-color 0.3s ease;
+}
+
+.btn-outlook:hover {
+ background-color: #005a9e; /* Darker blue on hover */
+ border-color: #005a9e;
+}
+
+.btn-gmail {
+ color: white;
+ background-color: #D93025; /* Gmail red */
+ border: 2px solid #D93025;
+ transition: background-color 0.3s ease;
+}
+
+.btn-gmail:hover {
+ background-color: #A52714; /* Darker red on hover */
+ border-color: #A52714;
+}
+
.iframe-min-height {
min-height: 70vh;
}
@@ -65,3 +97,92 @@
.magic-box {
scrollbar-width: none;
}
+
+.premium-btn {
+ perspective: 1000px;
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 8px 20px;
+ background: linear-gradient(145deg, #f7b42f, #fdd832);
+ color: #fff;
+ font-size: 16px;
+ font-weight: bold;
+ text-transform: uppercase;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+ transition: transform 0.3s ease;
+}
+
+.premium-btn:hover {
+ transform: scale(1.05);
+}
+
+.premium-btn .crown,
+.premium-btn .crown-bg1,
+.premium-btn .crown-bg2 {
+ transform-style: preserve-3d;
+ position: absolute;
+ background: url('/images/crown.webp') no-repeat center center;
+ background-size: contain;
+ animation: rotateCrownY 2s linear infinite;
+}
+
+/* Main Crown */
+.premium-btn .crown {
+ top: -20px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 40px;
+ height: 40px;
+ z-index: 3;
+}
+
+/* Background Crown 1 */
+.premium-btn .crown-bg1 {
+ top: -10px;
+ left: 5%;
+ width: 25px;
+ height: 25px;
+ animation-duration: 3s;
+ opacity: 0.5;
+ z-index: 1;
+}
+
+/* Background Crown 2 */
+.premium-btn .crown-bg2 {
+ bottom: -15px;
+ right: 15%;
+ width: 30px;
+ height: 30px;
+ animation-duration: 4s;
+ opacity: 0.4;
+ z-index: 1;
+}
+
+.premium-btn .btn-text {
+ z-index: 2;
+}
+
+@keyframes rotateCrownY {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.bg-pricing-modal {
+ background-color: #F3F4F6; /* Light mode background */
+}
+
+html.dark .bg-pricing-modal {
+ background-color: #101828; /* Dark mode */
+}
+
+
diff --git a/resources/js/boil.js b/resources/js/boil.js
index 236872d..5448e24 100644
--- a/resources/js/boil.js
+++ b/resources/js/boil.js
@@ -217,3 +217,24 @@ document.addEventListener('DOMContentLoaded', function () {
const fetchInterval = setInterval(fetchStoreEmail, 10000);
}, 3000);
});
+
+
+document.addEventListener('promotePremium', function () {
+ setTimeout(() => {
+ const event = new CustomEvent('modal-show', {
+ detail: {
+ name: 'premium',
+ scope: null
+ }
+ });
+ const showText = document.getElementById('premium-modal-limit');
+ showText.classList.remove('hidden');
+ const text1 = document.getElementById('focus-modal-text1');
+ text1.classList.remove('text-accent');
+ text1.classList.add('text-amber-500');
+ const text2 = document.getElementById('focus-modal-text2');
+ text2.classList.remove('text-accent');
+ text2.classList.add('text-amber-500');
+ document.dispatchEvent(event);
+ }, 500);
+});
diff --git a/resources/views/components/action-message.blade.php b/resources/views/components/action-message.blade.php
new file mode 100644
index 0000000..d313ee6
--- /dev/null
+++ b/resources/views/components/action-message.blade.php
@@ -0,0 +1,14 @@
+@props([
+ 'on',
+])
+
+
merge(['class' => 'text-sm']) }}
+>
+ {{ $slot->isEmpty() ? __('Saved.') : $slot }}
+
diff --git a/resources/views/components/app-logo-icon.blade.php b/resources/views/components/app-logo-icon.blade.php
new file mode 100644
index 0000000..a76b040
--- /dev/null
+++ b/resources/views/components/app-logo-icon.blade.php
@@ -0,0 +1 @@
+
diff --git a/resources/views/components/auth-header.blade.php b/resources/views/components/auth-header.blade.php
new file mode 100644
index 0000000..e596a3f
--- /dev/null
+++ b/resources/views/components/auth-header.blade.php
@@ -0,0 +1,9 @@
+@props([
+ 'title',
+ 'description',
+])
+
+
+ {{ $title }}
+ {{ $description }}
+
diff --git a/resources/views/components/auth-session-status.blade.php b/resources/views/components/auth-session-status.blade.php
new file mode 100644
index 0000000..98e0011
--- /dev/null
+++ b/resources/views/components/auth-session-status.blade.php
@@ -0,0 +1,9 @@
+@props([
+ 'status',
+])
+
+@if ($status)
+ merge(['class' => 'font-medium text-sm text-green-600']) }}>
+ {{ $status }}
+
+@endif
diff --git a/resources/views/components/layouts/app.blade.php b/resources/views/components/layouts/app.blade.php
index 08b811a..d200f77 100644
--- a/resources/views/components/layouts/app.blade.php
+++ b/resources/views/components/layouts/app.blade.php
@@ -45,6 +45,14 @@
{{ __('Refresh') }}
+
+
+
+ @auth
+
+
+ @endauth
+ @guest
+
+
+
+
+ @endguest
@@ -135,7 +163,7 @@
+
+
+
+
+
+
+ Looks like you have reached the daily email generation limit, consider subscribing and access to premium features
+
+
+
+
+
+
Zemail Premium
+
${{ config('app.plans')[0]->price ?? 10 }}
+
per month
+
+
+ Subscribe now
+
+
+
+ -
+
+ No Ads
+
+ -
+
+ Dedicated premium domains
+
+ -
+
+ Unlimited email addresses on public domains
+
+ -
+
+ Up to 100 email addresses daily on premium domains
+
+ -
+
+ Dedicated Premium Gmail Addresses
+
+ -
+
+ 100% Private address with full ownership
+
+ -
+
+ History Upto 30 Days
+
+ -
+
+ Enhanced privacy and security
+
+
+
+
+
+
+
+
+ Already subscribed?
Login now
+
+
+
+
@@ -212,6 +307,6 @@
});
{!! config('app.settings.app_footer') !!}
- @yield('custom_header')
+ @yield('custom_footer')