add client/admin pages, show info and created admin api and server actions
This commit is contained in:
@@ -3,45 +3,31 @@
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import Link from 'next/link'
|
||||
import { USER_PROFILE_URL } from '@/config/routes'
|
||||
import { CABINET_ROUTES, USER_PROFILE_URL } from '@/config/routes'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import UserButton from '@/components/auth/user-button'
|
||||
import LocaleSwitcher from '@/components/locale-switcher'
|
||||
|
||||
const Navbar = () => {
|
||||
export const Navbar = () => {
|
||||
const pathname = usePathname()
|
||||
//
|
||||
|
||||
console.log(USER_PROFILE_URL)
|
||||
|
||||
return (
|
||||
<nav className="bg-secondary flex justify-between items-center top-0 absolute px-6 py-4 w-full shadow-sm">
|
||||
<div className="flex gap-x-4">
|
||||
<Button asChild variant={pathname.match(/^\/(en\/|)server/) ? 'default' : 'outline'}>
|
||||
<Link href={'/server'}>
|
||||
Server
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild variant={pathname.match(/^\/(en\/|)client/) ? 'default' : 'outline'}>
|
||||
<Link href={'/client'}>
|
||||
Client
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild variant={pathname.match(/^\/(en\/|)admin/) ? 'default' : 'outline'}>
|
||||
<Link href={'/admin'}>
|
||||
Admin
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild variant={pathname.match(/^\/(en\/|)cabinet/) ? 'default' : 'outline'}>
|
||||
<Link href={USER_PROFILE_URL}>
|
||||
Cabinet
|
||||
</Link>
|
||||
</Button>
|
||||
{CABINET_ROUTES.map((route) => (
|
||||
<Button asChild key={route} variant={pathname.endsWith(route) ? 'default' : 'outline'} className="border">
|
||||
<Link href={route}>
|
||||
{route[1]?.toUpperCase() + route.substring(2)}
|
||||
</Link>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex gap-x-2">
|
||||
<LocaleSwitcher/>
|
||||
<UserButton/>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default Navbar
|
||||
|
||||
68
app/[locale]/(protected)/cabinet/admin/page.tsx
Normal file
68
app/[locale]/(protected)/cabinet/admin/page.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
'use client'
|
||||
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card'
|
||||
import { RoleGate } from '@/components/auth/role-gate'
|
||||
import FormSuccess from '@/components/form-success'
|
||||
import { UserRole } from '@prisma/client'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { toast } from 'sonner'
|
||||
import { admin } from '@/actions/admin'
|
||||
|
||||
const AdminPage = () => {
|
||||
const onServerActionClick = () => {
|
||||
admin()
|
||||
.then((data) => {
|
||||
if (data.error) {
|
||||
toast.error(data.error)
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
toast.success(data.success)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onApiRouteClick = () => {
|
||||
fetch('/api/admin')
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
toast.success('Allow API Route')
|
||||
} else {
|
||||
toast.error('Forbidden API Route')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="w-[600px]">
|
||||
<CardHeader>
|
||||
<p className="text-2xl font-semibold text-center">
|
||||
🔑 Admin
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<RoleGate allowedRole={UserRole.ADMIN}>
|
||||
<FormSuccess message="You are allowed to see this content!"/>
|
||||
</RoleGate>
|
||||
<div className="flex flex-row justify-between items-center rounded-lg border p-3 shadow-md">
|
||||
<p className="text-sm font-medium">
|
||||
Admin-only API Route
|
||||
</p>
|
||||
<Button onClick={onApiRouteClick}>
|
||||
Click to test
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between items-center rounded-lg border p-3 shadow-md">
|
||||
<p className="text-sm font-medium">
|
||||
Admin-only Server Action
|
||||
</p>
|
||||
<Button onClick={onServerActionClick}>
|
||||
Click to test
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default AdminPage
|
||||
14
app/[locale]/(protected)/cabinet/client/page.tsx
Normal file
14
app/[locale]/(protected)/cabinet/client/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
'use client'
|
||||
|
||||
import { UserInfo } from '@/components/cabinet/user-info'
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser'
|
||||
|
||||
const ClientPage = ({ params }: any) => {
|
||||
const user = useCurrentUser()
|
||||
|
||||
return (
|
||||
<UserInfo user={user} label="💻 Client component"/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ClientPage
|
||||
@@ -1,10 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { logout } from '@/actions/logout'
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser'
|
||||
|
||||
const CabinetPage = () => {
|
||||
const user = useCurrentUser()
|
||||
const CabinetPage = ({ params }: any) => {
|
||||
|
||||
const btnOnClick = () => logout()
|
||||
|
||||
return (
|
||||
|
||||
15
app/[locale]/(protected)/cabinet/server/page.tsx
Normal file
15
app/[locale]/(protected)/cabinet/server/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
'use server'
|
||||
|
||||
import { currentUser } from '@/lib/auth'
|
||||
import { UserInfo } from '@/components/cabinet/user-info'
|
||||
|
||||
const ServerPage = async () => {
|
||||
const user = await currentUser()
|
||||
|
||||
return (
|
||||
<UserInfo user={user} label="🗄️ Server component"/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServerPage
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Navbar from '@/app/[locale]/(protected)/_components/navbar'
|
||||
import { Navbar } from '@/app/[locale]/(protected)/_components/navbar'
|
||||
|
||||
interface ProtectedLayoutProps {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -10,7 +10,6 @@ type Props = {
|
||||
const AuthLayout = ({ children }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<Navbar/>
|
||||
<div
|
||||
className="h-full flex items-center justify-center bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-sky-400 to-blue-800">
|
||||
{children}
|
||||
|
||||
@@ -30,7 +30,7 @@ input[aria-invalid='false']:not(:placeholder-shown) {
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary: 200 98% 39%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
@@ -6,6 +6,8 @@ import { lc } from '@/lib/utils'
|
||||
import './globals.css'
|
||||
import { SessionProvider } from 'next-auth/react'
|
||||
import { auth } from '@/config/auth'
|
||||
import Navbar from '@/components/auth/navbar'
|
||||
import { Toaster } from '@/components/ui/sonner'
|
||||
|
||||
const inter = Inter({ subsets: ['cyrillic'] })
|
||||
|
||||
@@ -24,6 +26,8 @@ export default async function RootLayout ({ params: { locale }, children }: Read
|
||||
<html lang={lc(locale)?.java}>
|
||||
<body className={inter.className}>
|
||||
<I18nProviderClient locale={locale} fallback="Loading...">
|
||||
<Navbar/>
|
||||
<Toaster/>
|
||||
{children}
|
||||
</I18nProviderClient>
|
||||
</body>
|
||||
|
||||
10
app/api/admin/route.ts
Normal file
10
app/api/admin/route.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { currentRole } from '@/lib/auth'
|
||||
import { UserRole } from '@prisma/client'
|
||||
|
||||
export async function GET () {
|
||||
const role = await currentRole()
|
||||
const status: number = role === UserRole.ADMIN ? 200 : 403
|
||||
|
||||
return new NextResponse(null, { status })
|
||||
}
|
||||
@@ -15,7 +15,7 @@ export default function robots (): MetadataRoute.Robots {
|
||||
{
|
||||
userAgent: '*',
|
||||
allow: ['/'],
|
||||
disallow: ['/auth/', '/api/'],
|
||||
disallow: ['/auth/', '/api/', '/en/auth/', '/en/api/'],
|
||||
crawlDelay: 3,
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user