From a6517f4b5b6ee938ab1781b00e3ff6f467050e53 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Sun, 18 Jan 2026 17:48:12 -0300 Subject: [PATCH] feat: Introduce a self-hosted mode, refactor email notifications to use Mailable classes with new templates, and update the project README. --- .github/FUNDING.yml | 1 + LICENSE.md | 65 ++++++ README.md | 202 +++++++++++++++++- app/Enums/WorkspaceInvite/Status.php | 22 -- app/Http/Controllers/OnboardingController.php | 16 +- .../Controllers/WorkspaceInviteController.php | 96 +++++++-- app/Http/Middleware/EnsureSubscribed.php | 5 + app/Http/Middleware/HandleInertiaRequests.php | 1 + app/Http/Responses/LoginResponse.php | 5 + app/Http/Responses/RegisterResponse.php | 5 + app/Mail/WorkspaceInvite.php | 45 ++++ app/Models/WorkspaceInvite.php | 25 --- .../WorkspaceInviteNotification.php | 63 ------ app/Providers/AppServiceProvider.php | 12 +- app/Providers/FortifyServiceProvider.php | 5 +- config/trypost.php | 12 ++ lang/php_en.json | 1 + maizzle/components/footer.html | 24 +-- maizzle/components/header.html | 2 +- maizzle/layouts/main.html | 10 +- maizzle/templates/email-verification.html | 14 +- maizzle/templates/password-reset.html | 12 +- .../{invite.html => workspace-invite.html} | 25 ++- public/images/emails/logo.png | Bin 0 -> 6125 bytes resources/js/layouts/settings/Layout.vue | 74 ++++--- resources/js/pages/auth/Login.vue | 2 + resources/js/pages/auth/Register.vue | 5 + resources/js/pages/invites/Accept.vue | 138 ++++++++++++ resources/js/pages/settings/Members.vue | 51 ++--- resources/js/types/index.d.ts | 11 +- .../views/mail/email-verification.blade.php | 116 ++++++++++ resources/views/mail/password-reset.blade.php | 116 ++++++++++ .../views/mail/workspace-invite.blade.php | 117 ++++++++++ routes/web.php | 6 +- 34 files changed, 1039 insertions(+), 265 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 LICENSE.md create mode 100644 app/Mail/WorkspaceInvite.php delete mode 100644 app/Notifications/WorkspaceInviteNotification.php create mode 100644 lang/php_en.json rename maizzle/templates/{invite.html => workspace-invite.html} (57%) create mode 100644 public/images/emails/logo.png create mode 100644 resources/js/pages/invites/Accept.vue create mode 100644 resources/views/mail/email-verification.blade.php create mode 100644 resources/views/mail/password-reset.blade.php create mode 100644 resources/views/mail/workspace-invite.blade.php diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..ba1ec0dd --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [trypost-it] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..e6f59496 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,65 @@ +# TryPost License + +## Functional Source License, Version 1.1, MIT + +### Licensor + +TryPost + +### Software + +TryPost - Social Media Scheduling Platform + +### Use Limitation + +You may not use the Software to provide a commercial service that allows third parties to schedule, manage, or publish content to social media platforms, where such service competes with or substitutes for TryPost or any product or service provided by Licensor. + +### License Grant + +Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Software. If your use of the Software does not violate the Use Limitation, Licensor hereby grants you the right to make production use of the Software. + +### Permitted Uses + +The following uses are expressly permitted: + +1. **Personal Use**: Using TryPost for your own personal social media management +2. **Internal Business Use**: Using TryPost within your organization to manage your company's social media accounts +3. **Educational Use**: Using TryPost for learning, teaching, or research purposes +4. **Development**: Modifying, studying, or contributing to the TryPost codebase +5. **Self-Hosting**: Deploying TryPost on your own infrastructure for personal or internal business use + +### Prohibited Uses + +The following uses are expressly prohibited: + +1. **Competing SaaS**: Offering TryPost (or a modified version) as a hosted service to third parties +2. **White-Label Services**: Rebranding and selling TryPost as your own product +3. **Managed Service Provider**: Operating TryPost as a service for multiple clients or customers + +### Corollary + +For clarity, the Use Limitation does not prohibit: + +- Using TryPost to manage social media for your own business or clients (as a social media manager) +- Integrating TryPost into internal tools that are not offered as a competing service +- Contributing improvements back to the TryPost project + +### Notice + +You must ensure that anyone who receives a copy of any part of the Software from you also receives a copy of this License. + +### No Other Rights + +These terms do not grant you any right in any trademark or logo of Licensor or its affiliates. + +### Patent + +To the extent your use qualifies as a Permitted Use, Licensor grants you a license under any patent claims it can license that are necessarily infringed by the Software, to make, use, and distribute the Software. + +### Disclaimer + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- + +Copyright (c) 2025 TryPost diff --git a/README.md b/README.md index 387cff9e..6a53f435 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,199 @@ +

