feat(onboarding): redesign user onboarding flow with modern UI/UX

- Add centered, card-based layout with clean design
- Implement 3-step progress indicator component
- Add proper dark/light mode support following Coolify design system
- Implement Livewire URL state persistence for browser navigation
- Separate private key textareas for "Generate" vs "Add your own" modes
- Consistent checkpoint styling across all onboarding phases
- Enhanced typography with prominent titles (semibold, white in dark mode)
- Fixed state restoration on page refresh and browser back/forward navigation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai 2025-10-12 17:59:37 +02:00
parent 821aa6a531
commit 7a008c859a
5 changed files with 675 additions and 214 deletions

View file

@ -16,14 +16,18 @@ class Index extends Component
{ {
protected $listeners = ['refreshBoardingIndex' => 'validateServer']; protected $listeners = ['refreshBoardingIndex' => 'validateServer'];
#[\Livewire\Attributes\Url(as: 'step', history: true)]
public string $currentState = 'welcome'; public string $currentState = 'welcome';
#[\Livewire\Attributes\Url(keep: true)]
public ?string $selectedServerType = null; public ?string $selectedServerType = null;
public ?Collection $privateKeys = null; public ?Collection $privateKeys = null;
#[\Livewire\Attributes\Url(keep: true)]
public ?int $selectedExistingPrivateKey = null; public ?int $selectedExistingPrivateKey = null;
#[\Livewire\Attributes\Url(keep: true)]
public ?string $privateKeyType = null; public ?string $privateKeyType = null;
public ?string $privateKey = null; public ?string $privateKey = null;
@ -38,6 +42,7 @@ class Index extends Component
public ?Collection $servers = null; public ?Collection $servers = null;
#[\Livewire\Attributes\Url(keep: true)]
public ?int $selectedExistingServer = null; public ?int $selectedExistingServer = null;
public ?string $remoteServerName = null; public ?string $remoteServerName = null;
@ -58,6 +63,7 @@ class Index extends Component
public Collection $projects; public Collection $projects;
#[\Livewire\Attributes\Url(keep: true)]
public ?int $selectedProject = null; public ?int $selectedProject = null;
public ?Project $createdProject = null; public ?Project $createdProject = null;
@ -79,17 +85,63 @@ public function mount()
$this->minDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.'); $this->minDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.');
$this->privateKeyName = generate_random_name(); $this->privateKeyName = generate_random_name();
$this->remoteServerName = generate_random_name(); $this->remoteServerName = generate_random_name();
if (isDev()) {
$this->privateKey = '-----BEGIN OPENSSH PRIVATE KEY----- // Initialize collections to avoid null errors
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW if ($this->privateKeys === null) {
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk $this->privateKeys = collect();
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA }
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV if ($this->servers === null) {
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== $this->servers = collect();
-----END OPENSSH PRIVATE KEY-----'; }
$this->privateKeyDescription = 'Created by Coolify'; if (! isset($this->projects)) {
$this->remoteServerDescription = 'Created by Coolify'; $this->projects = collect();
$this->remoteServerHost = 'coolify-testing-host'; }
// Restore state when coming from URL with query params
if ($this->selectedServerType === 'localhost' && $this->selectedExistingServer === 0) {
$this->createdServer = Server::find(0);
if ($this->createdServer) {
$this->serverPublicKey = $this->createdServer->privateKey->getPublicKey();
}
}
if ($this->selectedServerType === 'remote') {
if ($this->privateKeys->isEmpty()) {
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
}
if ($this->servers->isEmpty()) {
$this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
}
if ($this->selectedExistingServer) {
$this->createdServer = Server::find($this->selectedExistingServer);
if ($this->createdServer) {
$this->serverPublicKey = $this->createdServer->privateKey->getPublicKey();
$this->updateServerDetails();
}
}
if ($this->selectedExistingPrivateKey) {
$this->createdPrivateKey = PrivateKey::where('team_id', currentTeam()->id)
->where('id', $this->selectedExistingPrivateKey)
->first();
if ($this->createdPrivateKey) {
$this->privateKey = $this->createdPrivateKey->private_key;
$this->publicKey = $this->createdPrivateKey->getPublicKey();
}
}
// Auto-regenerate key pair for "Generate with Coolify" mode on page refresh
if ($this->privateKeyType === 'create' && empty($this->privateKey)) {
$this->createNewPrivateKey();
}
}
if ($this->selectedProject) {
$this->createdProject = Project::find($this->selectedProject);
if (! $this->createdProject) {
$this->projects = Project::ownedByCurrentTeam(['name'])->get();
}
} }
} }
@ -129,11 +181,7 @@ public function setServerType(string $type)
return $this->validateServer('localhost'); return $this->validateServer('localhost');
} elseif ($this->selectedServerType === 'remote') { } elseif ($this->selectedServerType === 'remote') {
if (isDev()) { $this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->get();
} else {
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
}
if ($this->privateKeys->count() > 0) { if ($this->privateKeys->count() > 0) {
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id; $this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
} }
@ -202,6 +250,9 @@ public function setPrivateKey(string $type)
$this->privateKeyType = $type; $this->privateKeyType = $type;
if ($type === 'create') { if ($type === 'create') {
$this->createNewPrivateKey(); $this->createNewPrivateKey();
} else {
$this->privateKey = null;
$this->publicKey = null;
} }
$this->currentState = 'create-private-key'; $this->currentState = 'create-private-key';
} }

