mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-20 10:23:30 +00:00
feat(ui): align auth pages to shared shell and truncate team names
Move email verification and team invitation into x-auth.shell with shared guidance/alert patterns. Truncate long team names in the mobile header switcher so actions stay visible. Cover both with markup tests.
This commit is contained in:
@@ -1,12 +1,19 @@
|
||||
<x-layout>
|
||||
<section class="flex flex-col h-full lg:items-center lg:justify-center">
|
||||
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto max-w-7xl lg:py-0">
|
||||
<h1> Verification Email Sent </h1>
|
||||
<div class="flex justify-center gap-2 text-center">
|
||||
<br>To activate your account, please open the email and follow the
|
||||
instructions.
|
||||
<x-layout-simple>
|
||||
<x-auth.shell title="Coolify" description="Verify your email address to activate your account.">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="auth-guidance">
|
||||
<x-reicon name="mail" class="mt-0.5 size-4 shrink-0" />
|
||||
<p>We sent a verification link to your email address. Open it to continue to Coolify.</p>
|
||||
</div>
|
||||
|
||||
<livewire:verify-email />
|
||||
</div>
|
||||
</section>
|
||||
</x-layout>
|
||||
|
||||
<x-slot:footer>
|
||||
<span class="text-center">
|
||||
<span class="block sm:inline">Didn’t receive the email?</span>
|
||||
<span class="block sm:ml-1 sm:inline">Check your spam folder or resend it.</span>
|
||||
</span>
|
||||
</x-slot:footer>
|
||||
</x-auth.shell>
|
||||
</x-layout-simple>
|
||||
|
||||
@@ -1,43 +1,32 @@
|
||||
<x-layout-simple>
|
||||
<section class="bg-gray-50 dark:bg-base">
|
||||
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
|
||||
<div class="w-full max-w-md space-y-8">
|
||||
<div class="text-center space-y-2">
|
||||
<h1 class="!text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
|
||||
Coolify
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="p-6 rounded-lg border border-neutral-500/20 bg-white/5">
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">Team Invitation</h2>
|
||||
|
||||
<p class="text-sm text-gray-600 dark:text-neutral-400 mb-2">
|
||||
You have been invited to join:
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
{{ $team->name }}
|
||||
</p>
|
||||
|
||||
<p class="text-sm text-gray-600 dark:text-neutral-400 mb-1">
|
||||
Role: <span class="font-medium text-gray-900 dark:text-white">{{ ucfirst($invitation->role) }}</span>
|
||||
</p>
|
||||
|
||||
@if ($alreadyMember)
|
||||
<div class="mt-4 p-3 bg-warning/10 border border-warning rounded-lg">
|
||||
<p class="text-sm text-warning">You are already a member of this team.</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('team.invitation.accept', $invitation->uuid) }}" class="mt-6">
|
||||
@csrf
|
||||
<x-forms.button class="w-full justify-center py-3 box-boarding" type="submit" isHighlighted>
|
||||
{{ $alreadyMember ? 'Dismiss Invitation' : 'Accept Invitation' }}
|
||||
</x-forms.button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<x-auth.shell title="Coolify" description="Review your invitation to join a team.">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="auth-guidance">
|
||||
<x-reicon name="teams" class="mt-0.5 size-4 shrink-0" />
|
||||
<p>You have been invited to collaborate on Coolify.</p>
|
||||
</div>
|
||||
|
||||
<dl class="divide-y divide-neutral-200 rounded-lg border border-neutral-200 text-sm dark:divide-white/10 dark:border-white/10">
|
||||
<div class="flex items-center justify-between gap-4 px-3 py-2.5">
|
||||
<dt class="text-neutral-500 dark:text-fg-dim">Team</dt>
|
||||
<dd class="min-w-0 truncate font-medium text-neutral-900 dark:text-white">{{ $team->name }}</dd>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4 px-3 py-2.5">
|
||||
<dt class="text-neutral-500 dark:text-fg-dim">Role</dt>
|
||||
<dd class="font-medium text-neutral-900 dark:text-white">{{ ucfirst($invitation->role) }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
@if ($alreadyMember)
|
||||
<x-auth.alert type="warning">You are already a member of this team. Dismiss the invitation to continue.</x-auth.alert>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('team.invitation.accept', $invitation->uuid) }}">
|
||||
@csrf
|
||||
<x-forms.button class="w-full justify-center" type="submit" isHighlighted>
|
||||
{{ $alreadyMember ? 'Dismiss invitation' : 'Accept invitation' }}
|
||||
</x-forms.button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</x-auth.shell>
|
||||
</x-layout-simple>
|
||||
|
||||
@@ -94,15 +94,16 @@
|
||||
{{-- ============ MOBILE TOP BAR ============ --}}
|
||||
<div
|
||||
class="sticky top-0 z-40 flex items-center justify-between px-4 py-3 gap-x-4 sm:px-6 lg:hidden bg-white/95 dark:bg-panel/95 backdrop-blur-sm border-b border-neutral-200/60 dark:border-white/[0.06]">
|
||||
<div class="flex items-center gap-2.5 flex-shrink-0 min-w-0">
|
||||
<a href="/" class="flex items-center justify-center size-8 rounded-lg bg-neutral-100 dark:bg-white/[0.06] hover:opacity-80 transition-opacity">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-2.5">
|
||||
<a href="/"
|
||||
class="flex size-8 shrink-0 items-center justify-center rounded-lg bg-neutral-100 transition-opacity hover:opacity-80 dark:bg-white/[0.06]">
|
||||
<img src="/coolify-logo.svg" alt="Coolify" class="w-[18px] h-[18px]" />
|
||||
</a>
|
||||
<div class="min-w-0" x-data="{ collapsed: false }">
|
||||
<livewire:switch-team />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex min-w-0 items-center gap-1">
|
||||
<div class="flex shrink-0 items-center gap-1">
|
||||
{{-- Dev Server-Timing HUD docks here on <lg (desktop uses #server-timing-hud-slot) --}}
|
||||
<div id="server-timing-hud-slot-mobile" data-server-timing-hud-slot
|
||||
class="hidden shrink-0 items-center"></div>
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
@endphp
|
||||
<div class="min-w-0">
|
||||
{{-- Expanded: inline switcher (team name + up/down chevron) --}}
|
||||
<div class="relative" :class="collapsed && 'lg:hidden'"
|
||||
<div class="relative min-w-0" :class="collapsed && 'lg:hidden'"
|
||||
x-data="{ open: false }" @keydown.escape.window="open = false">
|
||||
<button type="button" @click="open = !open" @click.outside="open = false"
|
||||
title="Switch team"
|
||||
class="group/team flex items-center gap-1.5 h-8 px-2 -ml-1 rounded-lg text-left opacity-70 transition-[background-color,opacity] hover:opacity-100 hover:bg-neutral-100 dark:hover:bg-white/[0.05]">
|
||||
<span class="whitespace-nowrap text-[13px] font-semibold text-black dark:text-fg">{{ $currentTeam->name }}</span>
|
||||
class="group/team flex h-8 min-w-0 max-w-full items-center gap-1.5 rounded-lg px-2 -ml-1 text-left opacity-70 transition-[background-color,opacity] hover:bg-neutral-100 hover:opacity-100 dark:hover:bg-white/[0.05]">
|
||||
<span class="min-w-0 truncate text-[13px] font-semibold text-black dark:text-fg">{{ $currentTeam->name }}</span>
|
||||
<svg class="size-4 shrink-0 text-neutral-400 dark:text-fg-faint" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M8 9l4-4 4 4M8 15l4 4 4-4" stroke="currentColor" stroke-width="1.6"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<div class="pt-4">
|
||||
<x-forms.button wire:click="again">Send Verification Email Again</x-forms.button>
|
||||
<div>
|
||||
<x-forms.button class="w-full justify-center" wire:click="again" isHighlighted>
|
||||
Resend verification email
|
||||
</x-forms.button>
|
||||
</div>
|
||||
|
||||
@@ -45,3 +45,28 @@ test('two factor challenge page uses the shared auth shell', function () {
|
||||
->not->toContain('bg-gray-50 dark:bg-base')
|
||||
->not->toContain('!text-5xl font-extrabold');
|
||||
});
|
||||
|
||||
test('email verification page uses the shared auth shell', function () {
|
||||
$verification = file_get_contents(resource_path('views/auth/verify-email.blade.php'));
|
||||
|
||||
expect($verification)
|
||||
->toContain('x-layout-simple')
|
||||
->toContain('x-auth.shell')
|
||||
->toContain('title="Coolify"')
|
||||
->toContain('auth-guidance')
|
||||
->toContain('block sm:inline')
|
||||
->not->toContain('<x-layout>')
|
||||
->not->toContain('md:h-screen');
|
||||
});
|
||||
|
||||
test('team invitation page uses the shared auth shell', function () {
|
||||
$invitation = file_get_contents(resource_path('views/invitation/accept.blade.php'));
|
||||
|
||||
expect($invitation)
|
||||
->toContain('x-auth.shell')
|
||||
->toContain('title="Coolify"')
|
||||
->toContain('x-auth.alert')
|
||||
->toContain('Accept invitation')
|
||||
->not->toContain('bg-gray-50 dark:bg-base')
|
||||
->not->toContain('!text-5xl font-extrabold');
|
||||
});
|
||||
|
||||
@@ -40,6 +40,19 @@ it('separates the mobile sidebar from the page with a visible border', function
|
||||
expect($layout)->toContain('max-w-56 min-w-0 flex-col border-l border-neutral-200 bg-white shadow-xl dark:border-white/[0.12] dark:bg-panel');
|
||||
});
|
||||
|
||||
it('truncates long team names within the mobile header', function () {
|
||||
$layout = file_get_contents(resource_path('views/layouts/app.blade.php'));
|
||||
$switcher = file_get_contents(resource_path('views/livewire/switch-team.blade.php'));
|
||||
|
||||
expect($layout)
|
||||
->toContain('flex min-w-0 flex-1 items-center gap-2.5')
|
||||
->toContain('size-8 shrink-0 items-center justify-center rounded-lg')
|
||||
->toContain('flex shrink-0 items-center gap-1')
|
||||
->and($switcher)
|
||||
->toContain('group/team flex h-8 min-w-0 max-w-full items-center')
|
||||
->toContain('min-w-0 truncate text-[13px]');
|
||||
});
|
||||
|
||||
it('shows section titles and descriptions above settings navigation on smaller screens', function (string $view, string $title, string $description) {
|
||||
$layout = file_get_contents(resource_path("views/components/{$view}"));
|
||||
$css = file_get_contents(resource_path('css/app.css'));
|
||||
|
||||
@@ -30,7 +30,7 @@ test('GET invitation shows landing page without accepting', function () {
|
||||
$response->assertStatus(200);
|
||||
$response->assertViewIs('invitation.accept');
|
||||
$response->assertSee($this->team->name);
|
||||
$response->assertSee('Accept Invitation');
|
||||
$response->assertSee('Accept invitation');
|
||||
|
||||
// Invitation should NOT be deleted (not accepted yet)
|
||||
$this->assertDatabaseHas('team_invitations', [
|
||||
|
||||
Reference in New Issue
Block a user