+ TryPost +

-## Migration with base seed -```sh -php artisan migrate:fresh --seed +

TryPost

+ +

+ The open-source social media scheduling platform +

+ +

+ Schedule, manage, and publish content to all your social media accounts from one place. +
+ Self-hosted. Privacy-focused. No limits. +

+ +

+ Stars + License + Release +

+ +

+ Features • + Platforms • + Quick Start • + Self-Hosting • + Contributing +

+ +--- + +## Why TryPost? + +Tired of paying expensive monthly fees for social media scheduling tools? Want full control over your data? TryPost is the solution. + +- **100% Open Source** - Inspect the code, contribute, make it yours +- **Self-Hosted** - Your data stays on your servers +- **No Limits** - Schedule unlimited posts, connect unlimited accounts +- **Privacy First** - No tracking, no analytics, no data selling + +## Features + +- **Visual Calendar** - Drag and drop posts across your content calendar +- **Post Composer** - Create and preview posts for multiple platforms at once +- **Media Library** - Upload images and videos with automatic optimization +- **Team Collaboration** - Invite team members with role-based permissions +- **Workspaces** - Manage multiple brands or clients separately +- **Smart Scheduling** - Schedule posts for optimal engagement times +- **Multi-Platform Preview** - See exactly how your post will look on each platform + +## Supported Platforms + + + + + + + + + + + + + + + + +

X (Twitter)

LinkedIn

Facebook

Instagram

TikTok

YouTube

Pinterest

Threads

Bluesky

Mastodon
+ +## Quick Start + +### Requirements + +- PHP 8.2+ +- Node.js 18+ +- MySQL 8.0+ or PostgreSQL 14+ +- Redis +- Composer + +### Installation + +```bash +# Clone the repository +git clone https://github.com/trypost-it/trypost.git +cd trypost + +# Install dependencies +composer install +npm install + +# Setup environment +cp .env.example .env +php artisan key:generate + +# Configure your database in .env, then run migrations +php artisan migrate --seed + +# Build assets +npm run build + +# Start the server +php artisan serve ``` -# Start queue worker -```sh +Visit `http://localhost:8000` and create your account! + +## Self-Hosting + +TryPost is designed to be self-hosted. Enable self-hosted mode in your `.env`: + +```env +SELF_HOSTED=true +``` + +This skips payment/subscription requirements and gives you full access. + +### Running in Production + +```bash +# Start the queue worker +php artisan horizon + +# Or use the watcher for development php artisan horizon:watch ``` -# Share with Ngrok: -```sh -ngrok http --host-header=rewrite trypost.test:443 -``` \ No newline at end of file +### Docker (Coming Soon) + +```bash +docker-compose up -d +``` + +## Tech Stack + +| Layer | Technology | +|-------|------------| +| **Backend** | Laravel 12, PHP 8.4 | +| **Frontend** | Vue 3, Inertia.js, TypeScript | +| **Styling** | Tailwind CSS 4 | +| **Database** | MySQL / PostgreSQL | +| **Queue** | Redis + Laravel Horizon | +| **Real-time** | Laravel Reverb | + +## Roadmap + +Check out our [public roadmap](https://github.com/orgs/trypost-it/projects/1) to see what's coming next and vote on features! + +## Contributing + +We love contributions! Whether it's: + +- Bug reports +- Feature requests +- Documentation improvements +- Code contributions + +### Development Setup + +```bash +# Install dependencies +composer install +npm install + +# Start development server +npm run dev + +# Run tests +php artisan test +``` + +## Community + +- [GitHub Discussions](https://github.com/trypost-it/trypost/discussions) - Ask questions, share ideas +- [GitHub Issues](https://github.com/trypost-it/trypost/issues) - Report bugs, request features + +## License + +TryPost is licensed under the [Functional Source License (FSL)](LICENSE.md). + +**You can:** +- Use TryPost for personal or internal business use +- Self-host for your own social media management +- Modify and contribute to the codebase + +**You cannot:** +- Offer TryPost as a competing SaaS product +- White-label and resell TryPost + +--- + +

+ If TryPost helps you, please give us a star! +
+
+ + Star on GitHub + +

