crud user
This commit is contained in:
parent
681059eccc
commit
a786de2801
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$user = Auth::user();
|
||||
return inertia('admin/Dashboard', [
|
||||
'user' => $user,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use App\Models\User;
|
||||
|
|
@ -17,7 +18,7 @@ public function index()
|
|||
{
|
||||
//
|
||||
$user = User::paginate(10);
|
||||
return Inertia::render('user/UserIndex', [
|
||||
return Inertia::render('admin/user/UserIndex', [
|
||||
'users' => $user,
|
||||
]);
|
||||
}
|
||||
|
|
@ -28,7 +29,7 @@ public function index()
|
|||
public function create()
|
||||
{
|
||||
//
|
||||
return Inertia::render('user/UserCreate');
|
||||
return Inertia::render('admin/user/UserCreate');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -53,7 +54,7 @@ public function store(Request $request)
|
|||
|
||||
DB::commit();
|
||||
|
||||
return redirect()->route('users.index')->with('success', 'User berhasil ditambahkan.');
|
||||
return redirect()->route('admin.users.index')->with('success', 'User berhasil ditambahkan.');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
return redirect()->back()->with('error', 'Gagal menambahkan user: ' . $e->getMessage());
|
||||
|
|
@ -75,7 +76,7 @@ public function edit(string $id)
|
|||
{
|
||||
//
|
||||
$user = User::findOrFail($id);
|
||||
return Inertia::render('user/UserEdit', [
|
||||
return Inertia::render('admin/user/UserEdit', [
|
||||
'user' => $user,
|
||||
]);
|
||||
}
|
||||
|
|
@ -110,7 +111,7 @@ public function update(Request $request, string $id)
|
|||
|
||||
DB::commit();
|
||||
|
||||
return redirect()->route('users.index')->with('success', 'User berhasil diupdate.');
|
||||
return redirect()->route('admin.users.index')->with('success', 'User berhasil diupdate.');
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
return redirect()->back()->with('error', 'Gagal mengupdate user: ' . $e->getMessage());
|
||||
|
|
@ -126,7 +127,7 @@ public function destroy(string $id)
|
|||
try {
|
||||
$user = User::findOrFail($id);
|
||||
$user->delete();
|
||||
return redirect()->route('users.index')->with('success', 'User berhasil dihapus.');
|
||||
return redirect()->route('admin.users.index')->with('success', 'User berhasil dihapus.');
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()->with('error', 'Gagal menghapus user: ' . $e->getMessage());
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ const activeItemStyles =
|
|||
const mainNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
href: route('dashboard'),
|
||||
href: route('admin.dashboard'),
|
||||
icon: LayoutGrid,
|
||||
},
|
||||
];
|
||||
|
|
@ -146,7 +146,7 @@ const rightNavItems: NavItem[] = [
|
|||
</Sheet>
|
||||
</div>
|
||||
|
||||
<Link :href="route('dashboard')" class="flex items-center gap-x-2">
|
||||
<Link :href="route('admin.dashboard')" class="flex items-center gap-x-2">
|
||||
<AppLogo />
|
||||
</Link>
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ import type { NavItem } from '@/types';
|
|||
const mainNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
href: route('dashboard'),
|
||||
href: route('admin.dashboard'),
|
||||
icon: LayoutGrid,
|
||||
},
|
||||
{
|
||||
title: 'Users',
|
||||
href: route('users.index'),
|
||||
href: route('admin.users.index'),
|
||||
icon: Users,
|
||||
},
|
||||
];
|
||||
|
|
@ -50,7 +50,7 @@ const footerNavItems: NavItem[] = [
|
|||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton size="lg" as-child>
|
||||
<Link :href="route('dashboard')">
|
||||
<Link :href="route('admin.dashboard')">
|
||||
<AppLogo />
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,150 @@
|
|||
<template>
|
||||
<section id="features" class="py-24 px-4">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4 font-display">Everything you need to ship</h2>
|
||||
<p class="text-zinc-400 max-w-2xl mx-auto">
|
||||
Built for modern teams. Powerful features that help you build, deploy, and scale faster than ever.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Large card - System Status -->
|
||||
<div
|
||||
class="md:col-span-2 group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-zinc-600 hover:scale-[1.02] transition-all duration-300 overflow-hidden"
|
||||
>
|
||||
<div class="flex items-start justify-between mb-8">
|
||||
<div>
|
||||
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4">
|
||||
<Activity class="w-5 h-5 text-zinc-400" :stroke-width="1.5" />
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-white mb-2">Real-time Monitoring</h3>
|
||||
<p class="text-zinc-400 text-sm">
|
||||
Track system health, performance metrics, and alerts in real-time across all your deployments.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
v-for="(active, i) in dots"
|
||||
:key="i"
|
||||
:class="[
|
||||
'w-2 h-2 rounded-full',
|
||||
active ? 'bg-emerald-500 animate-pulse' : 'bg-zinc-700',
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<div v-for="metric in metrics" :key="metric" class="text-center">
|
||||
<div class="text-2xl font-bold text-white mb-1">{{ Math.floor(Math.random() * 40 + 60) }}%</div>
|
||||
<div class="text-xs text-zinc-500">{{ metric }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Command Palette -->
|
||||
<div
|
||||
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-zinc-600 hover:scale-[1.02] transition-all duration-300"
|
||||
>
|
||||
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4">
|
||||
<Command class="w-5 h-5 text-zinc-400" :stroke-width="1.5" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-white mb-2">Command Palette</h3>
|
||||
<p class="text-zinc-400 text-sm mb-6">Navigate anywhere instantly with powerful keyboard shortcuts.</p>
|
||||
<div class="flex items-center gap-1">
|
||||
<kbd class="px-2 py-1 text-xs bg-zinc-800 border border-zinc-700 rounded text-zinc-300 font-mono">
|
||||
⌘
|
||||
</kbd>
|
||||
<kbd class="px-2 py-1 text-xs bg-zinc-800 border border-zinc-700 rounded text-zinc-300 font-mono">
|
||||
K
|
||||
</kbd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Analytics -->
|
||||
<div
|
||||
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-zinc-600 hover:scale-[1.02] transition-all duration-300"
|
||||
>
|
||||
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4">
|
||||
<BarChart3 class="w-5 h-5 text-zinc-400" :stroke-width="1.5" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-white mb-2">Analytics</h3>
|
||||
<p class="text-zinc-400 text-sm mb-4">Deep insights into your application performance.</p>
|
||||
<svg viewBox="0 0 100 70" class="w-full h-24">
|
||||
<defs>
|
||||
<linearGradient id="chartGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="rgb(255,255,255)" stop-opacity="0.2" />
|
||||
<stop offset="100%" stop-color="rgb(255,255,255)" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M 0 60 L 20 45 L 40 55 L 60 30 L 80 40 L 100 15 L 100 70 L 0 70 Z"
|
||||
fill="url(#chartGradient)"
|
||||
class="opacity-50"
|
||||
/>
|
||||
<path
|
||||
d="M 0 60 L 20 45 L 40 55 L 60 30 L 80 40 L 100 15"
|
||||
fill="none"
|
||||
stroke="white"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Performance -->
|
||||
<div
|
||||
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-zinc-600 hover:scale-[1.02] transition-all duration-300"
|
||||
>
|
||||
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4">
|
||||
<Zap class="w-5 h-5 text-zinc-400" :stroke-width="1.5" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-white mb-2">Blazing Fast</h3>
|
||||
<p class="text-zinc-400 text-sm mb-4">
|
||||
Edge-optimized infrastructure for sub-50ms response times globally.
|
||||
</p>
|
||||
<div class="flex items-center gap-2 text-emerald-500 text-sm">
|
||||
<span class="font-mono">~32ms</span>
|
||||
<span class="text-zinc-500">avg response</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security -->
|
||||
<div
|
||||
class="group relative p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-zinc-600 hover:scale-[1.02] transition-all duration-300"
|
||||
>
|
||||
<div class="p-2 rounded-lg bg-zinc-800 w-fit mb-4">
|
||||
<Shield class="w-5 h-5 text-zinc-400" :stroke-width="1.5" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-white mb-2">Enterprise Security</h3>
|
||||
<p class="text-zinc-400 text-sm mb-4">SOC2 compliant with end-to-end encryption and SSO support.</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-1 text-xs bg-zinc-800 rounded text-zinc-400">SOC2</span>
|
||||
<span class="px-2 py-1 text-xs bg-zinc-800 rounded text-zinc-400">GDPR</span>
|
||||
<span class="px-2 py-1 text-xs bg-zinc-800 rounded text-zinc-400">HIPAA</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { Activity, Command, BarChart3, Zap, Shield } from 'lucide-vue-next'
|
||||
|
||||
const dots = ref([true, true, true, false, true])
|
||||
const metrics = ['CPU', 'Memory', 'Network', 'Storage']
|
||||
|
||||
let interval: NodeJS.Timeout
|
||||
|
||||
onMounted(() => {
|
||||
interval = setInterval(() => {
|
||||
dots.value = dots.value.map(() => Math.random() > 0.2)
|
||||
}, 2000)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(interval)
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<section class="py-24 px-4">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<h2 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6 tracking-tight font-display">
|
||||
Ready to ship faster?
|
||||
</h2>
|
||||
<p class="text-lg sm:text-xl text-zinc-400 mb-10 max-w-2xl mx-auto">
|
||||
Join thousands of teams already building with Apex. Start free, no credit card required.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<button
|
||||
class="shimmer-btn bg-white text-zinc-950 hover:bg-zinc-200 rounded-full px-8 py-3 text-base font-medium shadow-lg shadow-white/20 flex items-center gap-2"
|
||||
>
|
||||
Get Started for Free
|
||||
<ArrowRight :size="20" />
|
||||
</button>
|
||||
<button
|
||||
class="rounded-full px-8 py-3 text-base font-medium border border-zinc-800 text-zinc-300 hover:bg-zinc-900 hover:text-white hover:border-zinc-700 bg-transparent"
|
||||
>
|
||||
Talk to Sales
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="mt-8 text-sm text-zinc-500">Free forever for individuals. Team plans start at $29/month.</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ArrowRight } from 'lucide-vue-next'
|
||||
</script>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<footer class="border-t border-zinc-800 bg-zinc-950">
|
||||
<div class="max-w-6xl mx-auto px-4 py-16">
|
||||
<div class="grid grid-cols-2 md:grid-cols-5 gap-8">
|
||||
<!-- Brand -->
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<a href="#" class="flex items-center gap-2 mb-4">
|
||||
<div class="w-8 h-8 rounded-lg bg-white flex items-center justify-center">
|
||||
<span class="text-zinc-950 font-bold text-sm">A</span>
|
||||
</div>
|
||||
<span class="font-semibold text-white">Apex</span>
|
||||
</a>
|
||||
<p class="text-sm text-zinc-500 mb-4">The modern platform for teams who ship fast.</p>
|
||||
<div class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-zinc-900 border border-zinc-800">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-500 pulse-glow" />
|
||||
<span class="text-xs text-zinc-400">All Systems Operational</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Links -->
|
||||
<div v-for="(links, title) in footerLinks" :key="title">
|
||||
<h4 class="text-sm font-semibold text-white mb-4">{{ title }}</h4>
|
||||
<ul class="space-y-3">
|
||||
<li v-for="link in links" :key="link">
|
||||
<a href="#" class="text-sm text-zinc-500 hover:text-white transition-colors">
|
||||
{{ link }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom -->
|
||||
<div
|
||||
class="mt-16 pt-8 border-t border-zinc-800 flex flex-col sm:flex-row items-center justify-between gap-4"
|
||||
>
|
||||
<p class="text-sm text-zinc-500">© {{ new Date().getFullYear() }} Apex, Inc. All rights reserved.</p>
|
||||
<div class="flex items-center gap-6">
|
||||
<a href="#" class="text-sm text-zinc-500 hover:text-white transition-colors">Twitter</a>
|
||||
<a href="#" class="text-sm text-zinc-500 hover:text-white transition-colors">GitHub</a>
|
||||
<a href="#" class="text-sm text-zinc-500 hover:text-white transition-colors">Discord</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const footerLinks: Record<string, string[]> = {
|
||||
Product: ['Features', 'Pricing', 'Changelog', 'Roadmap', 'API'],
|
||||
Resources: ['Documentation', 'Guides', 'Blog', 'Community', 'Templates'],
|
||||
Company: ['About', 'Careers', 'Press', 'Partners', 'Contact'],
|
||||
Legal: ['Privacy', 'Terms', 'Security', 'Cookies', 'Licenses'],
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<section
|
||||
class="relative min-h-screen flex flex-col items-center justify-center px-4 pt-24 pb-16 overflow-hidden"
|
||||
>
|
||||
<!-- Background gradient -->
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-b from-zinc-950 via-zinc-950 to-zinc-900 pointer-events-none"
|
||||
/>
|
||||
|
||||
<!-- Subtle radial glow -->
|
||||
<div
|
||||
class="absolute top-1/4 left-1/2 -translate-x-1/2 w-[800px] h-[600px] bg-zinc-800/20 rounded-full blur-3xl pointer-events-none"
|
||||
/>
|
||||
|
||||
<div class="relative z-10 max-w-5xl mx-auto text-center">
|
||||
<!-- Badge -->
|
||||
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-zinc-900 border border-zinc-800 mb-8">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-500 pulse-glow" />
|
||||
<span class="text-sm text-zinc-400">Now in Public Beta</span>
|
||||
</div>
|
||||
|
||||
<!-- Headline -->
|
||||
<h1 class="text-5xl sm:text-6xl lg:text-7xl font-bold tracking-tight text-white mb-6 font-display">
|
||||
<span class="block">Ship faster.</span>
|
||||
<span class="block text-zinc-500">Scale smarter.</span>
|
||||
</h1>
|
||||
|
||||
<!-- Subheadline -->
|
||||
<p class="text-lg sm:text-xl text-zinc-400 max-w-2xl mx-auto mb-10 leading-relaxed">
|
||||
The modern platform for teams who ship fast. Built for scale, designed for speed. Everything you need to build,
|
||||
deploy, and grow.
|
||||
</p>
|
||||
|
||||
<!-- CTAs -->
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 mb-16">
|
||||
<button
|
||||
class="shimmer-btn bg-white text-zinc-950 hover:bg-zinc-200 rounded-full px-8 py-3 text-base font-medium shadow-lg shadow-white/10 flex items-center gap-2"
|
||||
>
|
||||
Start Building
|
||||
<ArrowRight :size="16" />
|
||||
</button>
|
||||
<button
|
||||
class="rounded-full px-8 py-3 text-base font-medium border border-zinc-800 text-zinc-300 hover:bg-zinc-900 hover:text-white hover:border-zinc-700 bg-transparent"
|
||||
>
|
||||
View Demo
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Social Proof -->
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="flex items-center -space-x-3">
|
||||
<img
|
||||
v-for="(avatar, index) in avatars"
|
||||
:key="index"
|
||||
:src="avatar"
|
||||
:alt="`Avatar ${index + 1}`"
|
||||
class="w-10 h-10 rounded-full border-2 border-zinc-950 object-cover"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-sm text-zinc-500">
|
||||
Trusted by <span class="text-zinc-300 font-medium">2,000+</span> teams worldwide
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ArrowRight } from 'lucide-vue-next'
|
||||
|
||||
const avatars = [
|
||||
'/professional-headshot-1.png',
|
||||
'/professional-headshot-2.png',
|
||||
'/professional-headshot-3.png',
|
||||
'/professional-headshot-4.png',
|
||||
'/professional-headshot-5.png',
|
||||
]
|
||||
</script>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<section class="py-16 overflow-hidden">
|
||||
<div class="text-center mb-10">
|
||||
<p class="text-sm text-zinc-500 uppercase tracking-wider font-medium">Trusted by industry leaders</p>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<!-- Fade masks -->
|
||||
<div
|
||||
class="absolute left-0 top-0 bottom-0 w-32 bg-gradient-to-r from-zinc-950 to-transparent z-10 pointer-events-none"
|
||||
/>
|
||||
<div
|
||||
class="absolute right-0 top-0 bottom-0 w-32 bg-gradient-to-l from-zinc-950 to-transparent z-10 pointer-events-none"
|
||||
/>
|
||||
|
||||
<!-- Marquee container -->
|
||||
<div class="flex animate-marquee">
|
||||
<div v-for="logo in [...logos, ...logos]" :key="`${logo.name}-marquee`" class="flex items-center justify-center min-w-[160px] h-16 mx-8 grayscale opacity-50 hover:grayscale-0 hover:opacity-100 transition-all duration-300">
|
||||
<div class="flex items-center gap-2 text-zinc-400">
|
||||
<div class="w-8 h-8 rounded-lg bg-zinc-800 flex items-center justify-center">
|
||||
<span class="text-xs font-bold">{{ logo.name[0] }}</span>
|
||||
</div>
|
||||
<span class="font-medium">{{ logo.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const logos = [
|
||||
{ name: 'Vercel', width: 100 },
|
||||
{ name: 'Stripe', width: 80 },
|
||||
{ name: 'Linear', width: 90 },
|
||||
{ name: 'Notion', width: 100 },
|
||||
{ name: 'Figma', width: 70 },
|
||||
{ name: 'Slack', width: 90 },
|
||||
{ name: 'Discord', width: 100 },
|
||||
{ name: 'GitHub', width: 90 },
|
||||
]
|
||||
</script>
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<header class="fixed top-4 left-1/2 -translate-x-1/2 z-50 w-[calc(100%-2rem)] max-w-3xl">
|
||||
<nav
|
||||
class="relative flex items-center justify-between px-4 py-3 rounded-full bg-zinc-900/40 backdrop-blur-md border border-zinc-800"
|
||||
>
|
||||
<!-- Logo -->
|
||||
<a href="#" class="flex items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-lg bg-white flex items-center justify-center">
|
||||
<span class="text-zinc-950 font-bold text-sm">A</span>
|
||||
</div>
|
||||
<span class="font-semibold text-white hidden sm:block">Apex</span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Nav Items -->
|
||||
<div class="hidden md:flex items-center gap-1 relative">
|
||||
<a
|
||||
v-for="(item, index) in navItems"
|
||||
:key="item.label"
|
||||
:href="item.href"
|
||||
class="relative px-4 py-2 text-sm text-zinc-400 hover:text-white transition-colors"
|
||||
@mouseenter="hoveredIndex = index"
|
||||
@mouseleave="hoveredIndex = null"
|
||||
>
|
||||
<div
|
||||
v-if="hoveredIndex === index"
|
||||
class="absolute inset-0 bg-zinc-800 rounded-full"
|
||||
/>
|
||||
<span class="relative z-10">{{ item.label }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="hidden md:flex items-center gap-3">
|
||||
<button
|
||||
class="px-4 py-2 text-sm text-zinc-400 hover:text-white hover:bg-zinc-800 rounded-lg transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
<button
|
||||
class="shimmer-btn bg-white text-zinc-950 hover:bg-zinc-200 rounded-full px-4 py-2 text-sm font-medium"
|
||||
>
|
||||
Get Started
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<button
|
||||
class="md:hidden p-2 text-zinc-400 hover:text-white"
|
||||
@click="mobileMenuOpen = !mobileMenuOpen"
|
||||
:aria-label="mobileMenuOpen ? 'Close menu' : 'Open menu'"
|
||||
>
|
||||
<Menu v-if="!mobileMenuOpen" :size="20" />
|
||||
<X v-else :size="20" />
|
||||
</button>
|
||||
|
||||
<!-- Mobile Menu -->
|
||||
<div
|
||||
v-if="mobileMenuOpen"
|
||||
class="absolute top-full left-0 right-0 mt-2 p-4 rounded-2xl bg-zinc-900/95 backdrop-blur-md border border-zinc-800"
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<a
|
||||
v-for="item in navItems"
|
||||
:key="item.label"
|
||||
:href="item.href"
|
||||
class="px-4 py-3 text-sm text-zinc-400 hover:text-white hover:bg-zinc-800 rounded-lg transition-colors"
|
||||
@click="mobileMenuOpen = false"
|
||||
>
|
||||
{{ item.label }}
|
||||
</a>
|
||||
<hr class="border-zinc-800 my-2" />
|
||||
<button class="px-4 py-3 text-sm text-zinc-400 hover:text-white hover:bg-zinc-800 rounded-lg transition-colors text-left">
|
||||
Sign In
|
||||
</button>
|
||||
<button
|
||||
class="shimmer-btn bg-white text-zinc-950 hover:bg-zinc-200 rounded-full px-4 py-2 text-sm font-medium w-full"
|
||||
>
|
||||
Get Started
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Menu, X } from 'lucide-vue-next'
|
||||
|
||||
const navItems = [
|
||||
{ label: 'Features', href: '#features' },
|
||||
{ label: 'Pricing', href: '#pricing' },
|
||||
{ label: 'Docs', href: '#docs' },
|
||||
{ label: 'Blog', href: '#blog' },
|
||||
]
|
||||
|
||||
const hoveredIndex = ref<number | null>(null)
|
||||
const mobileMenuOpen = ref(false)
|
||||
</script>
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
<template>
|
||||
<section id="pricing" class="py-24 px-4">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4 font-display">Simple, transparent pricing</h2>
|
||||
<p class="text-zinc-400 max-w-2xl mx-auto mb-8">Start free, scale as you grow. No hidden fees, no surprises.</p>
|
||||
|
||||
<!-- Billing Toggle -->
|
||||
<div class="inline-flex items-center p-1 rounded-full bg-zinc-900 border border-zinc-800">
|
||||
<button
|
||||
@click="billingCycle = 'monthly'"
|
||||
:class="[
|
||||
'relative px-4 py-2 text-sm font-medium rounded-full transition-colors',
|
||||
billingCycle === 'monthly' ? 'text-white' : 'text-zinc-400',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
v-if="billingCycle === 'monthly'"
|
||||
class="absolute inset-0 bg-zinc-800 rounded-full"
|
||||
/>
|
||||
<span class="relative z-10">Monthly</span>
|
||||
</button>
|
||||
<button
|
||||
@click="billingCycle = 'yearly'"
|
||||
:class="[
|
||||
'relative px-4 py-2 text-sm font-medium rounded-full transition-colors',
|
||||
billingCycle === 'yearly' ? 'text-white' : 'text-zinc-400',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
v-if="billingCycle === 'yearly'"
|
||||
class="absolute inset-0 bg-zinc-800 rounded-full"
|
||||
/>
|
||||
<span class="relative z-10">Yearly</span>
|
||||
<span class="relative z-10 ml-2 px-2 py-0.5 text-xs bg-emerald-500/20 text-emerald-400 rounded-full">
|
||||
-20%
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div
|
||||
v-for="(plan, index) in plans"
|
||||
:key="plan.name"
|
||||
:class="[
|
||||
'relative p-6 rounded-2xl border transition-all duration-300 hover:scale-[1.02]',
|
||||
plan.highlighted
|
||||
? 'bg-zinc-900 border-zinc-700'
|
||||
: 'bg-zinc-900/50 border-zinc-800 hover:border-zinc-600',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
v-if="plan.highlighted"
|
||||
class="absolute -top-3 left-1/2 -translate-x-1/2 px-3 py-1 bg-white text-zinc-950 text-xs font-medium rounded-full"
|
||||
>
|
||||
Most Popular
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<h3 class="text-xl font-semibold text-white mb-2">{{ plan.name }}</h3>
|
||||
<p class="text-zinc-400 text-sm">{{ plan.description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<div class="flex items-baseline gap-1">
|
||||
<span class="text-4xl font-bold text-white">${{ plan.price[billingCycle] }}</span>
|
||||
<span v-if="plan.price.monthly > 0" class="text-zinc-400 text-sm">/month</span>
|
||||
</div>
|
||||
<p v-if="billingCycle === 'yearly' && plan.price.yearly > 0" class="text-xs text-zinc-500 mt-1">
|
||||
Billed annually (${{ plan.price.yearly * 12 }}/year)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul class="space-y-3 mb-8">
|
||||
<li v-for="feature in plan.features" :key="feature" class="flex items-center gap-3 text-sm text-zinc-300">
|
||||
<Check class="w-4 h-4 text-emerald-500 shrink-0" :stroke-width="1.5" />
|
||||
{{ feature }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button
|
||||
:class="[
|
||||
'w-full rounded-full py-2 font-medium transition-colors',
|
||||
plan.highlighted
|
||||
? 'shimmer-btn bg-white text-zinc-950 hover:bg-zinc-200'
|
||||
: 'bg-zinc-800 text-white hover:bg-zinc-700 border border-zinc-700',
|
||||
]"
|
||||
>
|
||||
{{ plan.cta }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Check } from 'lucide-vue-next'
|
||||
|
||||
type BillingCycle = 'monthly' | 'yearly'
|
||||
|
||||
const billingCycle = ref<BillingCycle>('monthly')
|
||||
|
||||
const plans = [
|
||||
{
|
||||
name: 'Starter',
|
||||
description: 'Perfect for side projects and small teams',
|
||||
price: { monthly: 0, yearly: 0 },
|
||||
features: ['3 team members', '10 projects', 'Basic analytics', 'Community support', '1GB storage'],
|
||||
cta: 'Get Started',
|
||||
highlighted: false,
|
||||
},
|
||||
{
|
||||
name: 'Pro',
|
||||
description: 'For growing teams that need more power',
|
||||
price: { monthly: 29, yearly: 24 },
|
||||
features: [
|
||||
'Unlimited team members',
|
||||
'Unlimited projects',
|
||||
'Advanced analytics',
|
||||
'Priority support',
|
||||
'100GB storage',
|
||||
'Custom domains',
|
||||
'API access',
|
||||
],
|
||||
cta: 'Start Free Trial',
|
||||
highlighted: true,
|
||||
},
|
||||
{
|
||||
name: 'Enterprise',
|
||||
description: 'For organizations with advanced needs',
|
||||
price: { monthly: 99, yearly: 79 },
|
||||
features: [
|
||||
'Everything in Pro',
|
||||
'SSO & SAML',
|
||||
'Dedicated support',
|
||||
'SLA guarantee',
|
||||
'Unlimited storage',
|
||||
'Custom integrations',
|
||||
'Audit logs',
|
||||
],
|
||||
cta: 'Contact Sales',
|
||||
highlighted: false,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { route } from 'ziggy-js';
|
||||
import PlaceholderPattern from '@/components/PlaceholderPattern.vue';
|
||||
|
||||
defineOptions({
|
||||
layout: {
|
||||
breadcrumbs: [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
href: route('dashboard'),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head title="Dashboard" />
|
||||
|
||||
<div
|
||||
class="flex h-full flex-1 flex-col gap-4 overflow-x-auto rounded-xl p-4"
|
||||
>
|
||||
<div class="grid auto-rows-min gap-4 md:grid-cols-3">
|
||||
<div
|
||||
class="relative aspect-video overflow-hidden rounded-xl border border-sidebar-border/70 dark:border-sidebar-border"
|
||||
>
|
||||
<PlaceholderPattern />
|
||||
</div>
|
||||
<div
|
||||
class="relative aspect-video overflow-hidden rounded-xl border border-sidebar-border/70 dark:border-sidebar-border"
|
||||
>
|
||||
<PlaceholderPattern />
|
||||
</div>
|
||||
<div
|
||||
class="relative aspect-video overflow-hidden rounded-xl border border-sidebar-border/70 dark:border-sidebar-border"
|
||||
>
|
||||
<PlaceholderPattern />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="relative min-h-[100vh] flex-1 rounded-xl border border-sidebar-border/70 md:min-h-min dark:border-sidebar-border"
|
||||
>
|
||||
<PlaceholderPattern />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -26,7 +26,7 @@ withDefaults(
|
|||
<nav class="flex items-center justify-end gap-4">
|
||||
<Link
|
||||
v-if="$page.props.auth.user"
|
||||
:href="route('dashboard')"
|
||||
:href="route('admin.dashboard')"
|
||||
class="inline-block rounded-sm border border-[#19140035] px-5 py-1.5 text-sm leading-normal text-[#1b1b18] hover:border-[#1915014a] dark:border-[#3E3E3A] dark:text-[#EDEDEC] dark:hover:border-[#62605b]"
|
||||
>
|
||||
Dashboard
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
<script setup lang="ts">
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { route } from 'ziggy-js';
|
||||
import PlaceholderPattern from '@/components/PlaceholderPattern.vue';
|
||||
import { Card } from '@/components/ui/card';
|
||||
|
||||
defineOptions({
|
||||
layout: {
|
||||
breadcrumbs: [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
href: route('admin.dashboard'),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const { user } = defineProps<{ user: User }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head title="Dashboard" />
|
||||
|
||||
<div
|
||||
class="flex h-full flex-1 flex-col gap-4 overflow-x-auto rounded-xl p-4"
|
||||
>
|
||||
<!-- card greetings -->
|
||||
<Card class="flex-1 border-border bg-card">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-base font-medium text-foreground">
|
||||
Selamat Datang {{ user.name }}
|
||||
</h3>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
ini adalah dashboard admin
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -107,7 +107,7 @@ const form = useForm({
|
|||
});
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('users.store'), {
|
||||
form.post(route('admin.users.store'), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
form.reset();
|
||||
|
|
@ -41,7 +41,7 @@ const props = defineProps<{
|
|||
const deleteUser = () => {
|
||||
isDeleting.value = true;
|
||||
|
||||
router.delete(route('users.destroy', props.userId), {
|
||||
router.delete(route('admin.users.destroy', props.userId), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
open.value = false;
|
||||
|
|
@ -126,7 +126,7 @@ watch(open, (value) => {
|
|||
});
|
||||
|
||||
const submit = () => {
|
||||
form.put(route('users.update', props.user.id), {
|
||||
form.put(route('admin.users.update', props.user.id), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
open.value = false;
|
||||
|
|
@ -11,7 +11,7 @@ defineOptions({
|
|||
breadcrumbs: [
|
||||
{
|
||||
title: 'User',
|
||||
href: route('users.index'),
|
||||
href: route('admin.users.index'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<div class="bg-background text-foreground min-h-screen">
|
||||
<Navbar />
|
||||
<Hero />
|
||||
<LogoMarquee />
|
||||
<BentoGrid />
|
||||
<Pricing />
|
||||
<FinalCta />
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Navbar from '@/components/public/Navbar.vue'
|
||||
import Hero from '@/components/public/Hero.vue'
|
||||
import LogoMarquee from '@/components/public/LogoMarquee.vue'
|
||||
import BentoGrid from '@/components/public/BentoGrid.vue'
|
||||
import Pricing from '@/components/public/Pricing.vue'
|
||||
import FinalCta from '@/components/public/FinalCta.vue'
|
||||
import Footer from '@/components/public/Footer.vue'
|
||||
</script>
|
||||
|
|
@ -2,17 +2,22 @@
|
|||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Laravel\Fortify\Features;
|
||||
use App\Http\Controllers\UserController;
|
||||
use App\Http\Controllers\Admin\UserController;
|
||||
use App\Http\Controllers\Admin\DashboardController;
|
||||
|
||||
Route::inertia('/', 'Welcome', [
|
||||
'canRegister' => Features::enabled(Features::registration()),
|
||||
])->name('home');
|
||||
Route::redirect('dashboard', 'admin/dashboard')->name('dashboard');
|
||||
|
||||
Route::middleware(['auth', 'verified'])->group(function () {
|
||||
Route::inertia('dashboard', 'Dashboard')->name('dashboard');
|
||||
Route::get('/', function () {
|
||||
return inertia('public/Home', [
|
||||
'canRegister' => Features::enabled(Features::registration()),
|
||||
]);
|
||||
})->name('home');
|
||||
|
||||
Route::prefix('admin')->name('admin.')->middleware(['auth', 'verified'])->group(function () {
|
||||
Route::get('dashboard', [DashboardController::class, 'index'])->name('dashboard');
|
||||
Route::resource('users', UserController::class)->names('users');
|
||||
});
|
||||
|
||||
|
||||
|
||||
require __DIR__.'/settings.php';
|
||||
require __DIR__ . '/settings.php';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use App\Models\User;
|
||||
|
||||
test('guests are redirected to the login page', function () {
|
||||
$response = $this->get(route('dashboard'));
|
||||
$response = $this->get(route('admin.dashboard'));
|
||||
$response->assertRedirect(route('login'));
|
||||
});
|
||||
|
||||
|
|
@ -11,6 +11,6 @@
|
|||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->get(route('dashboard'));
|
||||
$response = $this->get(route('admin.dashboard'));
|
||||
$response->assertOk();
|
||||
});
|
||||
Loading…
Reference in New Issue