Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions app/Data/SecurityData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace App\Data;

use Spatie\LaravelData\Data;
use App\Data\UserData;

class SecurityData extends Data
{
public function __construct(
public readonly ?UserData $user,
) {
}
}
6 changes: 4 additions & 2 deletions app/Data/SharedData.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
namespace App\Data;

use Spatie\LaravelData\Data;
use App\Data\SecurityData;

class SharedData extends Data
{
public function __construct()
{
public function __construct(
public readonly SecurityData $security,
) {
}
}
5 changes: 5 additions & 0 deletions app/Http/Middleware/HandleHybridRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@

namespace App\Http\Middleware;

use App\Data\SecurityData;
use App\Data\SharedData;
use App\Data\UserData;
use Hybridly\Http\Middleware;

class HandleHybridRequests extends Middleware
{
public function share(): SharedData
{
return SharedData::from([
'security' => [
'user' => UserData::optional(auth()->user())
]
]);
}
}
1 change: 1 addition & 0 deletions app/Models/Action.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
/**
* @property Server $server
* @property Action\Status $status
* @mixin IdeHelperAction
*/
class Action extends Model
{
Expand Down
1 change: 1 addition & 0 deletions app/Models/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

/**
* @property array<string, mixed> $meta
* @mixin IdeHelperServer
*/
class Server extends Model
{
Expand Down
1 change: 1 addition & 0 deletions app/Models/Site.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

/**
* @property Site\Type $type
* @mixin IdeHelperSite
*/
class Site extends Model
{
Expand Down
3 changes: 3 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

/**
* @mixin IdeHelperUser
*/
class User extends Authenticatable
{
use HasFactory, Notifiable, Unguarded;
Expand Down
8 changes: 7 additions & 1 deletion lang/en/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

return [
'home' => [
'title' => 'Home',
'title' => 'Dashboard',
],
'servers' => [
'title' => 'Servers',
],
'sites' => [
'title' => 'Sites',
],
];
2 changes: 2 additions & 0 deletions lang/en/nouns.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
'home' => 'Home',
'servers' => 'Servers',
'sites' => 'Sites',
'profile' => 'Profile',
'sign-out' => 'Sign out',
];
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
"build": "vite build"
},
"devDependencies": {
"@iconify-json/mdi": "^1.1.65",
"@intlify/unplugin-vue-i18n": "^4.0.0",
"@tailwindcss/postcss": "^4.0.0-alpha.10",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@intlify/unplugin-vue-i18n": "^4.0.0",
"@types/node": "^20.11.30",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/runtime-core": "^3.4.21",
Expand All @@ -24,16 +25,17 @@
"tailwindcss": "^4.0.0-alpha.10",
"typescript": "^5.4.3",
"vite": "^5.0",
"vue-i18n": "^9.10.2",
"vue-eslint-parser": "^9.4.2"
"vue-eslint-parser": "^9.4.2",
"vue-i18n": "^9.10.2"
},
"dependencies": {
"@heroicons/vue": "^2.1.3",
"@unhead/vue": "^1.9.2",
"@vueuse/core": "^10.9.0",
"axios": "^1.6.8",
"hybridly": "^0.7.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"hybridly": "^0.7.3",
"tailwind-merge": "^2.2.2",
"vue": "^3.4.21"
}
Expand Down
66 changes: 66 additions & 0 deletions resources/components/main-header.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script setup lang="ts">
import { ChevronDownIcon, BellIcon, MagnifyingGlassIcon } from "@heroicons/vue/24/outline"
import { ref } from "vue"

const i18n = useI18n()
const user = useProperty("security.user")

const UserRoutes = [
{ text: i18n.t("nouns.profile"), url: "#" },
{ text: i18n.t("nouns.sign-out"), url: "#" },
]

const open = ref(false)
</script>

<template>
<div class="[ flex flex-1 gap-x-4 self-stretch lg:gap-x-6 ]">
<form class="[ relative flex flex-1 ]" action="#" method="GET">
<label for="search-field" class="[ sr-only ]">Search</label>
<MagnifyingGlassIcon class="[ pointer-events-none absolute inset-y-0 left-0 h-full w-5 text-gray-400 ]" aria-hidden="true" />
<input
id="search-field"
class="[ block h-full w-full border-0 py-0 pl-8 pr-0 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm ]"
placeholder="Search..."
type="search"
name="search"
/>
</form>
<div class="[ flex items-center gap-x-4 lg:gap-x-6 ]">
<button type="button" class="[ -m-2.5 p-2.5 text-gray-400 hover:text-gray-500 ]">
<span class="[ sr-only ]">View notifications</span>
<BellIcon class="[ h-6 w-6 ]" aria-hidden="true" />
</button>

<!-- Separator -->
<div class="[ hidden lg:block lg:h-6 lg:w-px lg:bg-gray-200 ]" aria-hidden="true" />

<!-- Profile dropdown -->
<div class="[ relative ]">
<div class="[ -m-1.5 flex items-center p-1.5 ]" @click="open = !open">
<span class="[ sr-only ]">Open user menu</span>
<span class="[ h-8 w-8 rounded-full bg-gray-200 ]"></span>
<span class="[ hidden lg:flex lg:items-center ]">
<span class="[ ml-4 text-sm font-semibold leading-6 text-gray-900 ]" aria-hidden="true">{{ user.name }}</span>
<ChevronDownIcon class="[ ml-2 h-5 w-5 text-gray-400 ]" aria-hidden="true" />
</span>
</div>
<transition
v-show="open"
enter-active-class="[ transition ease-out duration-100 ]"
enter-from-class="[ transform opacity-0 scale-95 ]"
enter-to-class="[ transform opacity-100 scale-100 ]"
leave-active-class="[ transition ease-in duration-75 ]"
leave-from-class="[ transform opacity-100 scale-100 ]"
leave-to-class="[ transform opacity-0 scale-95 ]"
>
<div class="[ absolute right-0 z-10 mt-2.5 w-32 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 focus:outline-none ]">
<div v-for="route in UserRoutes" :key="route.url">
<RouterLink :href="route.url" :class="['block px-3 py-1 text-sm leading-6 text-gray-900']">{{ route.text }}</RouterLink>
</div>
</div>
</transition>
</div>
</div>
</div>
</template>
120 changes: 120 additions & 0 deletions resources/components/sidebar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<script setup lang="ts">
import { XMarkIcon, HomeIcon, ServerIcon, GlobeAltIcon } from "@heroicons/vue/24/outline"

const i18n = useI18n()
const { matches } = useRoute()

const SidebarRoutes = [
{ text: i18n.t("nouns.home"), url: route("home.index"), name: "home", icon: HomeIcon },
{ text: i18n.t("nouns.servers"), url: route("servers.index"), name: "servers", icon: ServerIcon },
{ text: i18n.t("nouns.sites"), url: route("sites.index"), name: "sites", icon: GlobeAltIcon },
]

type Props = {
sidebarOpen: boolean
}

defineProps<Props>()
</script>

<template>
<div v-show="sidebarOpen">
<div class="[ relative z-50 lg:hidden ]" @click="sidebarOpen = false">
<transition
v-show="sidebarOpen"
enter="[ transition-opacity ease-linear duration-300 ]"
enter-from="[ opacity-0 ]"
enter-to="[ opacity-100 ]"
leave="[ transition-opacity ease-linear duration-300 ]"
leave-from="[ opacity-100 ]"
leave-to="[ opacity-0 ]"
>
<div class="[ fixed inset-0 bg-gray-900/80 ]" />
</transition>

<div class="[ fixed inset-0 flex ]">
<transition
v-show="sidebarOpen"
enter="[ transition ease-in-out duration-300 transform ]"
enter-from="[ -translate-x-full ]"
enter-to="[ translate-x-0 ]"
leave="[ transition ease-in-out duration-300 transform ]"
leave-from="[ translate-x-0 ]"
leave-to="[ -translate-x-full ]"
>
<div class="[ relative mr-16 flex w-full max-w-xs flex-1 ]">
<transition
v-show="sidebarOpen"
enter="[ ease-in-out duration-300 ]"
enter-from="[ opacity-0 ]"
enter-to="[ opacity-100 ]"
leave="[ ease-in-out duration-300 ]"
leave-from="[ opacity-100 ]"
leave-to="[ opacity-0 ]"
>
<div class="[ absolute left-full top-0 flex w-16 justify-center pt-5 ]">
<button type="button" class="[ -m-2.5 p-2.5 ]" @click="sidebarOpen = false">
<span class="[ sr-only ]">Close sidebar</span>
<XMarkIcon class="[ h-6 w-6 text-white ]" aria-hidden="true" />
</button>
</div>
</transition>

<div class="[ flex grow flex-col gap-y-5 overflow-y-auto bg-white px-6 pb-4 ]">
<div class="[ flex h-16 shrink-0 items-center ]">
<RouterLink :href="route('home.index')" class="[ text-lg font-extrabold tracking-tight ]"> roost </RouterLink>
</div>
<nav class="[ flex flex-1 flex-col ]">
<ul role="list" class="[ flex flex-1 flex-col gap-y-7 ]">
<li>
<ul role="list" class="[ -mx-2 space-y-1 ]">
<li v-for="route in SidebarRoutes" :key="route.url">
<RouterLink
:href="route.url"
:class="[
matches(`${route.name}.*`) ? 'bg-gray-50 text-indigo-600' : 'text-gray-700 hover:text-indigo-600 hover:bg-gray-50',
'group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold',
]"
@click="sidebarOpen = false"
>
<component :is="route.icon" class="[ h-6 w-6 ]" />
{{ route.text }}
</RouterLink>
</li>
</ul>
</li>
</ul>
</nav>
</div>
</div>
</transition>
</div>
</div>
</div>

<div class="[ hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col ]">
<div class="[ flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6 pb-4 ]">
<div class="[ flex h-16 shrink-0 items-center ]">
<RouterLink :href="route('home.index')" class="[ text-lg font-extrabold tracking-tight ]"> roost </RouterLink>
</div>
<nav class="[ flex flex-1 flex-col ]">
<ul role="list" class="[ flex flex-1 flex-col gap-y-7 ]">
<li>
<ul role="list" class="[ -mx-2 space-y-1 ]">
<li v-for="route in SidebarRoutes" :key="route.url">
<RouterLink
:href="route.url"
class="[ items-center gap-2 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold hover:bg-gray-50 hover:text-indigo-600 ]"
:class="[matches(`${route.name}.*`) ? 'bg-gray-50 text-indigo-600' : 'text-gray-700']"
>
<component :is="route.icon" class="[ h-6 w-6 ]" />
<span>{{ route.text }}</span>
</RouterLink>
</li>
</ul>
</li>
</ul>
</nav>
</div>
</div>
</template>
2 changes: 1 addition & 1 deletion resources/layouts/auth.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ContentContainer } from "@/primitives/content-container"
</script>

<template>
<main class="[ h-screen w-screen flex justify-center ]">
<main class="[ flex min-h-full flex-1 flex-col justify-center py-12 sm:px-6 lg:px-8 ]">
<ContentContainer class="[ max-w-lg ]">
<div class="[ mt-12 ]">
<slot />
Expand Down
Loading