diff --git a/app/Enums/WorkspaceInvite/Status.php b/app/Enums/WorkspaceInvite/Status.php index fcca0959..e9ae4d1e 100644 --- a/app/Enums/WorkspaceInvite/Status.php +++ b/app/Enums/WorkspaceInvite/Status.php @@ -6,26 +6,4 @@ enum Status: string { case Pending = 'pending'; case Accepted = 'accepted'; - case Expired = 'expired'; - case Cancelled = 'cancelled'; - - public function label(): string - { - return match ($this) { - self::Pending => 'Pending', - self::Accepted => 'Accepted', - self::Expired => 'Expired', - self::Cancelled => 'Cancelled', - }; - } - - public function color(): string - { - return match ($this) { - self::Pending => 'yellow', - self::Accepted => 'green', - self::Expired => 'gray', - self::Cancelled => 'red', - }; - } } diff --git a/app/Http/Controllers/OnboardingController.php b/app/Http/Controllers/OnboardingController.php index 28e3c9f0..3ebcdfe6 100644 --- a/app/Http/Controllers/OnboardingController.php +++ b/app/Http/Controllers/OnboardingController.php @@ -74,12 +74,24 @@ public function step2(Request $request): Response } /** - * Store step 2 and redirect to Stripe checkout. + * Store step 2 and redirect to Stripe checkout (or complete if self-hosted). */ - public function storeStep2(Request $request): SymfonyResponse + public function storeStep2(Request $request): SymfonyResponse|RedirectResponse { $user = $request->user(); + // Skip payment for self-hosted mode + if (config('trypost.self_hosted')) { + $user->update([ + 'setup' => Setup::Completed, + ]); + + session()->flash('flash.banner', 'Welcome to TryPost!'); + session()->flash('flash.bannerStyle', 'success'); + + return redirect()->route('calendar'); + } + $user->update([ 'setup' => Setup::Subscription, ]); diff --git a/app/Http/Controllers/WorkspaceInviteController.php b/app/Http/Controllers/WorkspaceInviteController.php index fb39a00b..1f96b8c2 100644 --- a/app/Http/Controllers/WorkspaceInviteController.php +++ b/app/Http/Controllers/WorkspaceInviteController.php @@ -4,10 +4,11 @@ use App\Enums\UserWorkspace\Role as WorkspaceRole; use App\Http\Requests\StoreWorkspaceInviteRequest; +use App\Mail\WorkspaceInvite as WorkspaceInviteMail; use App\Models\WorkspaceInvite; -use App\Notifications\WorkspaceInviteNotification; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Mail; use Inertia\Inertia; use Inertia\Response; @@ -86,9 +87,12 @@ public function store(StoreWorkspaceInviteRequest $request): RedirectResponse 'role' => $request->role ?? WorkspaceRole::Member, ]); - $invite->notify(new WorkspaceInviteNotification($invite)); + Mail::to($invite->email)->send(new WorkspaceInviteMail($invite)); - return back()->with('success', 'Invite sent successfully!'); + session()->flash('flash.banner', 'Invite sent successfully!'); + session()->flash('flash.bannerStyle', 'success'); + + return back(); } public function destroy(Request $request, WorkspaceInvite $invite): RedirectResponse @@ -105,23 +109,65 @@ public function destroy(Request $request, WorkspaceInvite $invite): RedirectResp abort(404); } - $invite->cancel(); + $invite->delete(); - return back()->with('success', 'Invite cancelled.'); + session()->flash('flash.banner', 'Invite deleted.'); + session()->flash('flash.bannerStyle', 'success'); + + return back(); + } + + public function show(Request $request, string $token): Response|RedirectResponse + { + $invite = WorkspaceInvite::where('token', $token) + ->with(['workspace', 'inviter']) + ->firstOrFail(); + + if (! $invite->isPending()) { + session()->flash('flash.banner', 'This invite is no longer valid.'); + session()->flash('flash.bannerStyle', 'danger'); + + return redirect()->route('login'); + } + + $user = $request->user(); + + // Store token in session for after login/register + session(['pending_invite_token' => $token]); + + return Inertia::render('invites/Accept', [ + 'invite' => [ + 'id' => $invite->id, + 'token' => $invite->token, + 'email' => $invite->email, + 'role' => [ + 'value' => $invite->role->value, + 'label' => $invite->role->label(), + ], + 'workspace' => [ + 'id' => $invite->workspace->id, + 'name' => $invite->workspace->name, + ], + 'inviter' => [ + 'id' => $invite->inviter->id, + 'name' => $invite->inviter->name, + 'email' => $invite->inviter->email, + ], + ], + 'isAuthenticated' => (bool) $user, + 'userEmail' => $user?->email, + ]); } public function accept(Request $request, string $token): RedirectResponse { $invite = WorkspaceInvite::where('token', $token)->firstOrFail(); - if (! $invite->isValid()) { - if ($invite->isExpired()) { - return redirect()->route('dashboard') - ->withErrors(['invite' => 'This invite has expired.']); - } + if (! $invite->isPending()) { + session()->flash('flash.banner', 'This invite is no longer valid.'); + session()->flash('flash.bannerStyle', 'danger'); - return redirect()->route('dashboard') - ->withErrors(['invite' => 'This invite is no longer valid.']); + return redirect()->route('login'); } $user = $request->user(); @@ -129,25 +175,28 @@ public function accept(Request $request, string $token): RedirectResponse if (! $user) { session(['pending_invite_token' => $token]); - return redirect()->route('login') - ->with('message', 'Please log in to accept the invite.'); + return redirect()->route('login'); } + // Clear the pending token + session()->forget('pending_invite_token'); + if ($invite->workspace->hasMember($user)) { - // Switch to this workspace $user->switchWorkspace($invite->workspace); - return redirect()->route('calendar') - ->with('message', 'You are already a member of this workspace.'); + session()->flash('flash.banner', 'You are already a member of this workspace.'); + session()->flash('flash.bannerStyle', 'info'); + + return redirect()->route('calendar'); } $invite->accept($user); - - // Switch to the new workspace $user->switchWorkspace($invite->workspace); - return redirect()->route('calendar') - ->with('success', 'You are now a member of the workspace!'); + session()->flash('flash.banner', 'You are now a member of the workspace!'); + session()->flash('flash.bannerStyle', 'success'); + + return redirect()->route('calendar'); } public function removeMember(Request $request, string $userId): RedirectResponse @@ -166,6 +215,9 @@ public function removeMember(Request $request, string $userId): RedirectResponse $workspace->members()->detach($userId); - return back()->with('success', 'Member removed successfully.'); + session()->flash('flash.banner', 'Member removed successfully.'); + session()->flash('flash.bannerStyle', 'success'); + + return back(); } } diff --git a/app/Http/Middleware/EnsureSubscribed.php b/app/Http/Middleware/EnsureSubscribed.php index 14590c54..72ffa63c 100644 --- a/app/Http/Middleware/EnsureSubscribed.php +++ b/app/Http/Middleware/EnsureSubscribed.php @@ -15,6 +15,11 @@ class EnsureSubscribed */ public function handle(Request $request, Closure $next): Response { + // Skip subscription check for self-hosted mode + if (config('trypost.self_hosted')) { + return $next($request); + } + $user = $request->user(); if (! $user) { diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 38816abc..5746e574 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -49,6 +49,7 @@ public function share(Request $request): array 'flash' => $request->session()->get('flash', []), 'env' => config('app.env'), 'locale' => app()->getLocale(), + 'selfHosted' => config('trypost.self_hosted'), ]; } } diff --git a/app/Http/Responses/LoginResponse.php b/app/Http/Responses/LoginResponse.php index c0b6c633..1ae1bfbe 100644 --- a/app/Http/Responses/LoginResponse.php +++ b/app/Http/Responses/LoginResponse.php @@ -12,6 +12,11 @@ public function toResponse($request): Response { $user = $request->user(); + // Check for pending invite token + if ($token = session('pending_invite_token')) { + return redirect()->route('invites.show', $token); + } + // Determine redirect based on setup status $redirect = match ($user->setup) { Setup::Completed => route('calendar'), diff --git a/app/Http/Responses/RegisterResponse.php b/app/Http/Responses/RegisterResponse.php index 0a200043..9bfb28a9 100644 --- a/app/Http/Responses/RegisterResponse.php +++ b/app/Http/Responses/RegisterResponse.php @@ -9,6 +9,11 @@ class RegisterResponse implements RegisterResponseContract { public function toResponse($request): Response { + // Check for pending invite token + if ($token = session('pending_invite_token')) { + return redirect()->route('invites.show', $token); + } + return $request->wantsJson() ? response()->json(['two_factor' => false]) : redirect()->route('onboarding.step1'); diff --git a/app/Mail/WorkspaceInvite.php b/app/Mail/WorkspaceInvite.php new file mode 100644 index 00000000..d3ef6d22 --- /dev/null +++ b/app/Mail/WorkspaceInvite.php @@ -0,0 +1,45 @@ +invite->workspace->name}", + ); + } + + public function content(): Content + { + return new Content( + view: 'mail.workspace-invite', + with: [ + 'title' => "You've been invited to join {$this->invite->workspace->name}", + 'previewText' => "{$this->invite->inviter->name} has invited you to collaborate on TryPost.", + 'invite' => $this->invite, + 'url' => route('invites.show', $this->invite->token), + ], + ); + } + + public function attachments(): array + { + return []; + } +} diff --git a/app/Models/WorkspaceInvite.php b/app/Models/WorkspaceInvite.php index c96e3cf5..1f6a6bd6 100644 --- a/app/Models/WorkspaceInvite.php +++ b/app/Models/WorkspaceInvite.php @@ -27,7 +27,6 @@ class WorkspaceInvite extends Model 'token', 'role', 'status', - 'expires_at', 'accepted_at', ]; @@ -39,7 +38,6 @@ protected function casts(): array return [ 'role' => Role::class, 'status' => InviteStatus::class, - 'expires_at' => 'datetime', 'accepted_at' => 'datetime', ]; } @@ -50,9 +48,6 @@ protected static function booted(): void if (empty($invite->token)) { $invite->token = Str::random(64); } - if (empty($invite->expires_at)) { - $invite->expires_at = now()->addDays(7); - } }); } @@ -71,26 +66,11 @@ public function scopePending(Builder $query): Builder return $query->where('status', InviteStatus::Pending); } - public function scopeValid(Builder $query): Builder - { - return $query->pending()->where('expires_at', '>', now()); - } - - public function isExpired(): bool - { - return $this->expires_at->isPast(); - } - public function isPending(): bool { return $this->status === InviteStatus::Pending; } - public function isValid(): bool - { - return $this->isPending() && ! $this->isExpired(); - } - public function accept(User $user): void { $this->workspace->members()->attach($user->id, [ @@ -103,11 +83,6 @@ public function accept(User $user): void ]); } - public function cancel(): void - { - $this->update(['status' => InviteStatus::Cancelled]); - } - public function routeNotificationForMail(): string { return $this->email; diff --git a/app/Notifications/WorkspaceInviteNotification.php b/app/Notifications/WorkspaceInviteNotification.php deleted file mode 100644 index 4cde61c2..00000000 --- a/app/Notifications/WorkspaceInviteNotification.php +++ /dev/null @@ -1,63 +0,0 @@ - - */ - public function via(object $notifiable): array - { - return ['mail']; - } - - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - $acceptUrl = route('invites.accept', $this->invite->token); - - return (new MailMessage) - ->subject("Convite para o workspace {$this->invite->workspace->name}") - ->greeting('Olá!') - ->line("{$this->invite->inviter->name} convidou você para colaborar no workspace **{$this->invite->workspace->name}**.") - ->line("Você foi convidado como **{$this->invite->role->label()}**.") - ->action('Aceitar Convite', $acceptUrl) - ->line('Este convite expira em 7 dias.') - ->salutation('Obrigado por usar TryPost!'); - } - - /** - * Get the array representation of the notification. - * - * @return array - */ - public function toArray(object $notifiable): array - { - return [ - 'workspace_id' => $this->invite->workspace_id, - 'workspace_name' => $this->invite->workspace->name, - 'inviter_name' => $this->invite->inviter->name, - 'role' => $this->invite->role->value, - ]; - } -} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c2335325..b5ec5c07 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -135,10 +135,10 @@ protected function configureDefaults(): void VerifyEmail::toMailUsing(function (User $user, string $url) { return (new MailMessage) ->from(config('mail.from.address'), config('mail.from.name')) - ->subject('Confirme o seu endereço de e-mail') + ->subject('Verify your email address') ->view('mail.email-verification', [ - 'title' => 'Confirme o seu endereço de e-mail', - 'previewText' => 'Por favor, confirme o seu endereço de e-mail.', + 'title' => 'Verify your email address', + 'previewText' => 'Please verify your email address.', 'user' => $user, 'url' => $url, ]); @@ -153,10 +153,10 @@ protected function configureDefaults(): void return (new MailMessage) ->from(config('mail.from.address'), config('mail.from.name')) - ->subject('Redefina sua senha') + ->subject('Reset your password') ->view('mail.password-reset', [ - 'title' => 'Redefina sua senha', - 'previewText' => 'Por favor, redefina sua senha.', + 'title' => 'Reset your password', + 'previewText' => 'Reset your password.', 'user' => $user, 'url' => $url, ]); diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index 9e74fe30..3e3f66af 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -56,6 +56,7 @@ private function configureViews(): void 'canResetPassword' => Features::enabled(Features::resetPasswords()), 'canRegister' => Features::enabled(Features::registration()), 'status' => $request->session()->get('status'), + 'email' => $request->query('email'), ])); Fortify::resetPasswordView(fn (Request $request) => Inertia::render('auth/ResetPassword', [ @@ -71,7 +72,9 @@ private function configureViews(): void 'status' => $request->session()->get('status'), ])); - Fortify::registerView(fn () => Inertia::render('auth/Register')); + Fortify::registerView(fn (Request $request) => Inertia::render('auth/Register', [ + 'email' => $request->query('email'), + ])); Fortify::twoFactorChallengeView(fn () => Inertia::render('auth/TwoFactorChallenge')); diff --git a/config/trypost.php b/config/trypost.php index 9ec91df4..95a9f76d 100644 --- a/config/trypost.php +++ b/config/trypost.php @@ -2,6 +2,18 @@ return [ + /* + |-------------------------------------------------------------------------- + | Self-Hosted Mode + |-------------------------------------------------------------------------- + | + | When enabled, the application runs in self-hosted mode which skips + | payment/subscription requirements during onboarding. + | + */ + + 'self_hosted' => env('SELF_HOSTED', true), + /* |-------------------------------------------------------------------------- | Social Platforms diff --git a/lang/php_en.json b/lang/php_en.json new file mode 100644 index 00000000..0ef12bda --- /dev/null +++ b/lang/php_en.json @@ -0,0 +1 @@ +{"auth.failed":"These credentials do not match our records.","auth.password":"The provided password is incorrect.","auth.throttle":"Too many login attempts. Please try again in :seconds seconds.","pagination.previous":"« Previous","pagination.next":"Next »","passwords.reset":"Your password has been reset.","passwords.sent":"We have emailed your password reset link.","passwords.throttled":"Please wait before retrying.","passwords.token":"This password reset token is invalid.","passwords.user":"We can't find a user with that email address.","validation.accepted":"The :attribute field must be accepted.","validation.accepted_if":"The :attribute field must be accepted when :other is :value.","validation.active_url":"The :attribute field must be a valid URL.","validation.after":"The :attribute field must be a date after :date.","validation.after_or_equal":"The :attribute field must be a date after or equal to :date.","validation.alpha":"The :attribute field must only contain letters.","validation.alpha_dash":"The :attribute field must only contain letters, numbers, dashes, and underscores.","validation.alpha_num":"The :attribute field must only contain letters and numbers.","validation.any_of":"The :attribute field is invalid.","validation.array":"The :attribute field must be an array.","validation.ascii":"The :attribute field must only contain single-byte alphanumeric characters and symbols.","validation.before":"The :attribute field must be a date before :date.","validation.before_or_equal":"The :attribute field must be a date before or equal to :date.","validation.between.array":"The :attribute field must have between :min and :max items.","validation.between.file":"The :attribute field must be between :min and :max kilobytes.","validation.between.numeric":"The :attribute field must be between :min and :max.","validation.between.string":"The :attribute field must be between :min and :max characters.","validation.boolean":"The :attribute field must be true or false.","validation.can":"The :attribute field contains an unauthorized value.","validation.confirmed":"The :attribute field confirmation does not match.","validation.contains":"The :attribute field is missing a required value.","validation.current_password":"The password is incorrect.","validation.date":"The :attribute field must be a valid date.","validation.date_equals":"The :attribute field must be a date equal to :date.","validation.date_format":"The :attribute field must match the format :format.","validation.decimal":"The :attribute field must have :decimal decimal places.","validation.declined":"The :attribute field must be declined.","validation.declined_if":"The :attribute field must be declined when :other is :value.","validation.different":"The :attribute field and :other must be different.","validation.digits":"The :attribute field must be :digits digits.","validation.digits_between":"The :attribute field must be between :min and :max digits.","validation.dimensions":"The :attribute field has invalid image dimensions.","validation.distinct":"The :attribute field has a duplicate value.","validation.doesnt_contain":"The :attribute field must not contain any of the following: :values.","validation.doesnt_end_with":"The :attribute field must not end with one of the following: :values.","validation.doesnt_start_with":"The :attribute field must not start with one of the following: :values.","validation.email":"The :attribute field must be a valid email address.","validation.encoding":"The :attribute field must be encoded in :encoding.","validation.ends_with":"The :attribute field must end with one of the following: :values.","validation.enum":"The selected :attribute is invalid.","validation.exists":"The selected :attribute is invalid.","validation.extensions":"The :attribute field must have one of the following extensions: :values.","validation.file":"The :attribute field must be a file.","validation.filled":"The :attribute field must have a value.","validation.gt.array":"The :attribute field must have more than :value items.","validation.gt.file":"The :attribute field must be greater than :value kilobytes.","validation.gt.numeric":"The :attribute field must be greater than :value.","validation.gt.string":"The :attribute field must be greater than :value characters.","validation.gte.array":"The :attribute field must have :value items or more.","validation.gte.file":"The :attribute field must be greater than or equal to :value kilobytes.","validation.gte.numeric":"The :attribute field must be greater than or equal to :value.","validation.gte.string":"The :attribute field must be greater than or equal to :value characters.","validation.hex_color":"The :attribute field must be a valid hexadecimal color.","validation.image":"The :attribute field must be an image.","validation.in":"The selected :attribute is invalid.","validation.in_array":"The :attribute field must exist in :other.","validation.in_array_keys":"The :attribute field must contain at least one of the following keys: :values.","validation.integer":"The :attribute field must be an integer.","validation.ip":"The :attribute field must be a valid IP address.","validation.ipv4":"The :attribute field must be a valid IPv4 address.","validation.ipv6":"The :attribute field must be a valid IPv6 address.","validation.json":"The :attribute field must be a valid JSON string.","validation.list":"The :attribute field must be a list.","validation.lowercase":"The :attribute field must be lowercase.","validation.lt.array":"The :attribute field must have less than :value items.","validation.lt.file":"The :attribute field must be less than :value kilobytes.","validation.lt.numeric":"The :attribute field must be less than :value.","validation.lt.string":"The :attribute field must be less than :value characters.","validation.lte.array":"The :attribute field must not have more than :value items.","validation.lte.file":"The :attribute field must be less than or equal to :value kilobytes.","validation.lte.numeric":"The :attribute field must be less than or equal to :value.","validation.lte.string":"The :attribute field must be less than or equal to :value characters.","validation.mac_address":"The :attribute field must be a valid MAC address.","validation.max.array":"The :attribute field must not have more than :max items.","validation.max.file":"The :attribute field must not be greater than :max kilobytes.","validation.max.numeric":"The :attribute field must not be greater than :max.","validation.max.string":"The :attribute field must not be greater than :max characters.","validation.max_digits":"The :attribute field must not have more than :max digits.","validation.mimes":"The :attribute field must be a file of type: :values.","validation.mimetypes":"The :attribute field must be a file of type: :values.","validation.min.array":"The :attribute field must have at least :min items.","validation.min.file":"The :attribute field must be at least :min kilobytes.","validation.min.numeric":"The :attribute field must be at least :min.","validation.min.string":"The :attribute field must be at least :min characters.","validation.min_digits":"The :attribute field must have at least :min digits.","validation.missing":"The :attribute field must be missing.","validation.missing_if":"The :attribute field must be missing when :other is :value.","validation.missing_unless":"The :attribute field must be missing unless :other is :value.","validation.missing_with":"The :attribute field must be missing when :values is present.","validation.missing_with_all":"The :attribute field must be missing when :values are present.","validation.multiple_of":"The :attribute field must be a multiple of :value.","validation.not_in":"The selected :attribute is invalid.","validation.not_regex":"The :attribute field format is invalid.","validation.numeric":"The :attribute field must be a number.","validation.password.letters":"The :attribute field must contain at least one letter.","validation.password.mixed":"The :attribute field must contain at least one uppercase and one lowercase letter.","validation.password.numbers":"The :attribute field must contain at least one number.","validation.password.symbols":"The :attribute field must contain at least one symbol.","validation.password.uncompromised":"The given :attribute has appeared in a data leak. Please choose a different :attribute.","validation.present":"The :attribute field must be present.","validation.present_if":"The :attribute field must be present when :other is :value.","validation.present_unless":"The :attribute field must be present unless :other is :value.","validation.present_with":"The :attribute field must be present when :values is present.","validation.present_with_all":"The :attribute field must be present when :values are present.","validation.prohibited":"The :attribute field is prohibited.","validation.prohibited_if":"The :attribute field is prohibited when :other is :value.","validation.prohibited_if_accepted":"The :attribute field is prohibited when :other is accepted.","validation.prohibited_if_declined":"The :attribute field is prohibited when :other is declined.","validation.prohibited_unless":"The :attribute field is prohibited unless :other is in :values.","validation.prohibits":"The :attribute field prohibits :other from being present.","validation.regex":"The :attribute field format is invalid.","validation.required":"The :attribute field is required.","validation.required_array_keys":"The :attribute field must contain entries for: :values.","validation.required_if":"The :attribute field is required when :other is :value.","validation.required_if_accepted":"The :attribute field is required when :other is accepted.","validation.required_if_declined":"The :attribute field is required when :other is declined.","validation.required_unless":"The :attribute field is required unless :other is in :values.","validation.required_with":"The :attribute field is required when :values is present.","validation.required_with_all":"The :attribute field is required when :values are present.","validation.required_without":"The :attribute field is required when :values is not present.","validation.required_without_all":"The :attribute field is required when none of :values are present.","validation.same":"The :attribute field must match :other.","validation.size.array":"The :attribute field must contain :size items.","validation.size.file":"The :attribute field must be :size kilobytes.","validation.size.numeric":"The :attribute field must be :size.","validation.size.string":"The :attribute field must be :size characters.","validation.starts_with":"The :attribute field must start with one of the following: :values.","validation.string":"The :attribute field must be a string.","validation.timezone":"The :attribute field must be a valid timezone.","validation.unique":"The :attribute has already been taken.","validation.uploaded":"The :attribute failed to upload.","validation.uppercase":"The :attribute field must be uppercase.","validation.url":"The :attribute field must be a valid URL.","validation.ulid":"The :attribute field must be a valid ULID.","validation.uuid":"The :attribute field must be a valid UUID.","validation.custom.attribute-name.rule-name":"custom-message"} \ No newline at end of file diff --git a/maizzle/components/footer.html b/maizzle/components/footer.html index e68f94aa..e184c769 100644 --- a/maizzle/components/footer.html +++ b/maizzle/components/footer.html @@ -7,29 +7,7 @@

- Enviado por Clinyx - Sistema para gestão de clínicas e consultórios -

- -

- - LinkedIn - - • - - Facebook - - • - - Instagram - - • - - YouTube - + Open-source social media scheduling tool

@if(isset($unsubscribe_url)) diff --git a/maizzle/components/header.html b/maizzle/components/header.html index ca8c3b82..7803f18d 100644 --- a/maizzle/components/header.html +++ b/maizzle/components/header.html @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/maizzle/layouts/main.html b/maizzle/layouts/main.html index 8a0e3e1b..c4013513 100644 --- a/maizzle/layouts/main.html +++ b/maizzle/layouts/main.html @@ -1,5 +1,5 @@ - + @@ -39,15 +39,15 @@ - @if(isset($preheader)) + @if(isset($previewText)) @endif -
+
- \ No newline at end of file + diff --git a/maizzle/templates/email-verification.html b/maizzle/templates/email-verification.html index 07ac831d..fe7a2344 100644 --- a/maizzle/templates/email-verification.html +++ b/maizzle/templates/email-verification.html @@ -8,30 +8,30 @@

- Olá @{{$user->name}}, + Hi @{{ $user->name }},

- Por favor, confirme seu endereço de e-mail clicando no botão abaixo: + Please confirm your email address by clicking the button below:

- Confirmar Endereço de E-mail → + Verify Email Address →

- Se você não criou esta conta, pode ignorar este e-mail com segurança. + If you did not create an account, you can safely ignore this email.

- Atenciosamente, + Best regards,
- Equipe Clinyx + The TryPost Team

@@ -41,4 +41,4 @@
- \ No newline at end of file + diff --git a/maizzle/templates/password-reset.html b/maizzle/templates/password-reset.html index c7e40ef4..a0b3b0a8 100644 --- a/maizzle/templates/password-reset.html +++ b/maizzle/templates/password-reset.html @@ -8,30 +8,30 @@

- Olá @{{$user->name}}, + Hi @{{ $user->name }},

- Recebemos uma solicitação para redefinir a senha da sua conta. Clique no botão abaixo para criar uma nova senha: + We received a request to reset your password. Click the button below to create a new password:

- Redefinir Senha → + Reset Password →

- Este link expira em 60 minutos. Se você não solicitou a redefinição de senha, pode ignorar este e-mail com segurança. + This link expires in 60 minutes. If you didn't request a password reset, you can safely ignore this email.

- Atenciosamente, + Best regards,
- Equipe Clinyx + The TryPost Team

diff --git a/maizzle/templates/invite.html b/maizzle/templates/workspace-invite.html similarity index 57% rename from maizzle/templates/invite.html rename to maizzle/templates/workspace-invite.html index c177c2ec..64f9f919 100644 --- a/maizzle/templates/invite.html +++ b/maizzle/templates/workspace-invite.html @@ -1,7 +1,3 @@ ---- -bodyClass: bg-slate-50 -preheader: Convite para o sistema da Clinyx ----
@@ -13,23 +9,30 @@

- Olá 👋 + You've been invited!

- Você foi convidado para acessar o sistema da Clinyx. -
-
- Para aceitar o convite e começar a utilizar a plataforma, clique no botão abaixo. + @{{ $invite->inviter->name }} has invited you to collaborate on the @{{ $invite->workspace->name }} workspace. +

+ +

+ You've been invited as @{{ $invite->role->label() }}.

- Aceitar convite → + Accept Invite →
+ + + +

+ This invite expires in 7 days. +

@@ -38,4 +41,4 @@

-
\ No newline at end of file + diff --git a/public/images/emails/logo.png b/public/images/emails/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8636fa4033367af0e92e2517dcfc9ab7d446435b GIT binary patch literal 6125 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 KZY2yI_5T19p@vTY literal 0 HcmV?d00001 diff --git a/resources/js/layouts/settings/Layout.vue b/resources/js/layouts/settings/Layout.vue index eed816c6..3715f583 100644 --- a/resources/js/layouts/settings/Layout.vue +++ b/resources/js/layouts/settings/Layout.vue @@ -1,5 +1,6 @@ diff --git a/resources/js/pages/auth/Login.vue b/resources/js/pages/auth/Login.vue index e38bc42b..cb7581ee 100644 --- a/resources/js/pages/auth/Login.vue +++ b/resources/js/pages/auth/Login.vue @@ -17,6 +17,7 @@ defineProps<{ status?: string; canResetPassword: boolean; canRegister: boolean; + email?: string | null; }>(); @@ -52,6 +53,7 @@ defineProps<{ :tabindex="1" autocomplete="email" placeholder="email@example.com" + :default-value="email ?? ''" /> diff --git a/resources/js/pages/auth/Register.vue b/resources/js/pages/auth/Register.vue index 55088de7..d8034d0c 100644 --- a/resources/js/pages/auth/Register.vue +++ b/resources/js/pages/auth/Register.vue @@ -10,6 +10,10 @@ import { Spinner } from '@/components/ui/spinner'; import AuthBase from '@/layouts/AuthLayout.vue'; import { login } from '@/routes'; import { store } from '@/routes/register'; + +defineProps<{ + email?: string | null; +}>();