View file

@ -0,0 +1,47 @@
@props(['currentStep' => 1, 'totalSteps' => 3])
<div class="w-full max-w-2xl mx-auto mb-8">
<div class="flex items-center justify-between">
@for ($i = 1; $i <= $totalSteps; $i++)
<div class="flex items-center {{ $i < $totalSteps ? 'flex-1' : '' }}">
<div class="flex flex-col items-center">
<div
class="flex items-center justify-center size-10 rounded-full border-2 transition-all duration-300
{{ $i < $currentStep ? 'bg-success border-success' : '' }}
{{ $i === $currentStep ? 'bg-white dark:bg-coolgray-100' : '' }}
">
@if ($i < $currentStep)
<svg class="size-5 text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
@else
<span
class="text-sm font-bold text-black dark:text-white">
{{ $i }}
</span>
@endif
</div>
<span
class="mt-2 text-xs font-medium text-black dark:text-white">
@if ($i === 1)
Server
@elseif ($i === 2)
Connection
@elseif ($i === 3)
Complete
@endif
</span>
</div>
@if ($i < $totalSteps)
<div
class="flex-1 h-0.5 mx-4 transition-all duration-300
{{ $i < $currentStep ? 'bg-success' : 'bg-coolgray-200 dark:bg-coolgray-600' }}">
</div>
@endif
</div>
@endfor
</div>
</div>

View file

@ -1,25 +1,29 @@
<div class="grid grid-cols-1 gap-4 md:grid-cols-3"> <div class="w-full max-w-4xl">
<div class="box-border col-span-2 lg:min-w-[24rem] lg:min-h-[21rem]"> <div class="bg-white dark:bg-coolgray-100 rounded-lg shadow-sm border border-neutral-200 dark:border-coolgray-300 overflow-hidden">
<h1 class="text-2xl font-bold lg:text-5xl">{{ $title }}</h1> <div class="p-8 lg:p-12">
<div class="py-6"> <h1 class="text-3xl font-bold lg:text-4xl mb-4">{{ $title }}</h1>
@isset($question) @isset($question)
<p class="dark:text-neutral-400"> <div class="text-base lg:text-lg dark:text-neutral-400 mb-8">
{{ $question }} {{ $question }}
</p> </div>
@endisset @endisset
@if ($actions)
<div class="flex flex-col gap-4">
{{ $actions }}
</div>
@endif
</div> </div>
@if ($actions)
<div class="flex flex-col flex-wrap gap-4 lg:items-center md:flex-row"> @isset($explanation)
{{ $actions }} <div class="dark:bg-coolgray-200 border-t border-neutral-200 dark:border-coolgray-300 p-8 lg:p-12 bg-neutral-50">
<h3 class="text-sm font-bold uppercase tracking-wide mb-4 dark:text-neutral-400">
Technical Details
</h3>
<div class="space-y-3 text-sm dark:text-neutral-400">
{{ $explanation }}
</div>
</div> </div>
@endif @endisset
</div> </div>
@isset($explanation)
<div class="col-span-1">
<h3 class="pb-8 font-bold">Explanation</h3>
<div class="space-y-4">
{{ $explanation }}
</div>
</div>
@endisset
</div> </div>

View file

@ -1,6 +1,6 @@
@extends('layouts.base') @extends('layouts.base')
@section('body') @section('body')
<main class="h-full"> <main class="min-h-screen flex items-center justify-center p-4">
{{ $slot }} {{ $slot }}
</main> </main>
@parent @parent

View file

