Wire Ladill domain picker and purchase modal into servers flows.
Deploy Ladill Servers / deploy (push) Successful in 27s
Deploy Ladill Servers / deploy (push) Successful in 27s
Use the Domains API for owned domains across panel, account, and order forms, with an embedded iframe purchase modal instead of redirecting to domains.ladill.com. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -48,6 +48,9 @@ BILLING_API_KEY_SERVERS=
|
|||||||
IDENTITY_API_URL=https://ladill.com/api
|
IDENTITY_API_URL=https://ladill.com/api
|
||||||
IDENTITY_API_KEY_SERVERS=
|
IDENTITY_API_KEY_SERVERS=
|
||||||
|
|
||||||
|
DOMAINS_API_URL=https://domains.ladill.com/api
|
||||||
|
DOMAINS_API_KEY_SERVERS=
|
||||||
|
|
||||||
DOMAIN_API_URL=https://ladill.com/api/domains
|
DOMAIN_API_URL=https://ladill.com/api/domains
|
||||||
DOMAIN_API_KEY_SERVERS=
|
DOMAIN_API_KEY_SERVERS=
|
||||||
|
|
||||||
|
|||||||
@@ -1116,6 +1116,18 @@ class HostingPanelController extends Controller
|
|||||||
$onboardingMode = $isOwnedDomain
|
$onboardingMode = $isOwnedDomain
|
||||||
? Domain::MODE_NS_AUTO
|
? Domain::MODE_NS_AUTO
|
||||||
: ($validated['onboarding_mode'] ?? Domain::MODE_NS_AUTO);
|
: ($validated['onboarding_mode'] ?? Domain::MODE_NS_AUTO);
|
||||||
|
|
||||||
|
if ($isOwnedDomain) {
|
||||||
|
$owned = $this->availableHostingDomainsForUser($request->user()->id, $account);
|
||||||
|
if (! $owned->contains($domainHost)) {
|
||||||
|
if ($request->wantsJson()) {
|
||||||
|
return response()->json(['message' => 'That domain is not in your Ladill account.'], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
return back()->with('error', 'That domain is not in your Ladill account.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$serverIp = $account->node?->ip_address ?? '161.97.138.149';
|
$serverIp = $account->node?->ip_address ?? '161.97.138.149';
|
||||||
$docRoot = ! empty($validated['document_root'])
|
$docRoot = ! empty($validated['document_root'])
|
||||||
? "/home/{$account->username}/" . ltrim($validated['document_root'], '/')
|
? "/home/{$account->username}/" . ltrim($validated['document_root'], '/')
|
||||||
@@ -1297,35 +1309,13 @@ class HostingPanelController extends Controller
|
|||||||
->filter()
|
->filter()
|
||||||
->all();
|
->all();
|
||||||
|
|
||||||
$domainRegistryHosts = Domain::where('user_id', $userId)->pluck('host');
|
$user = \App\Models\User::query()->find($userId);
|
||||||
|
$owned = $user
|
||||||
|
? app(\App\Services\Domains\LadillDomainsClient::class)->ownedForUser((string) $user->public_id)
|
||||||
|
: [];
|
||||||
|
|
||||||
$registeredDomains = RcServiceOrder::where('user_id', $userId)
|
return collect($owned)
|
||||||
->whereIn('order_type', [RcServiceOrder::TYPE_DOMAIN_REGISTRATION, RcServiceOrder::TYPE_DOMAIN_TRANSFER])
|
|
||||||
->where('status', RcServiceOrder::STATUS_ACTIVE)
|
|
||||||
->whereNotNull('domain_name')
|
|
||||||
->pluck('domain_name');
|
|
||||||
|
|
||||||
$customerHostingDomains = CustomerHostingOrder::where('user_id', $userId)
|
|
||||||
->whereNotIn('status', [
|
|
||||||
CustomerHostingOrder::STATUS_PENDING_PAYMENT,
|
|
||||||
CustomerHostingOrder::STATUS_CANCELLED,
|
|
||||||
CustomerHostingOrder::STATUS_FAILED,
|
|
||||||
])
|
|
||||||
->whereNotNull('domain_name')
|
|
||||||
->pluck('domain_name');
|
|
||||||
|
|
||||||
$hostingAccountDomains = HostingAccount::where('user_id', $userId)
|
|
||||||
->whereNotNull('primary_domain')
|
|
||||||
->pluck('primary_domain');
|
|
||||||
|
|
||||||
return $domainRegistryHosts
|
|
||||||
->merge($registeredDomains)
|
|
||||||
->merge($customerHostingDomains)
|
|
||||||
->merge($hostingAccountDomains)
|
|
||||||
->map(fn ($d) => strtolower(trim((string) $d)))
|
|
||||||
->filter(fn ($d) => (bool) preg_match('/^(?=.{1,253}$)(?!-)(?:[a-z0-9-]{1,63}\.)+[a-z]{2,63}$/', $d))
|
|
||||||
->reject(fn ($d) => in_array($d, $linkedDomains, true))
|
->reject(fn ($d) => in_array($d, $linkedDomains, true))
|
||||||
->unique()
|
|
||||||
->sort()
|
->sort()
|
||||||
->values();
|
->values();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -632,17 +632,9 @@ class HostingProductController extends Controller
|
|||||||
return $product->catalogSummary();
|
return $product->catalogSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getUserDomains($user): \Illuminate\Support\Collection
|
private function getUserDomains($user): array
|
||||||
{
|
{
|
||||||
return \App\Models\Domain::query()
|
return app(\App\Services\Domains\LadillDomainsClient::class)
|
||||||
->where('user_id', $user->id)
|
->ownedForUser((string) $user->public_id);
|
||||||
->whereNull('hosting_account_id')
|
|
||||||
->whereDoesntHave('hostedSites')
|
|
||||||
->where(function ($query) {
|
|
||||||
$query->where('source', 'purchased')
|
|
||||||
->orWhereNotNull('email_domain_id');
|
|
||||||
})
|
|
||||||
->orderBy('host')
|
|
||||||
->get(['id', 'host', 'status', 'onboarding_state']);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Services\Domains\LadillDomainsClient;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class OwnedDomainsController extends Controller
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, LadillDomainsClient $domains): JsonResponse
|
||||||
|
{
|
||||||
|
$exclude = collect($request->query('exclude', []))
|
||||||
|
->map(fn ($d) => strtolower(trim((string) $d)))
|
||||||
|
->filter()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
$owned = collect($domains->ownedForUser((string) ladill_account()->public_id))
|
||||||
|
->reject(fn ($d) => in_array($d, $exclude, true))
|
||||||
|
->sort()
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
return response()->json(['data' => $owned]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Domains;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class LadillDomainsClient
|
||||||
|
{
|
||||||
|
/** @return list<string> */
|
||||||
|
public function ownedForUser(string $publicId): array
|
||||||
|
{
|
||||||
|
$key = (string) (config('domains.api_key') ?? '');
|
||||||
|
if ($key === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$res = Http::withToken($key)
|
||||||
|
->acceptJson()
|
||||||
|
->timeout(10)
|
||||||
|
->get(rtrim((string) config('domains.api_url'), '/').'/owned-domains', [
|
||||||
|
'user' => $publicId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$res->throw();
|
||||||
|
|
||||||
|
return collect($res->json('data', []))
|
||||||
|
->map(fn ($d) => strtolower(trim((string) $d)))
|
||||||
|
->filter()
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('LadillDomainsClient: could not load owned domains', [
|
||||||
|
'user' => $publicId,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,17 @@ if (! function_exists('ladill_domains_url')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! function_exists('ladill_domains_embed_url')) {
|
||||||
|
function ladill_domains_embed_url(string $path = '/embed/find'): string
|
||||||
|
{
|
||||||
|
$url = ladill_domains_url($path);
|
||||||
|
$origin = rtrim((string) config('app.url'), '/');
|
||||||
|
$separator = str_contains($url, '?') ? '&' : '?';
|
||||||
|
|
||||||
|
return $url.$separator.'parent_origin='.urlencode($origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (! function_exists('ladill_account_url')) {
|
if (! function_exists('ladill_account_url')) {
|
||||||
function ladill_account_url(string $path = ''): string
|
function ladill_account_url(string $path = ''): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'api_url' => env('DOMAINS_API_URL', 'https://domains.ladill.com/api'),
|
||||||
|
'api_key' => env('DOMAINS_API_KEY_SERVERS'),
|
||||||
|
];
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
import './bootstrap';
|
import './bootstrap';
|
||||||
|
|
||||||
import { registerLadillConfirmStore, registerLadillModalHelpers } from './ladill-modals';
|
import { registerLadillConfirmStore, registerLadillModalHelpers } from './ladill-modals';
|
||||||
|
import { registerLadillSearchShortcut } from './ladill-search-shortcut';
|
||||||
|
import { registerLadillDomainPurchase } from './ladill-domain-purchase';
|
||||||
|
|
||||||
registerLadillModalHelpers();
|
registerLadillModalHelpers();
|
||||||
|
registerLadillSearchShortcut();
|
||||||
|
registerLadillDomainPurchase();
|
||||||
|
|
||||||
|
|
||||||
import Alpine from 'alpinejs';
|
import Alpine from 'alpinejs';
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { closeLadillModal, openLadillModal } from './ladill-modals';
|
||||||
|
|
||||||
|
const PURCHASE_MODAL = 'domain-purchase';
|
||||||
|
|
||||||
|
function allowedParentOrigin(origin) {
|
||||||
|
try {
|
||||||
|
const host = new URL(origin).hostname;
|
||||||
|
|
||||||
|
return host === 'ladill.com' || host.endsWith('.ladill.com');
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPurchaseFrame() {
|
||||||
|
const frame = document.getElementById('ladill-domain-purchase-frame');
|
||||||
|
if (!frame || frame.dataset.loaded === '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const embedUrl = frame.dataset.embedUrl;
|
||||||
|
if (embedUrl) {
|
||||||
|
frame.src = embedUrl;
|
||||||
|
frame.dataset.loaded = '1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerLadillDomainPurchase() {
|
||||||
|
window.ladillDomainSourceForm = (initial = {}) => ({
|
||||||
|
domainSource: initial.domainSource ?? 'external',
|
||||||
|
selectedOwnedDomain: initial.selectedOwnedDomain ?? '',
|
||||||
|
externalDomain: initial.externalDomain ?? '',
|
||||||
|
ownedDomains: initial.ownedDomains ?? [],
|
||||||
|
ownedUrl: initial.ownedUrl ?? '',
|
||||||
|
onboardingMode: initial.onboardingMode ?? 'ns_auto',
|
||||||
|
|
||||||
|
openPurchaseModal() {
|
||||||
|
loadPurchaseFrame();
|
||||||
|
openLadillModal(PURCHASE_MODAL);
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshOwnedDomains() {
|
||||||
|
if (!this.ownedUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(this.ownedUrl, {
|
||||||
|
headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = await res.json();
|
||||||
|
this.ownedDomains = Array.isArray(payload.data) ? payload.data : [];
|
||||||
|
this.domainSource = 'ladill';
|
||||||
|
} catch {
|
||||||
|
// Non-fatal.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('message', (event) => {
|
||||||
|
if (!allowedParentOrigin(event.origin)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = event.data;
|
||||||
|
if (!data || data.type !== 'ladill:domains:purchase-complete') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeLadillModal(PURCHASE_MODAL);
|
||||||
|
|
||||||
|
const domains = Array.isArray(data.domains) ? data.domains : (data.domain ? [data.domain] : []);
|
||||||
|
document.querySelectorAll('[x-data*="ladillDomainSourceForm"]').forEach((el) => {
|
||||||
|
const component = el._x_dataStack?.[0];
|
||||||
|
if (!component?.refreshOwnedDomains) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.refreshOwnedDomains().then(() => {
|
||||||
|
const newest = domains.map((d) => String(d).toLowerCase()).find(Boolean);
|
||||||
|
if (newest) {
|
||||||
|
component.selectedOwnedDomain = newest;
|
||||||
|
component.domainSource = 'ladill';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('open-modal', (event) => {
|
||||||
|
if (String(event.detail) === PURCHASE_MODAL) {
|
||||||
|
loadPurchaseFrame();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
function isEditableTarget(element) {
|
||||||
|
if (!(element instanceof HTMLElement)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tag = element.tagName;
|
||||||
|
|
||||||
|
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return element.isContentEditable;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVisibleInput(input) {
|
||||||
|
if (!(input instanceof HTMLElement)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = window.getComputedStyle(input);
|
||||||
|
|
||||||
|
return style.display !== 'none'
|
||||||
|
&& style.visibility !== 'hidden'
|
||||||
|
&& style.opacity !== '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSearchInput() {
|
||||||
|
const marked = [...document.querySelectorAll('[data-ladill-search-input]')];
|
||||||
|
const visibleMarked = marked.find(isVisibleInput);
|
||||||
|
|
||||||
|
if (visibleMarked) {
|
||||||
|
return visibleMarked;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (marked.length > 0) {
|
||||||
|
return marked[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return document.querySelector('[x-data*="topbarSearch"] input');
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusSearchInput() {
|
||||||
|
const input = findSearchInput();
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
const fallbackUrl = document.body?.dataset?.ladillSearchUrl;
|
||||||
|
|
||||||
|
if (fallbackUrl) {
|
||||||
|
window.location.href = fallbackUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isVisibleInput(input)) {
|
||||||
|
const fallbackUrl = document.body?.dataset?.ladillSearchUrl;
|
||||||
|
|
||||||
|
if (fallbackUrl) {
|
||||||
|
window.location.href = fallbackUrl;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
|
||||||
|
if (input instanceof HTMLInputElement && input.type === 'text') {
|
||||||
|
input.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerLadillSearchShortcut() {
|
||||||
|
if (window.__ladillSearchShortcutRegistered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__ladillSearchShortcutRegistered = true;
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key !== '/' && event.code !== 'Slash') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.ctrlKey || event.metaKey || event.altKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEditableTarget(document.activeElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
focusSearchInput();
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
@props(['appLabel' => 'Ladill Servers'])
|
||||||
|
|
||||||
|
<x-modal name="domain-purchase" maxWidth="2xl">
|
||||||
|
<div class="flex flex-col" style="height: min(80vh, 720px);">
|
||||||
|
<div class="flex shrink-0 items-center justify-between border-b border-slate-100 px-5 py-4">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-base font-semibold text-slate-900">Register a domain</h2>
|
||||||
|
<p class="mt-0.5 text-xs text-slate-500">Search and buy without leaving {{ $appLabel }}.</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" data-close-modal="domain-purchase"
|
||||||
|
class="rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-600">
|
||||||
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<iframe id="ladill-domain-purchase-frame" title="Ladill Domains"
|
||||||
|
src="about:blank"
|
||||||
|
data-embed-url="{{ ladill_domains_embed_url() }}"
|
||||||
|
class="min-h-0 w-full flex-1 border-0 bg-white"></iframe>
|
||||||
|
</div>
|
||||||
|
</x-modal>
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
@props([
|
||||||
|
'ownedDomains' => [],
|
||||||
|
'fieldName' => 'domain',
|
||||||
|
'ownedUrl' => '',
|
||||||
|
'variant' => 'slate',
|
||||||
|
'showConnectionMethod' => false,
|
||||||
|
'showDocumentRoot' => false,
|
||||||
|
])
|
||||||
|
|
||||||
|
@php
|
||||||
|
$ownedDomainHosts = collect($ownedDomains)->map(fn ($d) => (string) $d)->all();
|
||||||
|
$oldValue = trim((string) old($fieldName, ''));
|
||||||
|
$initialDomainSource = empty($ownedDomainHosts)
|
||||||
|
? 'external'
|
||||||
|
: ((string) old('is_owned_domain', '1') === '0' ? 'external' : 'ladill');
|
||||||
|
$selectedOwnedDomain = in_array($oldValue, $ownedDomainHosts, true) ? $oldValue : '';
|
||||||
|
$externalFallbackDomain = ! in_array($oldValue, $ownedDomainHosts, true) ? $oldValue : '';
|
||||||
|
$isGray = $variant === 'gray';
|
||||||
|
$inputClass = $isGray
|
||||||
|
? 'block w-full rounded-lg border-gray-200 bg-white text-sm focus:border-gray-400 focus:ring-0'
|
||||||
|
: 'block w-full rounded-xl border-slate-200 bg-slate-50 text-sm shadow-sm focus:border-indigo-500 focus:bg-white focus:ring-indigo-500';
|
||||||
|
$toggleWrapClass = $isGray ? 'bg-gray-100' : 'bg-slate-100';
|
||||||
|
$purchaseLinkClass = $isGray
|
||||||
|
? 'font-medium text-gray-700 underline underline-offset-2 hover:text-gray-900'
|
||||||
|
: 'font-medium text-indigo-600 underline underline-offset-2 hover:text-indigo-800';
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="mb-2 flex items-center justify-between">
|
||||||
|
<label class="text-sm font-medium {{ $isGray ? 'text-gray-700' : 'text-slate-700' }}">Domain</label>
|
||||||
|
<div class="inline-flex items-center rounded-md {{ $toggleWrapClass }} p-0.5 text-[11px] font-medium">
|
||||||
|
<button type="button" @click="domainSource = 'ladill'"
|
||||||
|
:class="domainSource === 'ladill' ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500'"
|
||||||
|
class="rounded px-2 py-1 transition">Ladill</button>
|
||||||
|
<button type="button" @click="domainSource = 'external'"
|
||||||
|
:class="domainSource === 'external' ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500'"
|
||||||
|
class="rounded px-2 py-1 transition">External</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="domainSource === 'ladill'" x-cloak>
|
||||||
|
<template x-if="ownedDomains.length">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<select name="{{ $fieldName }}" x-model="selectedOwnedDomain" :disabled="domainSource !== 'ladill'" class="{{ $inputClass }}">
|
||||||
|
<option value="">Choose a domain...</option>
|
||||||
|
<template x-for="domain in ownedDomains" :key="domain">
|
||||||
|
<option :value="domain" x-text="domain"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
<p class="text-xs {{ $isGray ? 'text-gray-500' : 'text-slate-500' }}">
|
||||||
|
Prefer another?
|
||||||
|
<button type="button" @click="openPurchaseModal()" class="{{ $purchaseLinkClass }}">Get a new domain</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template x-if="!ownedDomains.length">
|
||||||
|
<div @class([
|
||||||
|
'rounded-xl px-4 py-3 text-center text-sm',
|
||||||
|
'bg-gray-50 text-gray-600' => $isGray,
|
||||||
|
'bg-slate-50 text-slate-600' => ! $isGray,
|
||||||
|
])>
|
||||||
|
No Ladill domains yet.
|
||||||
|
<button type="button" @click="openPurchaseModal()" class="{{ $purchaseLinkClass }}">Register a domain</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="domainSource === 'external'" x-cloak>
|
||||||
|
<input type="text" name="{{ $fieldName }}" x-model="externalDomain" :disabled="domainSource !== 'external'"
|
||||||
|
placeholder="example.com" class="{{ $inputClass }} @error($fieldName) border-rose-300 @enderror">
|
||||||
|
<p class="mt-1.5 text-xs {{ $isGray ? 'text-gray-500' : 'text-slate-500' }}">Enter your domain without www or http (e.g., example.com)</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@error($fieldName)
|
||||||
|
<p class="mt-1.5 text-sm text-rose-600">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="is_owned_domain" :value="domainSource === 'ladill' ? '1' : '0'">
|
||||||
|
|
||||||
|
@if($showConnectionMethod)
|
||||||
|
<div x-show="domainSource === 'external'" x-cloak class="mt-5">
|
||||||
|
<label class="mb-3 block text-sm font-medium {{ $isGray ? 'text-gray-700' : 'text-slate-700' }}">Connection method</label>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<label @class([
|
||||||
|
'relative flex cursor-pointer rounded-lg border bg-white p-4 transition hover:bg-slate-50',
|
||||||
|
'border-slate-200' => ! $isGray,
|
||||||
|
'border-gray-200 hover:bg-gray-50' => $isGray,
|
||||||
|
])>
|
||||||
|
<input type="radio" name="onboarding_mode" value="ns_auto" x-model="onboardingMode" class="sr-only peer">
|
||||||
|
<div class="flex flex-1 items-start gap-3">
|
||||||
|
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-emerald-100 text-emerald-600">
|
||||||
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.7L5.737 5.1a3.375 3.375 0 0 1 2.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 0 1 .9 2.7m0 0a3 3 0 0 1-3 3m0 3h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Zm-3 6h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Z"/></svg>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-sm font-semibold text-slate-900">Point nameservers</p>
|
||||||
|
<p class="mt-0.5 text-xs text-slate-500">Point your domain's nameservers to Ladill. We'll manage DNS and SSL automatically.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute right-4 top-4 hidden h-5 w-5 items-center justify-center rounded-full bg-indigo-600 text-white peer-checked:flex">
|
||||||
|
<svg class="h-3 w-3" fill="currentColor" viewBox="0 0 20 20"><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>
|
||||||
|
</div>
|
||||||
|
<div class="pointer-events-none absolute inset-0 rounded-lg border-2 border-transparent peer-checked:border-indigo-600"></div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label @class([
|
||||||
|
'relative flex cursor-pointer rounded-lg border bg-white p-4 transition hover:bg-slate-50',
|
||||||
|
'border-slate-200' => ! $isGray,
|
||||||
|
'border-gray-200 hover:bg-gray-50' => $isGray,
|
||||||
|
])>
|
||||||
|
<input type="radio" name="onboarding_mode" value="manual_dns" x-model="onboardingMode" class="sr-only peer">
|
||||||
|
<div class="flex flex-1 items-start gap-3">
|
||||||
|
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-blue-100 text-blue-600">
|
||||||
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-sm font-semibold text-slate-900">Add DNS records</p>
|
||||||
|
<p class="mt-0.5 text-xs text-slate-500">Keep your current DNS provider and manually add A records pointing to our server.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute right-4 top-4 hidden h-5 w-5 items-center justify-center rounded-full bg-indigo-600 text-white peer-checked:flex">
|
||||||
|
<svg class="h-3 w-3" fill="currentColor" viewBox="0 0 20 20"><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>
|
||||||
|
</div>
|
||||||
|
<div class="pointer-events-none absolute inset-0 rounded-lg border-2 border-transparent peer-checked:border-indigo-600"></div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
@error('onboarding_mode')
|
||||||
|
<p class="mt-1.5 text-sm text-rose-600">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($showDocumentRoot)
|
||||||
|
<div class="mt-5">
|
||||||
|
<label class="mb-1 block text-sm font-medium text-slate-700">Document root <span class="font-normal text-slate-400">(optional)</span></label>
|
||||||
|
<input type="text" name="document_root" placeholder="public_html/example.com" value="{{ old('document_root') }}"
|
||||||
|
class="w-full rounded-lg border-slate-200 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
|
<p class="mt-1 text-xs text-slate-500">Leave empty to use default: public_html/domain.com</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
@@ -36,9 +36,9 @@
|
|||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@php
|
@php
|
||||||
$ownedDomainHosts = $ownedDomains->pluck('host')->map(fn ($d) => (string) $d)->all();
|
$ownedDomainHosts = collect($ownedDomains)->map(fn ($d) => (string) $d)->all();
|
||||||
$oldDomain = trim((string) old('domain', ''));
|
$oldDomain = trim((string) old('domain', ''));
|
||||||
$initialDomainSource = $ownedDomains->isEmpty()
|
$initialDomainSource = empty($ownedDomainHosts)
|
||||||
? 'external'
|
? 'external'
|
||||||
: ((string) old('is_owned_domain', '1') === '0' ? 'external' : 'ladill');
|
: ((string) old('is_owned_domain', '1') === '0' ? 'external' : 'ladill');
|
||||||
$selectedOwned = in_array($oldDomain, $ownedDomainHosts, true) ? $oldDomain : '';
|
$selectedOwned = in_array($oldDomain, $ownedDomainHosts, true) ? $oldDomain : '';
|
||||||
@@ -54,10 +54,6 @@
|
|||||||
showSheet: false,
|
showSheet: false,
|
||||||
checkoutUrl: '',
|
checkoutUrl: '',
|
||||||
renewLoading: false,
|
renewLoading: false,
|
||||||
domainSource: @js($initialDomainSource),
|
|
||||||
selectedOwnedDomain: @js($selectedOwned),
|
|
||||||
externalDomain: @js($externalFallback),
|
|
||||||
onboardingMode: '{{ old('onboarding_mode', 'ns_auto') }}',
|
|
||||||
async submitRenewal() {
|
async submitRenewal() {
|
||||||
this.renewLoading = true;
|
this.renewLoading = true;
|
||||||
try {
|
try {
|
||||||
@@ -472,9 +468,16 @@
|
|||||||
x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
|
x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
|
||||||
class="relative min-h-full sm:min-h-0 w-full sm:max-w-lg sm:rounded-2xl sm:border sm:border-slate-200 bg-white shadow-xl" @click.stop>
|
class="relative min-h-full sm:min-h-0 w-full sm:max-w-lg sm:rounded-2xl sm:border sm:border-slate-200 bg-white shadow-xl" @click.stop>
|
||||||
|
|
||||||
<form method="POST" action="{{ $addDomainAction }}">
|
<form method="POST" action="{{ $addDomainAction }}"
|
||||||
|
x-data="ladillDomainSourceForm({
|
||||||
|
domainSource: @js($initialDomainSource),
|
||||||
|
selectedOwnedDomain: @js($selectedOwned),
|
||||||
|
externalDomain: @js($externalFallback),
|
||||||
|
ownedDomains: @js($ownedDomainHosts),
|
||||||
|
ownedUrl: @js(route('servers.domains.owned', ['exclude' => $linkedSites->pluck('domain')->filter()->all()])),
|
||||||
|
onboardingMode: @js(old('onboarding_mode', 'ns_auto')),
|
||||||
|
})">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="is_owned_domain" :value="domainSource === 'ladill' ? '1' : '0'">
|
|
||||||
|
|
||||||
<div class="flex items-center justify-between border-b border-slate-100 px-6 py-4">
|
<div class="flex items-center justify-between border-b border-slate-100 px-6 py-4">
|
||||||
<h3 class="text-base font-semibold text-slate-900">Connect a Domain</h3>
|
<h3 class="text-base font-semibold text-slate-900">Connect a Domain</h3>
|
||||||
@@ -483,83 +486,15 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6 space-y-5">
|
<div class="p-6 space-y-5">
|
||||||
{{-- Domain Source Toggle --}}
|
<x-domain-source-fields
|
||||||
<div>
|
:owned-domains="$ownedDomainHosts"
|
||||||
<div class="mb-2 flex items-center justify-between">
|
owned-url="{{ route('servers.domains.owned') }}"
|
||||||
<label class="text-sm font-medium text-slate-700">Domain</label>
|
show-connection-method
|
||||||
<div class="inline-flex items-center rounded-md bg-slate-100 p-0.5 text-[11px] font-medium">
|
/>
|
||||||
<button type="button" @click="domainSource = 'ladill'"
|
|
||||||
:class="domainSource === 'ladill' ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500'"
|
|
||||||
class="rounded px-2 py-1 transition">Ladill</button>
|
|
||||||
<button type="button" @click="domainSource = 'external'"
|
|
||||||
:class="domainSource === 'external' ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500'"
|
|
||||||
class="rounded px-2 py-1 transition">External</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Ladill (Owned) Domains --}}
|
|
||||||
<div x-show="domainSource === 'ladill'" x-cloak>
|
|
||||||
@if($ownedDomains->isNotEmpty())
|
|
||||||
<select name="domain" x-model="selectedOwnedDomain" :disabled="domainSource !== 'ladill'"
|
|
||||||
class="block w-full rounded-xl border-slate-200 bg-slate-50 text-sm focus:border-indigo-500 focus:bg-white focus:ring-indigo-500">
|
|
||||||
<option value="">Choose a domain...</option>
|
|
||||||
@foreach($ownedDomains as $domain)
|
|
||||||
<option value="{{ $domain->host }}">{{ $domain->host }}</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
<p class="mt-1.5 text-xs text-slate-400">
|
|
||||||
Don't have a domain?
|
|
||||||
<a href="{{ ladill_domains_url('/find') }}" class="font-medium text-indigo-600 hover:text-indigo-800">Purchase one first</a>
|
|
||||||
</p>
|
|
||||||
@else
|
|
||||||
<div class="rounded-lg bg-slate-50 px-4 py-3 text-center text-sm text-slate-600">
|
|
||||||
No domains found in your account.
|
|
||||||
<a href="{{ ladill_domains_url('/find') }}" class="font-medium text-indigo-600 underline underline-offset-2 hover:text-indigo-800">Register a domain</a>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- External Domain --}}
|
|
||||||
<div x-show="domainSource === 'external'" x-cloak>
|
|
||||||
<input name="domain" type="text" x-model="externalDomain" :disabled="domainSource !== 'external'" placeholder="example.com"
|
|
||||||
class="w-full rounded-xl border-slate-200 bg-slate-50 px-4 py-2.5 text-sm focus:border-indigo-500 focus:bg-white focus:ring-indigo-500">
|
|
||||||
<p class="mt-1.5 text-xs text-slate-400">Enter your domain without www or http (e.g., example.com)</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if ($formErrors->has('domain'))
|
|
||||||
<p class="mt-2 text-xs text-rose-600">{{ $formErrors->first('domain') }}</p>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Connection Method (External only) --}}
|
|
||||||
<div x-show="domainSource === 'external'" x-cloak>
|
|
||||||
<label class="mb-2 block text-sm font-medium text-slate-700">Connection method</label>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<label class="flex cursor-pointer items-start gap-3 rounded-xl border p-3.5 transition"
|
|
||||||
:class="onboardingMode === 'ns_auto' ? 'border-indigo-300 bg-indigo-50/50' : 'border-slate-200 hover:border-slate-300'">
|
|
||||||
<input type="radio" x-model="onboardingMode" name="onboarding_mode" value="ns_auto" class="mt-0.5 text-indigo-600 focus:ring-indigo-500">
|
|
||||||
<div>
|
|
||||||
<p class="text-sm font-medium text-slate-900">Point Nameservers</p>
|
|
||||||
<p class="mt-0.5 text-xs text-slate-500">Point your domain's nameservers to Ladill. We'll manage DNS and SSL automatically.</p>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<label class="flex cursor-pointer items-start gap-3 rounded-xl border p-3.5 transition"
|
|
||||||
:class="onboardingMode === 'manual_dns' ? 'border-indigo-300 bg-indigo-50/50' : 'border-slate-200 hover:border-slate-300'">
|
|
||||||
<input type="radio" x-model="onboardingMode" name="onboarding_mode" value="manual_dns" class="mt-0.5 text-indigo-600 focus:ring-indigo-500">
|
|
||||||
<div>
|
|
||||||
<p class="text-sm font-medium text-slate-900">Add DNS Records</p>
|
|
||||||
<p class="mt-0.5 text-xs text-slate-500">Keep your current DNS provider and manually add A records pointing to our server.</p>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-end gap-3 pt-1">
|
<div class="flex items-center justify-end gap-3 pt-1">
|
||||||
<button @click="domainModal = false" type="button" class="rounded-xl border border-slate-200 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition">Cancel</button>
|
<button @click="domainModal = false" type="button" class="rounded-xl border border-slate-200 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition">Cancel</button>
|
||||||
<button type="submit"
|
<button type="submit" class="btn-primary">Add Domain</button>
|
||||||
class="btn-primary">
|
|
||||||
Add Domain
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
@php
|
@php
|
||||||
$ownedDomainHosts = $ownedDomains->map(fn ($d) => (string) $d)->all();
|
$ownedDomainHosts = collect($ownedDomains)->map(fn ($d) => (string) $d)->all();
|
||||||
$oldDomain = trim((string) old('domain', ''));
|
$oldDomain = trim((string) old('domain', ''));
|
||||||
$initialDomainSource = $ownedDomains->isEmpty()
|
$initialDomainSource = empty($ownedDomainHosts)
|
||||||
? 'external'
|
? 'external'
|
||||||
: ((string) old('is_owned_domain', '1') === '0' ? 'external' : 'ladill');
|
: ((string) old('is_owned_domain', '1') === '0' ? 'external' : 'ladill');
|
||||||
$selectedOwnedDomain = in_array($oldDomain, $ownedDomainHosts, true) ? $oldDomain : '';
|
$selectedOwnedDomain = in_array($oldDomain, $ownedDomainHosts, true) ? $oldDomain : '';
|
||||||
@@ -28,113 +28,21 @@
|
|||||||
<div class="rounded-xl border border-slate-200 bg-white p-6">
|
<div class="rounded-xl border border-slate-200 bg-white p-6">
|
||||||
<h3 class="text-base font-semibold text-slate-900 mb-4">Add Domain</h3>
|
<h3 class="text-base font-semibold text-slate-900 mb-4">Add Domain</h3>
|
||||||
<form action="{{ route('hosting.panel.domains.add', $account) }}" method="POST" class="space-y-5"
|
<form action="{{ route('hosting.panel.domains.add', $account) }}" method="POST" class="space-y-5"
|
||||||
x-data="{
|
x-data="ladillDomainSourceForm({
|
||||||
domainSource: @js($initialDomainSource),
|
domainSource: @js($initialDomainSource),
|
||||||
selectedOwnedDomain: @js($selectedOwnedDomain),
|
selectedOwnedDomain: @js($selectedOwnedDomain),
|
||||||
externalDomain: @js($externalFallbackDomain),
|
externalDomain: @js($externalFallbackDomain),
|
||||||
onboardingMode: '{{ old('onboarding_mode', 'ns_auto') }}'
|
ownedDomains: @js($ownedDomainHosts),
|
||||||
}">
|
ownedUrl: @js(route('servers.domains.owned', ['exclude' => $account->sites->pluck('domain')->filter()->all()])),
|
||||||
|
onboardingMode: @js(old('onboarding_mode', 'ns_auto')),
|
||||||
|
})">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="is_owned_domain" :value="domainSource === 'ladill' ? '1' : '0'">
|
<x-domain-source-fields
|
||||||
|
:owned-domains="$ownedDomainHosts"
|
||||||
{{-- Domain Source Toggle --}}
|
owned-url="{{ route('servers.domains.owned') }}"
|
||||||
<div>
|
show-connection-method
|
||||||
<div class="mb-2 flex items-center justify-between">
|
show-document-root
|
||||||
<label class="text-sm font-medium text-slate-700">Domain</label>
|
/>
|
||||||
<div class="inline-flex items-center rounded-md bg-slate-100 p-0.5 text-[11px] font-medium">
|
|
||||||
<button type="button" @click="domainSource = 'ladill'"
|
|
||||||
:class="domainSource === 'ladill' ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500'"
|
|
||||||
class="rounded px-2 py-1 transition">Ladill</button>
|
|
||||||
<button type="button" @click="domainSource = 'external'"
|
|
||||||
:class="domainSource === 'external' ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500'"
|
|
||||||
class="rounded px-2 py-1 transition">External</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Ladill (Owned) Domains --}}
|
|
||||||
<div x-show="domainSource === 'ladill'" x-cloak>
|
|
||||||
@if($ownedDomains->isNotEmpty())
|
|
||||||
<select name="domain" x-model="selectedOwnedDomain" :disabled="domainSource !== 'ladill'"
|
|
||||||
class="block w-full rounded-lg border-slate-200 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
||||||
<option value="">Choose a domain...</option>
|
|
||||||
@foreach($ownedDomains as $domain)
|
|
||||||
<option value="{{ $domain }}">{{ $domain }}</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
<p class="mt-1.5 text-xs text-slate-500">
|
|
||||||
Don't have a domain?
|
|
||||||
<a href="{{ ladill_domains_url('/find') }}" class="font-medium text-indigo-600 hover:text-indigo-800">Purchase one first</a>
|
|
||||||
</p>
|
|
||||||
@else
|
|
||||||
<div class="rounded-lg bg-slate-50 px-4 py-3 text-center text-sm text-slate-600">
|
|
||||||
No domains found in your account.
|
|
||||||
<a href="{{ ladill_domains_url('/find') }}" class="font-medium text-indigo-600 underline underline-offset-2 hover:text-indigo-800">Register a domain</a>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- External Domain --}}
|
|
||||||
<div x-show="domainSource === 'external'" x-cloak>
|
|
||||||
<input type="text" name="domain" x-model="externalDomain" :disabled="domainSource !== 'external'" required placeholder="example.com"
|
|
||||||
class="block w-full rounded-lg border-slate-200 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('domain') border-red-300 @enderror">
|
|
||||||
<p class="mt-1.5 text-xs text-slate-500">Enter your domain without www or http (e.g., example.com)</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@error('domain')
|
|
||||||
<p class="mt-1.5 text-sm text-red-600">{{ $message }}</p>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Onboarding Mode (External only) --}}
|
|
||||||
<div x-show="domainSource === 'external'" x-cloak>
|
|
||||||
<label class="block text-sm font-medium text-slate-700 mb-3">Connection Method</label>
|
|
||||||
<div class="space-y-3">
|
|
||||||
<label class="relative flex cursor-pointer rounded-lg border border-slate-200 bg-white p-4 hover:bg-slate-50 transition">
|
|
||||||
<input type="radio" name="onboarding_mode" value="ns_auto" x-model="onboardingMode" class="sr-only peer">
|
|
||||||
<div class="flex flex-1 items-start gap-3">
|
|
||||||
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-emerald-100 text-emerald-600">
|
|
||||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.7L5.737 5.1a3.375 3.375 0 0 1 2.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 0 1 .9 2.7m0 0a3 3 0 0 1-3 3m0 3h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Zm-3 6h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Z"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<p class="text-sm font-semibold text-slate-900">Point Nameservers</p>
|
|
||||||
<p class="mt-0.5 text-xs text-slate-500">Point your domain's nameservers to Ladill. We'll manage DNS and SSL automatically.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="absolute right-4 top-4 hidden h-5 w-5 items-center justify-center rounded-full bg-indigo-600 text-white peer-checked:flex">
|
|
||||||
<svg class="h-3 w-3" fill="currentColor" viewBox="0 0 20 20"><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>
|
|
||||||
</div>
|
|
||||||
<div class="absolute inset-0 rounded-lg border-2 border-transparent peer-checked:border-indigo-600 pointer-events-none"></div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="relative flex cursor-pointer rounded-lg border border-slate-200 bg-white p-4 hover:bg-slate-50 transition">
|
|
||||||
<input type="radio" name="onboarding_mode" value="manual_dns" x-model="onboardingMode" class="sr-only peer">
|
|
||||||
<div class="flex flex-1 items-start gap-3">
|
|
||||||
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-blue-100 text-blue-600">
|
|
||||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<p class="text-sm font-semibold text-slate-900">Add DNS Records</p>
|
|
||||||
<p class="mt-0.5 text-xs text-slate-500">Keep your current DNS provider and manually add A records pointing to our server.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="absolute right-4 top-4 hidden h-5 w-5 items-center justify-center rounded-full bg-indigo-600 text-white peer-checked:flex">
|
|
||||||
<svg class="h-3 w-3" fill="currentColor" viewBox="0 0 20 20"><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>
|
|
||||||
</div>
|
|
||||||
<div class="absolute inset-0 rounded-lg border-2 border-transparent peer-checked:border-indigo-600 pointer-events-none"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
@error('onboarding_mode')
|
|
||||||
<p class="mt-1.5 text-sm text-red-600">{{ $message }}</p>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Document Root (optional, both flows) --}}
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-slate-700 mb-1">Document Root <span class="font-normal text-slate-400">(optional)</span></label>
|
|
||||||
<input type="text" name="document_root" placeholder="public_html/example.com" value="{{ old('document_root') }}"
|
|
||||||
class="w-full rounded-lg border-slate-200 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
||||||
<p class="mt-1 text-xs text-slate-500">Leave empty to use default: public_html/domain.com</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn-primary">
|
<button type="submit" class="btn-primary">
|
||||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
||||||
@@ -188,7 +96,6 @@
|
|||||||
>
|
>
|
||||||
<x-slot:trigger>
|
<x-slot:trigger>
|
||||||
<button type="button" class="inline-flex items-center gap-1.5 rounded-lg border border-red-200 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 transition">
|
<button type="button" class="inline-flex items-center gap-1.5 rounded-lg border border-red-200 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 transition">
|
||||||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>
|
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
</x-slot:trigger>
|
</x-slot:trigger>
|
||||||
@@ -209,21 +116,14 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@php
|
@php $serverIp = $account->node?->ip_address ?? '161.97.138.149'; @endphp
|
||||||
$serverIp = $account->node?->ip_address ?? '161.97.138.149';
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
{{-- DNS Quick Reference --}}
|
|
||||||
<div class="rounded-xl border border-slate-200 bg-white p-6">
|
<div class="rounded-xl border border-slate-200 bg-white p-6">
|
||||||
<h3 class="text-base font-semibold text-slate-900 mb-2">DNS Quick Reference</h3>
|
<h3 class="text-base font-semibold text-slate-900 mb-2">DNS Quick Reference</h3>
|
||||||
<p class="text-sm text-slate-600 mb-4">Use one of these methods to connect an external domain:</p>
|
<p class="text-sm text-slate-600 mb-4">Use one of these methods to connect an external domain:</p>
|
||||||
|
|
||||||
<div class="grid gap-4 md:grid-cols-2">
|
<div class="grid gap-4 md:grid-cols-2">
|
||||||
<div class="rounded-lg border border-emerald-200 bg-emerald-50/50 p-4">
|
<div class="rounded-lg border border-emerald-200 bg-emerald-50/50 p-4">
|
||||||
<div class="flex items-center gap-2 mb-2">
|
<h4 class="text-sm font-semibold text-slate-900 mb-2">Nameservers</h4>
|
||||||
<h4 class="text-sm font-semibold text-slate-900">Nameservers</h4>
|
|
||||||
<span class="inline-flex items-center rounded-full bg-emerald-100 px-2 py-0.5 text-[10px] font-medium text-emerald-800">Recommended</span>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<div class="flex items-center justify-between rounded bg-white border border-emerald-200 px-3 py-1.5">
|
<div class="flex items-center justify-between rounded bg-white border border-emerald-200 px-3 py-1.5">
|
||||||
<span class="text-sm font-mono text-slate-900">ns1.ladill.com</span>
|
<span class="text-sm font-mono text-slate-900">ns1.ladill.com</span>
|
||||||
@@ -235,27 +135,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-lg border border-slate-200 bg-slate-50/50 p-4">
|
<div class="rounded-lg border border-slate-200 bg-slate-50/50 p-4">
|
||||||
<h4 class="text-sm font-semibold text-slate-900 mb-2">A Records</h4>
|
<h4 class="text-sm font-semibold text-slate-900 mb-2">A Records</h4>
|
||||||
<div class="space-y-1 rounded bg-white border border-slate-200 p-3">
|
<div class="space-y-1 rounded bg-white border border-slate-200 p-3 text-xs font-mono">
|
||||||
<div class="grid grid-cols-3 gap-2 text-xs">
|
<div>A @ {{ $serverIp }}</div>
|
||||||
<span class="font-medium text-slate-500">Type</span>
|
<div>A www {{ $serverIp }}</div>
|
||||||
<span class="font-medium text-slate-500">Name</span>
|
|
||||||
<span class="font-medium text-slate-500">Value</span>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 gap-2 text-xs">
|
|
||||||
<span class="font-mono text-slate-900">A</span>
|
|
||||||
<span class="font-mono text-slate-900">@</span>
|
|
||||||
<span class="font-mono text-slate-900">{{ $serverIp }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 gap-2 text-xs">
|
|
||||||
<span class="font-mono text-slate-900">A</span>
|
|
||||||
<span class="font-mono text-slate-900">www</span>
|
|
||||||
<span class="font-mono text-slate-900">{{ $serverIp }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-xs text-slate-500">DNS changes can take up to 24–48 hours.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -35,50 +35,23 @@
|
|||||||
|
|
||||||
@if ($nativeForm['requires_domain'])
|
@if ($nativeForm['requires_domain'])
|
||||||
@php
|
@php
|
||||||
$newDomainUrl = ladill_domains_url('/find');
|
$ownedDomainHosts = $userDomains;
|
||||||
|
$selectedOwned = in_array($oldDomainName, $ownedDomainHosts, true) ? $oldDomainName : '';
|
||||||
|
$externalFallback = $initialDomainSource === 'external' ? $oldDomainName : '';
|
||||||
@endphp
|
@endphp
|
||||||
<div>
|
<div x-data="ladillDomainSourceForm({
|
||||||
<div class="mb-2 flex items-center justify-between">
|
domainSource: @js($initialDomainSource),
|
||||||
<label class="text-sm font-medium text-gray-700">Domain</label>
|
selectedOwnedDomain: @js($selectedOwned),
|
||||||
<div class="inline-flex items-center rounded-md bg-gray-100 p-0.5 text-[11px] font-medium">
|
externalDomain: @js($externalFallback),
|
||||||
<button type="button" @click="domainSource = 'ladill'"
|
ownedDomains: @js($ownedDomainHosts),
|
||||||
:class="domainSource === 'ladill' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-500'"
|
ownedUrl: @js(route('servers.domains.owned')),
|
||||||
class="rounded px-2 py-1 transition">Ladill</button>
|
})">
|
||||||
<button type="button" @click="domainSource = 'external'"
|
<x-domain-source-fields
|
||||||
:class="domainSource === 'external' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-500'"
|
field-name="domain_name"
|
||||||
class="rounded px-2 py-1 transition">External</button>
|
:owned-domains="$ownedDomainHosts"
|
||||||
</div>
|
owned-url="{{ route('servers.domains.owned') }}"
|
||||||
</div>
|
variant="gray"
|
||||||
|
/>
|
||||||
<div x-show="domainSource === 'ladill'" x-cloak>
|
|
||||||
@if ($userDomains->isNotEmpty())
|
|
||||||
<div class="space-y-2">
|
|
||||||
<select name="domain_name" :disabled="domainSource !== 'ladill'"
|
|
||||||
class="w-full rounded-lg border-gray-200 bg-white px-3 py-2 text-sm focus:border-gray-400 focus:ring-0">
|
|
||||||
<option value="">Choose a domain</option>
|
|
||||||
@foreach ($userDomains as $domain)
|
|
||||||
<option value="{{ $domain->host }}" @selected(old('domain_name') === $domain->host)>{{ $domain->host }}</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
<p class="text-xs text-gray-500">
|
|
||||||
Prefer another?
|
|
||||||
<a href="{{ $newDomainUrl }}" class="font-medium text-gray-700 underline underline-offset-2 hover:text-gray-900">Get a new domain</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<div class="rounded-lg bg-gray-50 px-3 py-2.5 text-center text-xs">
|
|
||||||
<a href="{{ $newDomainUrl }}" class="font-medium text-gray-700 underline underline-offset-2 hover:text-gray-900">Get a new domain</a>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
<div x-show="domainSource === 'external'" x-cloak>
|
|
||||||
<input name="domain_name" type="text" x-model="externalDomain" :disabled="domainSource !== 'external'" value="{{ old('domain_name') }}"
|
|
||||||
placeholder="example.com"
|
|
||||||
class="w-full rounded-lg border-gray-200 bg-white px-3 py-2 text-sm placeholder-gray-400 focus:border-gray-400 focus:ring-0">
|
|
||||||
</div>
|
|
||||||
@error('domain_name')
|
|
||||||
<p class="mt-1 text-xs text-red-600">{{ $message }}</p>
|
|
||||||
@enderror
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
$orderFormErrorFields = ['domain_name', 'plan_id', 'months'];
|
$orderFormErrorFields = ['domain_name', 'plan_id', 'months'];
|
||||||
$hasOrderFormErrors = collect($orderFormErrorFields)->contains(fn ($field) => $errors->has($field));
|
$hasOrderFormErrors = collect($orderFormErrorFields)->contains(fn ($field) => $errors->has($field));
|
||||||
$shouldOpenOrderModal = (bool) session('open_product_order_modal', false) || $hasOrderFormErrors;
|
$shouldOpenOrderModal = (bool) session('open_product_order_modal', false) || $hasOrderFormErrors;
|
||||||
$knownDomainHosts = $userDomains->pluck('host')->all();
|
$knownDomainHosts = $userDomains;
|
||||||
$oldDomainName = trim((string) old('domain_name', ''));
|
$oldDomainName = trim((string) old('domain_name', ''));
|
||||||
$initialDomainSource = $oldDomainName !== '' && ! in_array($oldDomainName, $knownDomainHosts, true) ? 'external' : 'ladill';
|
$initialDomainSource = $oldDomainName !== '' && ! in_array($oldDomainName, $knownDomainHosts, true) ? 'external' : 'ladill';
|
||||||
$initialExternalDomain = $initialDomainSource === 'external' ? $oldDomainName : '';
|
$initialExternalDomain = $initialDomainSource === 'external' ? $oldDomainName : '';
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
|| $rcServiceOrders->isNotEmpty();
|
|| $rcServiceOrders->isNotEmpty();
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<div class="space-y-6" x-data="{ showOrderModal: @js($shouldOpenOrderModal), domainSource: @js($initialDomainSource), externalDomain: @js($initialExternalDomain) }">
|
<div class="space-y-6" x-data="{ showOrderModal: @js($shouldOpenOrderModal) }">
|
||||||
{{-- Page Header --}}
|
{{-- Page Header --}}
|
||||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
</head>
|
</head>
|
||||||
<body class="h-full font-sans antialiased bg-slate-100" x-data="{ sidebarOpen: false }">
|
<body class="h-full font-sans antialiased bg-slate-100" x-data="{ sidebarOpen: false }" data-ladill-search-url="{{ route('servers.search') }}">
|
||||||
<div class="min-h-screen max-lg:min-h-dvh">
|
<div class="min-h-screen max-lg:min-h-dvh">
|
||||||
<div x-show="sidebarOpen" x-cloak class="fixed inset-0 z-30 bg-black/50 lg:hidden" @click="sidebarOpen = false"></div>
|
<div x-show="sidebarOpen" x-cloak class="fixed inset-0 z-30 bg-black/50 lg:hidden" @click="sidebarOpen = false"></div>
|
||||||
<aside x-cloak class="fixed inset-y-0 left-0 z-40 w-64 transform transition-transform lg:translate-x-0"
|
<aside x-cloak class="fixed inset-y-0 left-0 z-40 w-64 transform transition-transform lg:translate-x-0"
|
||||||
@@ -53,5 +53,6 @@
|
|||||||
@endauth
|
@endauth
|
||||||
@include('partials.sso-keepalive')
|
@include('partials.sso-keepalive')
|
||||||
@include('partials.confirm-prompt')
|
@include('partials.confirm-prompt')
|
||||||
|
@include('components.domain-purchase-modal')
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<div class="px-4 pb-3">
|
<div class="px-4 pb-3">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<svg class="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"/></svg>
|
<svg class="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"/></svg>
|
||||||
<input x-ref="input"
|
<input data-ladill-search-input x-ref="input"
|
||||||
type="text"
|
type="text"
|
||||||
x-model="query"
|
x-model="query"
|
||||||
@focus="onFocus()"
|
@focus="onFocus()"
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<h1 class="text-2xl font-semibold text-slate-900">{{ $heading }}</h1>
|
<h1 class="text-2xl font-semibold text-slate-900">{{ $heading }}</h1>
|
||||||
<div class="relative mt-3">
|
<div class="relative mt-3">
|
||||||
<svg class="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"/></svg>
|
<svg class="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"/></svg>
|
||||||
<input x-ref="input"
|
<input data-ladill-search-input x-ref="input"
|
||||||
type="text"
|
type="text"
|
||||||
x-model="query"
|
x-model="query"
|
||||||
@focus="onFocus()"
|
@focus="onFocus()"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="relative hidden w-full max-w-md lg:block" x-data="topbarSearch({ searchUrl: @js(route('servers.search')) })" @click.outside="open = false" @keydown.escape.window="open = false">
|
<div class="relative hidden w-full max-w-md lg:block" x-data="topbarSearch({ searchUrl: @js(route('servers.search')) })" @click.outside="open = false" @keydown.escape.window="open = false">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<svg class="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.2-5.2m0 0A7.5 7.5 0 1 0 5.2 5.2a7.5 7.5 0 0 0 10.6 10.6Z"/></svg>
|
<svg class="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.2-5.2m0 0A7.5 7.5 0 1 0 5.2 5.2a7.5 7.5 0 0 0 10.6 10.6Z"/></svg>
|
||||||
<input type="text" x-model="query" @focus="onFocus()" @input.debounce.200ms="search()" @keydown.arrow-down.prevent="moveDown()" @keydown.arrow-up.prevent="moveUp()" @keydown.enter.prevent="go()" placeholder="Search servers…"
|
<input data-ladill-search-input type="text" x-model="query" @focus="onFocus()" @input.debounce.200ms="search()" @keydown.arrow-down.prevent="moveDown()" @keydown.arrow-up.prevent="moveUp()" @keydown.enter.prevent="go()" placeholder="Search servers…"
|
||||||
class="w-full rounded-xl border border-slate-200 bg-slate-50 py-2 pl-9 pr-3 text-sm text-slate-700 placeholder-slate-400 transition focus:border-indigo-300 focus:bg-white focus:ring-2 focus:ring-indigo-100 focus:outline-none">
|
class="w-full rounded-xl border border-slate-200 bg-slate-50 py-2 pl-9 pr-3 text-sm text-slate-700 placeholder-slate-400 transition focus:border-indigo-300 focus:bg-white focus:ring-2 focus:ring-indigo-100 focus:outline-none">
|
||||||
</div>
|
</div>
|
||||||
<div x-show="open && (results.length > 0 || (query.length >= 2 && !loading))" x-cloak x-transition.opacity.duration.150ms class="absolute left-0 top-full z-30 mt-1.5 w-full overflow-hidden rounded-xl border border-slate-200 bg-white shadow-lg">
|
<div x-show="open && (results.length > 0 || (query.length >= 2 && !loading))" x-cloak x-transition.opacity.duration.150ms class="absolute left-0 top-full z-30 mt-1.5 w-full overflow-hidden rounded-xl border border-slate-200 bg-white shadow-lg">
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ Route::middleware(['auth'])->group(function () {
|
|||||||
Route::post('/notifications/mark-all-read', [NotificationController::class, 'markAllAsRead'])->name('notifications.mark-all-read');
|
Route::post('/notifications/mark-all-read', [NotificationController::class, 'markAllAsRead'])->name('notifications.mark-all-read');
|
||||||
|
|
||||||
Route::get('/search', SearchController::class)->name('servers.search');
|
Route::get('/search', SearchController::class)->name('servers.search');
|
||||||
|
Route::get('/domains/owned', \App\Http\Controllers\OwnedDomainsController::class)->name('servers.domains.owned');
|
||||||
Route::get('/dashboard', [OverviewController::class, 'index'])->name('servers.dashboard');
|
Route::get('/dashboard', [OverviewController::class, 'index'])->name('servers.dashboard');
|
||||||
|
|
||||||
Route::get('/vps', [HostingProductController::class, 'vps'])->name('servers.vps');
|
Route::get('/vps', [HostingProductController::class, 'vps'])->name('servers.vps');
|
||||||
|
|||||||
Reference in New Issue
Block a user