@ -2,68 +2,160 @@
<x-slot:title> <x-slot:title>
Onboarding | Coolify Onboarding | Coolify
</x-slot> </x-slot>
<section class="flex flex-col h-full lg:items-center lg:justify-center"> <section class="w-full">
<div <div class="flex flex-col items-center w-full space-y-8">
class="flex flex-col items-center justify-center p-10 mx-2 mt-10 bg-white border rounded-lg shadow-sm lg:p-20 dark:bg-transparent dark:border-none max-w-7xl border-neutral-200">
@if ($currentState === 'welcome') @if ($currentState === 'welcome')
<h1 class="text-3xl font-bold lg:text-5xl">Welcome to Coolify</h1> <div class="w-full max-w-2xl text-center space-y-8">
<div class="py-6 text-center lg:text-xl">Let me help you set up the basics.</div> <div class="space-y-4">
<div class="flex justify-center "> <h1 class="text-4xl font-bold lg:text-6xl">Welcome to Coolify</h1>
<x-forms.button class="justify-center w-64 box-boarding" <p class="text-lg lg:text-xl dark:text-neutral-400">
wire:click="$set('currentState','explanation')">Get Connect your first server and start deploying in minutes
Started </p>
</x-forms.button> </div>
<div
class="bg-white dark:bg-coolgray-100 rounded-lg shadow-sm border border-neutral-200 dark:border-coolgray-300 p-8 text-left">
<h2 class="text-sm font-bold uppercase tracking-wide dark:text-neutral-400 mb-4">
What You'll Set Up
</h2>
<div class="space-y-3">
<div class="flex items-start gap-3">
<div class="flex-shrink-0 mt-0.5">
<svg class="size-5 text-success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</div>
<div>
<div class="font-semibold text-base dark:text-white">Server Connection</div>
<div class="text-sm dark:text-neutral-400">Connect via SSH to deploy your resources</div>
</div>
</div>
<div class="flex items-start gap-3">
<div class="flex-shrink-0 mt-0.5">
<svg class="size-5 text-success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</div>
<div>
<div class="font-semibold text-base dark:text-white">Docker Environment</div>
<div class="text-sm dark:text-neutral-400">Automated installation and configuration</div>
</div>
</div>
<div class="flex items-start gap-3">
<div class="flex-shrink-0 mt-0.5">
<svg class="size-5 text-success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</div>
<div>
<div class="font-semibold text-base dark:text-white">Project Structure</div>
<div class="text-sm dark:text-neutral-400">Organize your applications and resources</div>
</div>
</div>
</div>
</div>
<div class="flex justify-center pt-4">
<x-forms.button class="justify-center px-12 py-4 text-lg font-bold box-boarding"
wire:click="explanation">
Start Setup
</x-forms.button>
</div>
</div> </div>
@elseif ($currentState === 'explanation') @elseif ($currentState === 'explanation')
<x-boarding-step title="What is Coolify?"> <x-boarding-progress :currentStep="0" />
<x-boarding-step title="Platform Overview">
<x-slot:question> <x-slot:question>
Coolify is an all-in-one application to automate tasks on your servers, deploy applications with Coolify automates deployment and infrastructure management on your own servers. Deploy applications
Git from Git, manage databases, and monitor everything—without vendor lock-in.
integrations, deploy databases and services, monitor these resources with notifications and
alerts
without vendor lock-in. <br />
<a href="https://coolify.io" class="dark:text-white hover:underline">Coolify Home</a>.
<br><br>
<span class="text-xl">
<x-highlighted text="Self-hosting with superpowers!" /></span>
</x-slot:question> </x-slot:question>
<x-slot:explanation> <x-slot:explanation>
<p> <p>
<x-highlighted text="Task automation:" /> You don't need to manage your servers anymore. <x-highlighted text="Automation:" /> Coolify handles server configuration, Docker management, and
Coolify does deployments automatically.
it for you.
</p> </p>
<p> <p>
<x-highlighted text="No vendor lock-in:" /> All configurations are stored on your servers, so <x-highlighted text="Self-hosted:" /> All data and configurations live on your infrastructure.
everything works without a connection to Coolify (except integrations and automations). Works offline except for external integrations.
</p> </p>
<p> <p>
<x-highlighted text="Monitoring:" />You can get notified on your favourite platforms <x-highlighted text="Monitoring & Alerts:" /> Get real-time notifications via Discord, Telegram,
(Discord, Email, and other platforms.
Telegram, Email, etc.) when something goes wrong, or an action is needed from your side.
</p> </p>
</x-slot:explanation> </x-slot:explanation>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center w-64 box-boarding" wire:click="explanation">Next <x-forms.button class="justify-center w-full lg:w-auto px-8 py-3 box-boarding"
wire:click="explanation">
Continue
</x-forms.button> </x-forms.button>
</x-slot:actions> </x-slot:actions>
</x-boarding-step> </x-boarding-step>
@elseif ($currentState === 'select-server-type') @elseif ($currentState === 'select-server-type')
<x-boarding-step title="Server"> <x-boarding-progress :currentStep="1" />
<x-boarding-step title="Choose Server Type">
<x-slot:question> <x-slot:question>
Do you want to deploy your resources to your Select where to deploy your applications and databases. You can add more servers later.
<x-highlighted text="Localhost" />
or to a
<x-highlighted text="Remote Server" />?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center w-64 box-boarding" wire:target="setServerType('localhost')" <div class="grid grid-cols-1 lg:grid-cols-2 gap-4 w-full">
wire:click="setServerType('localhost')">Localhost <button
</x-forms.button> class="group relative box-without-bg cursor-pointer hover:border-coollabs transition-all duration-200 p-6"
wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between">
<svg class="size-10"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z" />
</svg>
<span
class="px-2 py-1 text-xs font-bold uppercase tracking-wide bg-neutral-100 dark:bg-coolgray-300 dark:text-neutral-400 rounded">
Quick Start
</span>
</div>
<div>
<h3 class="text-xl font-bold mb-2">This Machine</h3>
<p class="text-sm dark:text-neutral-400">
Deploy on the server running Coolify. Best for testing and single-server setups.
</p>
</div>
</div>
</button>
<x-forms.button class="justify-center w-64 box-boarding " wire:target="setServerType('remote')" <button
wire:click="setServerType('remote')">Remote Server class="group relative box-without-bg cursor-pointer hover:border-coollabs transition-all duration-200 p-6"
</x-forms.button> wire:target="setServerType('remote')" wire:click="setServerType('remote')">
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between">
<svg class="size-10 " xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.25 15a4.5 4.5 0 004.5 4.5H18a3.75 3.75 0 001.332-7.257 3 3 0 00-3.758-3.848 5.25 5.25 0 00-10.233 2.33A4.502 4.502 0 002.25 15z" />
</svg>
<span
class="px-2 py-1 text-xs font-bold uppercase tracking-wide bg-warning/10 dark:bg-warning/20 text-warning rounded">
Recommended
</span>
</div>
<div>
<h3 class="text-xl font-bold mb-2">Remote Server</h3>
<p class="text-sm dark:text-neutral-400">
Connect via SSH to any server—cloud VPS, bare metal, or home infrastructure.
</p>
</div>
</div>
</button>
</div>
@if (!$serverReachable) @if (!$serverReachable)
<div class="mt-6 p-4 border border-error rounded-lg text-gray-800 dark:text-gray-200"> <div class="mt-6 p-4 border border-error rounded-lg text-gray-800 dark:text-gray-200">
@ -111,51 +203,97 @@ class="bg-red-200 dark:bg-red-900 px-1 rounded-sm">~/.ssh/authorized_keys</code>
@endif @endif
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>Servers are the main building blocks, as they will host your applications, databases,
services, called resources. Any CPU intensive process will use the server's CPU where you
are deploying your resources.</p>
<p> <p>
<x-highlighted text="Localhost" /> is the server where Coolify is running on. It is not <x-highlighted text="Servers" /> host your applications, databases, and services (collectively
recommended to use one server called resources). All CPU-intensive operations run on the target server.
for everything.
</p> </p>
<p> <p>
<x-highlighted text="A remote server" /> is a server reachable through SSH. It can be hosted <x-highlighted text="Localhost:" /> The machine running Coolify. Not recommended for production
at home, or from any cloud workloads due to resource contention.
provider. </p>
<p>
<x-highlighted text="Remote Server:" /> Any SSH-accessible server—cloud providers (AWS, Hetzner,
DigitalOcean), bare metal, or self-hosted infrastructure.
</p> </p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@elseif ($currentState === 'private-key') @elseif ($currentState === 'private-key')
<x-boarding-step title="SSH Key"> <x-boarding-progress :currentStep="2" />
<x-boarding-step title="SSH Authentication">
<x-slot:question> <x-slot:question>
Do you have your own SSH Private Key? Configure SSH key-based authentication for secure server access.
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center lg:w-64 box-boarding" wire:target="setPrivateKey('own')" @if ($privateKeys && $privateKeys->count() > 0)
wire:click="setPrivateKey('own')">Yes <div class="w-full space-y-4">
</x-forms.button> <div class="p-4 rounded-lg border border-neutral-200 dark:border-coolgray-400">
<x-forms.button class="justify-center lg:w-64 box-boarding" wire:target="setPrivateKey('create')" <form wire:submit='selectExistingPrivateKey' class="flex flex-col gap-4">
wire:click="setPrivateKey('create')">No (create one for me) <x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'>
</x-forms.button> @foreach ($privateKeys as $privateKey)
@if (count($privateKeys) > 0) <option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}">
<form wire:submit='selectExistingPrivateKey' class="flex flex-col w-full gap-4 lg:pr-10"> {{ $privateKey->name }}</option>
<x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'> @endforeach
@foreach ($privateKeys as $privateKey) </x-forms.select>
<option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}"> <x-forms.button type="submit" class="w-full lg:w-auto">Use Selected Key</x-forms.button>
{{ $privateKey->name }}</option> </form>
@endforeach </div>
</x-forms.select> <div class="relative">
<x-forms.button type="submit">Use this SSH Key</x-forms.button> <div class="absolute inset-0 flex items-center">
</form> <div class="w-full border-t border-neutral-300 dark:border-coolgray-400"></div>
</div>
<div class="relative flex justify-center text-sm">
<div
class="px-2 py-1 bg-white dark:bg-coolgray-100 border border-neutral-300 dark:border-coolgray-300 rounded text-xs font-bold text-neutral-500 dark:text-neutral-400">
OR
</div>
</div>
</div>
</div>
@endif @endif
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 w-full">
<x-forms.button class="justify-center h-auto py-6 box-without-bg hover:border-coollabs transition-all duration-200" wire:target="setPrivateKey('own')"
wire:click="setPrivateKey('own')">
<div class="flex flex-col items-center gap-2">
<svg class="size-8" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" />
</svg>
<div class="text-center">
<h3 class="text-xl font-bold mb-2">Use Existing Key</h3>
<p class="text-sm dark:text-neutral-400">I have my own SSH key</p>
</div>
</div>
</x-forms.button>
<x-forms.button class="justify-center h-auto py-6 box-without-bg hover:border-coollabs transition-all duration-200" wire:target="setPrivateKey('create')"
wire:click="setPrivateKey('create')">
<div class="flex flex-col items-center gap-2">
<svg class="size-8" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M19 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zM4 19.235v-.11a6.375 6.375 0 0112.75 0v.109A12.318 12.318 0 0110.374 21c-2.331 0-4.512-.645-6.374-1.766z" />
</svg>
<div class="text-center">
<h3 class="text-xl font-bold mb-2">Generate New Key</h3>
<p class="text-sm dark:text-neutral-400">Create ED25519 key pair</p>
</div>
</div>
</x-forms.button>
</div>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p> <p>
<p>You can use your own ssh private key, or you can let Coolify to create one for you.</p> <x-highlighted text="SSH Key Authentication:" /> Uses public-key cryptography for secure,
<p>In both ways, you need to add the public version of your ssh private key to the remote password-less server access.
server's </p>
<code class="dark:text-warning">~/.ssh/authorized_keys</code> file. <p>
<x-highlighted text="Public Key Deployment:" /> Add the public key to your server's
<code class="text-xs bg-coolgray-300 dark:bg-coolgray-400 px-1 py-0.5 rounded">~/.ssh/authorized_keys</code>
file.
</p>
<p>
<x-highlighted text="Key Generation:" /> Coolify generates ED25519 keys by default for optimal
security and performance.
</p> </p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@ -237,164 +375,385 @@ class="bg-red-200 dark:bg-red-900 px-1 rounded-sm">~/.ssh/authorized_keys</code>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@elseif ($currentState === 'create-private-key') @elseif ($currentState === 'create-private-key')
<x-boarding-step title="Create Private Key"> <x-boarding-progress :currentStep="2" />
<x-boarding-step title="SSH Key Configuration">
<x-slot:question> <x-slot:question>
Please let me know your key details. Configure your SSH key for server authentication.
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<form wire:submit='savePrivateKey' class="flex flex-col w-full gap-4 lg:pr-10"> <form wire:submit='savePrivateKey' class="flex flex-col w-full gap-4">
<x-forms.input required placeholder="Choose a name for your Private Key. Could be anything." <x-forms.input required placeholder="e.g., production-server-key"
label="Name" id="privateKeyName" /> label="Key Name" id="privateKeyName" />
<x-forms.input placeholder="Description, so others will know more about this." <x-forms.input placeholder="Optional: Note what this key is used for"
label="Description" id="privateKeyDescription" /> label="Description" id="privateKeyDescription" />
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
label="Private Key" id="privateKey" />
@if ($privateKeyType === 'create') @if ($privateKeyType === 'create')
<x-forms.textarea required readonly label="Private Key" id="privateKey" rows="8" />
<x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" /> <x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" />
<span class="font-bold dark:text-warning">ACTION REQUIRED: Copy the 'Public Key' to your @else
server's <x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
~/.ssh/authorized_keys label="Private Key" id="privateKey" rows="8" />
file.</span>
@endif @endif
<x-forms.button type="submit">Save</x-forms.button> @if ($privateKeyType === 'create')
<div class="p-4 bg-warning/10 border border-warning rounded-lg">
<div class="flex gap-3">
<svg class="size-5 text-warning flex-shrink-0 mt-0.5" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
clip-rule="evenodd" />
</svg>
<div>
<p class="font-bold text-warning mb-1">Action Required</p>
<p class="text-sm dark:text-white text-black">
Copy the public key above and add it to your server's
<code
class="text-xs bg-coolgray-300 dark:bg-coolgray-400 px-1 py-0.5 rounded">~/.ssh/authorized_keys</code>
file.
</p>
</div>
</div>
</div>
@endif
<x-forms.button type="submit" class="w-full lg:w-auto">Save SSH Key</x-forms.button>
</form> </form>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p> <p>
<p>You can use your own private key, or you can let Coolify to create one for you.</p> <x-highlighted text="Key Storage:" /> Private keys are encrypted at rest in Coolify's database.
<p>In both ways, you need to add the public version of your private key to the remote server's </p>
<code>~/.ssh/authorized_keys</code> file. <p>
<x-highlighted text="Public Key Distribution:" /> Deploy the public key to
<code class="text-xs bg-coolgray-300 dark:bg-coolgray-400 px-1 py-0.5 rounded">~/.ssh/authorized_keys</code>
on your target server for the specified user.
</p>
<p>
<x-highlighted text="Key Format:" /> Supports RSA, ED25519, ECDSA, and DSA key types in OpenSSH
format.
</p> </p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@elseif ($currentState === 'create-server') @elseif ($currentState === 'create-server')
<x-boarding-step title="Create Server"> <x-boarding-progress :currentStep="2" />
<x-boarding-step title="Server Configuration">
<x-slot:question> <x-slot:question>
Please let me know your server details. Provide connection details for your remote server.
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<form wire:submit='saveServer' class="flex flex-col w-full gap-4 lg:w-96"> <form wire:submit='saveServer' class="flex flex-col w-full gap-4">
<x-forms.input required placeholder="Choose a name for your Server. Could be anything." <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
label="Name" id="remoteServerName" wire:model="remoteServerName" /> <x-forms.input required placeholder="e.g., production-app-server"
<x-forms.input placeholder="Description, so others will know more about this." label="Server Name" id="remoteServerName" wire:model="remoteServerName" />
<x-forms.input required placeholder="IP address or hostname" label="IP Address/Hostname"
id="remoteServerHost" wire:model="remoteServerHost" />
</div>
<x-forms.input placeholder="Optional: Note what this server hosts"
label="Description" id="remoteServerDescription" wire:model="remoteServerDescription" /> label="Description" id="remoteServerDescription" wire:model="remoteServerDescription" />
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost"
wire:model="remoteServerHost" /> <div x-data="{ showAdvanced: false }" class="flex flex-col gap-4">
<div x-data="{ showAdvanced: false }" class="flex flex-col gap-2">
<button @click="showAdvanced = !showAdvanced" type="button" <button @click="showAdvanced = !showAdvanced" type="button"
class="text-left text-sm text-gray-600 dark:text-gray-300 hover:underline"> class="flex items-center gap-2 text-left text-sm font-medium hover:underline">
Advanced Settings <svg x-show="!showAdvanced" class="size-4" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
clip-rule="evenodd" />
</svg>
<svg x-show="showAdvanced" class="size-4" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clip-rule="evenodd" />
</svg>
Advanced Connection Settings
</button> </button>
<div x-show="showAdvanced" class="flex flex-col gap-2"> <div x-show="showAdvanced" x-cloak class="grid grid-cols-1 lg:grid-cols-2 gap-4 p-4 rounded-lg border border-neutral-200 dark:border-coolgray-400">
<x-forms.input placeholder="Port number of your server. Default is 22." label="Port" <x-forms.input placeholder="Default: 22" label="SSH Port" type="number"
id="remoteServerPort" wire:model="remoteServerPort" /> id="remoteServerPort" wire:model="remoteServerPort" />
<div> <div>
<x-forms.input placeholder="Default is root." label="User" <x-forms.input placeholder="Default: root" label="SSH User"
id="remoteServerUser" wire:model="remoteServerUser" /> id="remoteServerUser" wire:model="remoteServerUser" />
<div class="text-xs text-gray-600 dark:text-gray-300">Non-root user is <p class="mt-1 text-xs dark:text-white text-black">
experimental: <a class="font-bold underline" target="_blank" Non-root user support is experimental.
href="https://coolify.io/docs/knowledge-base/server/non-root-user">docs</a>. <a class="font-bold underline hover:text-coollabs" target="_blank"
href="https://coolify.io/docs/knowledge-base/server/non-root-user">Learn more</a>
</p>
</div>
</div>
</div>
<x-forms.button type="submit" class="w-full lg:w-auto">Validate Connection</x-forms.button>
</form>
</x-slot:actions>
<x-slot:explanation>
<p>
<x-highlighted text="Connection Requirements:" /> Server must be accessible via SSH on the
specified port (default 22).
</p>
<p>
<x-highlighted text="Hostname Resolution:" /> Use IP addresses for direct connections or ensure
DNS resolution is configured.
</p>
<p>
<x-highlighted text="User Permissions:" /> Root or sudo-enabled users recommended for full Docker
management capabilities.
</p>
</x-slot:explanation>
</x-boarding-step>
@elseif ($currentState === 'validate-server')
<x-boarding-progress :currentStep="2" />
<x-boarding-step title="Server Validation">
<x-slot:question>
Coolify will automatically install Docker {{ $minDockerVersion }}+ if not present.
</x-slot:question>
<x-slot:actions>
<div class="w-full space-y-6">
<div class="p-6 bg-neutral-50 dark:bg-coolgray-200 rounded-lg border border-neutral-200 dark:border-coolgray-400">
<h3 class="font-bold text-black dark:text-white mb-4">Validation Steps</h3>
<div class="space-y-3">
<div class="flex items-start gap-3">
<div class="flex-shrink-0 mt-0.5">
<svg class="size-5 text-success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</div>
<div>
<div class="font-semibold text-base dark:text-white">Test SSH Connection</div>
<div class="text-sm dark:text-neutral-400">Verify key-based authentication</div>
</div>
</div>
<div class="flex items-start gap-3">
<div class="flex-shrink-0 mt-0.5">
<svg class="size-5 text-success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</div>
<div>
<div class="font-semibold text-base dark:text-white">Check OS Compatibility</div>
<div class="text-sm dark:text-neutral-400">Verify supported Linux distribution</div>
</div>
</div>
<div class="flex items-start gap-3">
<div class="flex-shrink-0 mt-0.5">
<svg class="size-5 text-success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</div>
<div>
<div class="font-semibold text-base dark:text-white">Install Docker Engine</div>
<div class="text-sm dark:text-neutral-400">Auto-install if version {{ $minDockerVersion }}+ not
found</div>
</div>
</div>
<div class="flex items-start gap-3">
<div class="flex-shrink-0 mt-0.5">
<svg class="size-5 text-success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</div>
<div>
<div class="font-semibold text-base dark:text-white">Configure Network</div>
<div class="text-sm dark:text-neutral-400">Set up Docker networks and proxy</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<x-forms.button type="submit">Continue</x-forms.button>
</form> <x-slide-over closeWithX fullScreen>
</x-slot:actions> <x-slot:title>Server Validation</x-slot:title>
</x-boarding-step> <x-slot:content>
@elseif ($currentState === 'validate-server') <livewire:server.validate-and-install :server="$this->createdServer" />
<x-boarding-step title="Validate & Configure Server"> </x-slot:content>
<x-slot:question> <x-forms.button @click="slideOverOpen=true" class="w-full font-bold py-4 box-boarding"
I need to validate your server (connection, Docker Engine, etc) and configure if something is wire:click.prevent='installServer' isHighlighted>
missing for me. Are you okay with this? Start Validation
</x-slot:question> </x-forms.button>
<x-slot:actions> </x-slide-over>
<x-slide-over closeWithX fullScreen> </div>
<x-slot:title>Validate & configure</x-slot:title>
<x-slot:content>
<livewire:server.validate-and-install :server="$this->createdServer" />
</x-slot:content>
<x-forms.button @click="slideOverOpen=true" class="w-full font-bold box-boarding lg:w-96"
wire:click.prevent='installServer' isHighlighted>
Let's do it!
</x-forms.button>
</x-slide-over>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>This will install the latest Docker Engine on your server, configure a few things to be able <p>
to run optimal.<br><br>Minimum Docker Engine version is: {{ $minDockerVersion }}<br><br>To <x-highlighted text="Automated Setup:" /> Coolify installs Docker Engine, Docker Compose, and
manually install configures system requirements automatically.
Docker </p>
Engine, check <a target="_blank" class="underline dark:text-warning" <p>
href="https://docs.docker.com/engine/install/#server">this <x-highlighted text="Version Requirements:" /> Minimum Docker Engine {{ $minDockerVersion }}.x
documentation</a>.</p> required.
<a target="_blank" class="underline hover:text-coollabs"
href="https://docs.docker.com/engine/install/#server">Manual installation guide</a>
</p>
<p>
<x-highlighted text="System Configuration:" /> Sets up Docker networks, proxy configuration, and
resource monitoring.
</p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@elseif ($currentState === 'create-project') @elseif ($currentState === 'create-project')
<x-boarding-step title="Project"> <x-boarding-progress :currentStep="3" />
<x-boarding-step title="Project Setup">
<x-slot:question> <x-slot:question>
@if (count($projects) > 0) @if ($projects && $projects->count() > 0)
You already have some projects. Do you want to use one of them or should I create a new one You have existing projects. Select one or create a new project to organize your resources.
for
you?
@else @else
Let's create an initial project for you. You can change all the details later on. Create your first project to organize applications, databases, and services.
@endif @endif
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center w-64 box-boarding" wire:click="createNewProject">Create new <div class="w-full space-y-4">
project!</x-forms.button> <x-forms.button class="justify-center w-full py-4 font-bold box-boarding"
<div> wire:click="createNewProject" isHighlighted>
@if (count($projects) > 0) Create "My First Project"
<form wire:submit='selectExistingProject' class="flex flex-col w-full gap-4 lg:w-96"> </x-forms.button>
<x-forms.select label="Existing projects" class="w-96" id='selectedProject'>
@if ($projects && $projects->count() > 0)
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-neutral-300 dark:border-coolgray-400"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-white dark:bg-coolgray-100 text-coolgray-400">Or use existing</span>
</div>
</div>
<form wire:submit='selectExistingProject' class="flex flex-col gap-4">
<x-forms.select label="Existing Projects" id='selectedProject'>
@foreach ($projects as $project) @foreach ($projects as $project)
<option wire:key="{{ $loop->index }}" value="{{ $project->id }}"> <option wire:key="{{ $loop->index }}" value="{{ $project->id }}">
{{ $project->name }}</option> {{ $project->name }}</option>
@endforeach @endforeach
</x-forms.select> </x-forms.select>
<x-forms.button type="submit">Use this Project</x-forms.button> <x-forms.button type="submit" class="w-full lg:w-auto">Use Selected Project</x-forms.button>
</form> </form>
@endif @endif
</div> </div>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>Projects contain several resources combined into one virtual group. There are no <p>
limitations on the number of projects you can add.</p> <x-highlighted text="Project Organization:" /> Group related resources (apps, databases, services)
<p>Each project should have at least one environment, this allows you to create a production & into logical projects.
staging version of the same application, but grouped separately.</p> </p>
<p>
<x-highlighted text="Environments:" /> Each project includes a production environment by default.
Add staging, development, or custom environments as needed.
</p>
<p>
<x-highlighted text="Team Access:" /> Projects inherit team permissions and can be managed
collaboratively.
</p>
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@elseif ($currentState === 'create-resource') @elseif ($currentState === 'create-resource')
<x-boarding-step title="Resources"> <x-boarding-progress :currentStep="3" />
<x-slot:question> <div class="w-full max-w-2xl text-center space-y-8">
Let's go to the new resource page, where you can create your first resource. <div class="space-y-4">
</x-slot:question> <div class="flex justify-center">
<x-slot:actions> <svg class="size-16 text-success" xmlns="http://www.w3.org/2000/svg" fill="none"
<div class="items-center justify-center w-64 box-boarding" wire:click="showNewResource">Let's do viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
it!</div> <path stroke-linecap="round" stroke-linejoin="round"
</x-slot:actions> d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
<x-slot:explanation> </svg>
<p>A resource could be an application, a database or a service (like WordPress).</p> </div>
</x-slot:explanation> <h1 class="text-4xl font-bold lg:text-5xl">Setup Complete!</h1>
</x-boarding-step> <p class="text-lg dark:text-neutral-400">
Your server is connected and ready. Start deploying your first resource.
</p>
</div>
<div
class="bg-white dark:bg-coolgray-100 rounded-lg shadow-sm border border-neutral-200 dark:border-coolgray-300 p-8 text-left">
<h2 class="text-sm font-bold uppercase tracking-wide dark:text-neutral-400 mb-4">
What's Configured
</h2>
<div class="space-y-3">
<div class="flex items-start gap-3">
<div class="flex-shrink-0 mt-0.5">
<svg class="size-5 text-success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</div>
<div>
<div class="font-semibold text-base dark:text-white">Server: {{ $createdServer->name }}</div>
<div class="text-sm dark:text-neutral-400">{{ $createdServer->ip }}</div>
</div>
</div>
<div class="flex items-start gap-3">
<div class="flex-shrink-0 mt-0.5">
<svg class="size-5 text-success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</div>
<div>
<div class="font-semibold text-base dark:text-white">Project: {{ $createdProject->name }}</div>
<div class="text-sm dark:text-neutral-400">Production environment ready</div>
</div>
</div>
<div class="flex items-start gap-3">
<div class="flex-shrink-0 mt-0.5">
<svg class="size-5 text-success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</div>
<div>
<div class="font-semibold text-base dark:text-white">Docker Engine</div>
<div class="text-sm dark:text-neutral-400">Installed and running</div>
</div>
</div>
</div>
</div>
<div class="flex flex-col gap-3">
<x-forms.button class="justify-center w-full py-4 text-lg font-bold box-boarding"
wire:click="showNewResource" isHighlighted>
Deploy Your First Resource
</x-forms.button>
<button wire:click="skipBoarding"
class="text-sm dark:text-neutral-400 hover:text-coollabs dark:hover:text-warning hover:underline transition-colors">
Go to Dashboard
</button>
</div>
</div>
@endif @endif
</div> </div>
<div class="flex flex-col justify-center gap-4 pt-4 lg:gap-2 lg:flex">
<div class="flex justify-center w-full gap-2"> @if ($currentState !== 'welcome' && $currentState !== 'create-resource')
<div class="cursor-pointer hover:underline dark:hover:text-white" wire:click='skipBoarding'>Skip <div class="flex flex-col items-center gap-4 pt-8 mt-8 border-t border-neutral-200 dark:border-coolgray-400">
onboarding</div> <div class="flex justify-center gap-6 text-sm">
<div class="cursor-pointer hover:underline dark:hover:text-white" wire:click='restartBoarding'>Restart <button wire:click='skipBoarding'
onboarding</div> class="dark:text-neutral-400 hover:text-coollabs dark:hover:text-warning hover:underline transition-colors">
Skip Setup
</button>
<button wire:click='restartBoarding'
class="dark:text-neutral-400 hover:text-coollabs dark:hover:text-warning hover:underline transition-colors">
Restart
</button>
</div>
<x-modal-input title="Need Help?">
<x-slot:content>
<button
class="text-sm dark:text-neutral-400 hover:text-coollabs dark:hover:text-warning hover:underline transition-colors">
Contact Support
</button>
</x-slot:content>
<livewire:help />
</x-modal-input>
</div> </div>
<x-modal-input title="How can we help?"> @endif
<x-slot:content>
<div class="w-full text-center cursor-pointer hover:underline dark:hover:text-white"
title="Send us feedback or get help!">
Feedback
</div>
</x-slot:content>
<livewire:help />
</x-modal-input>
</div>
</section> </section>