Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: re #38

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ yarn-error.log*
.pnpm-debug.log*

# local env files
.env
.env*.local
.env*.example

Expand Down
Binary file modified bun.lockb
Binary file not shown.
21 changes: 20 additions & 1 deletion next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,26 @@ const nextConfig = {
buildActivityPosition: "bottom-right",
},
images: {
domains: ["images.unsplash.com", "source.unsplash.com", "picsum.photos"],
remotePatterns: [
{
protocol: "https",
hostname: "source.unsplash.com",
port: "",
pathname: "/**",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🤔 Hold up! Do we really need that /** pathway wildcard?

Yo dawg, I heard you like wildcards, but /** is like giving everyone VIP access to the club! Consider tightening those pathname patterns if you're only using specific paths. For example, if you're only grabbing images from /photos/, lock it down!

Here's a spicier way to do it:

-        pathname: "/**",
+        pathname: "/photos/*", // adjust based on your actual usage

Do this for each pattern if you know the specific paths you're using. Remember: in security, less access = more awesome! 🔒

Also applies to: 21-21, 27-27

},
{
protocol: "https",
hostname: "images.unsplash.com",
port: "",
pathname: "/**",
},
{
protocol: "https",
hostname: "picsum.photos",
port: "",
pathname: "/**",
},
],
},
// redirect / to login
async redirects() {
Expand Down
101 changes: 51 additions & 50 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,74 +10,75 @@
},
"dependencies": {
"@badrap/bar-of-progress": "^0.2.2",
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-aspect-ratio": "^1.0.3",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.0.7",
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-aspect-ratio": "^1.1.0",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-menubar": "^1.0.4",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@tabler/icons-react": "^2.28.0",
"@tanstack/react-table": "^8.9.3",
"autoprefixer": "^10.4.14",
"axios": "^1.4.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slider": "^1.2.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@tabler/icons-react": "^2.47.0",
"@tanstack/react-table": "^8.20.5",
"autoprefixer": "^10.4.20",
"axios": "^1.7.5",
"caniuse-lite": "^1.0.30001653",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "^0.2.0",
"crypto-js": "^4.1.1",
"date-fns": "^3.3.1",
"embla-carousel-react": "^8.0.0-rc21",
"filepond": "^4.30.4",
"clsx": "^2.1.1",
"cmdk": "^0.2.1",
"crypto-js": "^4.2.0",
"date-fns": "^3.6.0",
"embla-carousel-react": "^8.2.0",
"filepond": "^4.31.2",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.241.0",
"nanoid": "^4.0.2",
"next": "^14.0",
"next-auth": "^4.22.3",
"next": "^14.2.7",
"next-auth": "^4.24.7",
"next-themes": "^0.2.1",
"postcss": "^8.4.27",
"postcss": "^8.4.41",
"react": "18.2.0",
"react-day-picker": "^8.10.0",
"react-day-picker": "^8.10.1",
"react-dom": "18.2.0",
"react-filepond": "^7.1.2",
"react-hook-form": "^7.49.3",
"react-hook-form": "^7.53.0",
"react-resizable-panels": "^1.0.10",
"sass": "^1.64.1",
"sonner": "^1.4.0",
"tailwind-merge": "^2.2.1",
"tailwindcss": "^3.3.3",
"sass": "^1.77.8",
"sonner": "^1.5.0",
"tailwind-merge": "^2.5.2",
"tailwindcss": "^3.4.10",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.8.9",
"zod": "^3.22.4",
"zustand": "^4.3.9"
"zod": "^3.23.8",
"zustand": "^4.5.5"
},
"devDependencies": {
"@types/node": "18.11.18",
"@types/react": "18.0.27",
"@types/react-dom": "18.0.10",
"eslint": "8.44.0",
"eslint-config-next": "^14.0",
"eslint-config-next": "^14.2.7",
"typescript": "5.1.3"
}
}
44 changes: 44 additions & 0 deletions src/app/(authenticated)/dashboard/components/analytics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";

import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { api } from "@/lib/api";
import { useEffect, useState } from "react";

type Analytics = {
name: string;
value: number;
};

async function getAnalytics() {
const response = await api.get<Analytics[]>("/analytics");
return response.data;
}
Comment on lines +12 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🚨 Houston, we need error handling!

The API call is looking sus without any error handling. What happens when the server is taking a coffee break?

 async function getAnalytics() {
+  try {
     const response = await api.get<Analytics[]>("/analytics");
     return response.data;
+  } catch (error) {
+    console.error('Failed to fetch analytics:', error);
+    return [];
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function getAnalytics() {
const response = await api.get<Analytics[]>("/analytics");
return response.data;
}
async function getAnalytics() {
try {
const response = await api.get<Analytics[]>("/analytics");
return response.data;
} catch (error) {
console.error('Failed to fetch analytics:', error);
return [];
}
}


export default function AnalyticsCard() {
const [analitycs, setAnalitycs] = useState<Analytics[]>([]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🐛 Yo dawg, I heard you like typos!

There's a spicy typo in your state variable name. "analitycs" should be "analytics" - unless you're trying to start a new naming trend!

-  const [analitycs, setAnalitycs] = useState<Analytics[]>([]);
+  const [analytics, setAnalytics] = useState<Analytics[]>([]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [analitycs, setAnalitycs] = useState<Analytics[]>([]);
const [analytics, setAnalytics] = useState<Analytics[]>([]);


useEffect(() => {
getAnalytics().then((data) => setAnalitycs(data));
}, [analitycs]);
Comment on lines +20 to +22
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🔄 Infinite loop alert! Time to fix this dependency array!

You've created the infinite loop of doom! The useEffect depends on analitycs which triggers a re-fetch every time the data changes. It's like trying to catch your own tail!

 useEffect(() => {
   getAnalytics().then((data) => setAnalitycs(data));
-}, [analitycs]);
+}, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
getAnalytics().then((data) => setAnalitycs(data));
}, [analitycs]);
useEffect(() => {
getAnalytics().then((data) => setAnalitycs(data));
}, []);


return (
<div className='gap-4 grid md:grid-cols-2 lg:grid-cols-4 mt-5'>
{analitycs.map((item) => (
<Card
key={item.name}
className='shadow-md hover:scale-105 duration-500 cursor-pointer'>
<CardHeader className='flex flex-row justify-between items-center space-y-0 pb-2'>
<CardTitle className='font-medium text-sm'>{item.name}</CardTitle>
{/* <DollarSign className="w-4 h-4 text-muted-foreground" /> */}
</CardHeader>
<CardContent>
<div className='font-bold text-2xl'>{item.value}</div>
{/* <p className="text-muted-foreground text-xs">
jumlah keseluruhan mapel
</p> */}
</CardContent>
</Card>
))}
</div>
Comment on lines +24 to +42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

💅 Sweet responsive grid setup, but let's add loading states!

The grid layout is fire, but users might see a blank screen while data loads. Let's add some loading states to keep it 💯

 export default function AnalyticsCard() {
   const [analytics, setAnalytics] = useState<Analytics[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
 
   useEffect(() => {
-    getAnalytics().then((data) => setAnalytics(data));
+    getAnalytics()
+      .then((data) => setAnalytics(data))
+      .finally(() => setIsLoading(false));
   }, []);
 
   return (
     <div className='gap-4 grid md:grid-cols-2 lg:grid-cols-4 mt-5'>
+      {isLoading && Array(4).fill(0).map((_, i) => (
+        <Card key={i} className="animate-pulse">
+          <CardHeader className="h-8 bg-gray-200 rounded" />
+          <CardContent className="h-12 bg-gray-200 rounded" />
+        </Card>
+      ))}
-      {analytics.map((item) => (
+      {!isLoading && analytics.map((item) => (

Committable suggestion skipped: line range outside the PR's diff.

);
}
47 changes: 9 additions & 38 deletions src/app/(authenticated)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { Metadata } from "next";
import ModulList from "../modul/components/modul-list";
import { api } from "@/lib/api";
import AnalyticsCard from "./components/analytics";

// revalidate every 5 seconds
export const revalidate = 5;
Expand All @@ -12,53 +11,25 @@ export const metadata: Metadata = {
description: "Halaman utama",
};

async function analytics() {
const response = await api.get("/analytics");
return response.data;
}

export default async function Page() {
const analitycs = await analytics();

// console.log(analitycs);

return (
<div className='h-full flex flex-col space-y-5'>
<div className='flex flex-col space-y-5 h-full'>
<div className='flex flex-col h-full'>
<div className='flex items-center justify-between'>
<div className='flex justify-between items-center'>
<div className='space-y-1'>
<h2 className='text-2xl font-semibold tracking-tight'>Dashboard</h2>
<p className='text-sm text-muted-foreground'>
<h2 className='font-semibold text-2xl tracking-tight'>Dashboard</h2>
<p className='text-muted-foreground text-sm'>
Selamat datang kembali
</p>
</div>
</div>

<div className='mt-5 grid gap-4 md:grid-cols-2 lg:grid-cols-4'>
{analitycs.map((item) => (
<Card
key={item.name}
className='shadow-md hover:scale-105 duration-500 cursor-pointer'>
<CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
<CardTitle className='text-sm font-medium'>
{item.name}
</CardTitle>
{/* <DollarSign className="h-4 w-4 text-muted-foreground" /> */}
</CardHeader>
<CardContent>
<div className='text-2xl font-bold'>{item.value}</div>
{/* <p className="text-xs text-muted-foreground">
jumlah keseluruhan mapel
</p> */}
</CardContent>
</Card>
))}
</div>
<AnalyticsCard />
Comment on lines +16 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Holy flexbox, Batman! Let's talk about this layout structure! 🚀

The nested flex containers are looking clean AF, but here's a pro-tip: we could make this even more maintainable with CSS Grid for the analytics section.

Try this absolute banger of a layout:

-    <div className='flex flex-col space-y-5 h-full'>
-      <div className='flex flex-col h-full'>
+    <div className='grid grid-rows-[auto_1fr] gap-5 h-full'>
+      <div className='grid gap-5'>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className='flex flex-col space-y-5 h-full'>
<div className='flex flex-col h-full'>
<div className='flex items-center justify-between'>
<div className='flex justify-between items-center'>
<div className='space-y-1'>
<h2 className='text-2xl font-semibold tracking-tight'>Dashboard</h2>
<p className='text-sm text-muted-foreground'>
<h2 className='font-semibold text-2xl tracking-tight'>Dashboard</h2>
<p className='text-muted-foreground text-sm'>
Selamat datang kembali
</p>
</div>
</div>
<div className='mt-5 grid gap-4 md:grid-cols-2 lg:grid-cols-4'>
{analitycs.map((item) => (
<Card
key={item.name}
className='shadow-md hover:scale-105 duration-500 cursor-pointer'>
<CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
<CardTitle className='text-sm font-medium'>
{item.name}
</CardTitle>
{/* <DollarSign className="h-4 w-4 text-muted-foreground" /> */}
</CardHeader>
<CardContent>
<div className='text-2xl font-bold'>{item.value}</div>
{/* <p className="text-xs text-muted-foreground">
jumlah keseluruhan mapel
</p> */}
</CardContent>
</Card>
))}
</div>
<AnalyticsCard />
<div className='grid grid-rows-[auto_1fr] gap-5 h-full'>
<div className='grid gap-5'>
<div className='flex justify-between items-center'>
<div className='space-y-1'>
<h2 className='font-semibold text-2xl tracking-tight'>Dashboard</h2>
<p className='text-muted-foreground text-sm'>
Selamat datang kembali
</p>
</div>
</div>
<AnalyticsCard />


<div className='mt-5'>
<div className='mt-6 space-y-1'>
<h2 className='text-2xl font-semibold tracking-tight'>Modul</h2>
<p className='text-sm text-muted-foreground'>
<div className='space-y-1 mt-6'>
<h2 className='font-semibold text-2xl tracking-tight'>Modul</h2>
<p className='text-muted-foreground text-sm'>
Modul yang kamu kelola
</p>
</div>
Expand Down
26 changes: 13 additions & 13 deletions src/app/(authenticated)/modul/components/dialog-modul.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function DialogModul() {
const [open, setOpen] = useState(false);
const [selected, setSelected] = useState({
rombongan_belajar_id: null,
teacher_id: null,
// teacher_id: null,
mapel_id: null,
});

Expand All @@ -45,7 +45,7 @@ export default function DialogModul() {
// reset selected
setSelected({
rombongan_belajar_id: null,
teacher_id: null,
// teacher_id: null,
mapel_id: null,
});
}
Expand All @@ -56,8 +56,8 @@ export default function DialogModul() {
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className="flex items-center">
<IconPlus className="w-5 h-5 mr-2" />
<Button className='flex items-center'>
<IconPlus className='mr-2 w-5 h-5' />
Comment on lines +59 to +60
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🎨 Spicy UI Update: Button Glow Up!

Looking clean with that flex layout! The IconPlus with margin is chef's kiss - but let's make it even MORE AWESOME with some hover effects!

- <Button className='flex items-center'>
+ <Button className='flex items-center hover:scale-105 transition-transform'>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button className='flex items-center'>
<IconPlus className='mr-2 w-5 h-5' />
<Button className='flex items-center hover:scale-105 transition-transform'>
<IconPlus className='mr-2 w-5 h-5' />

Buat Modul Baru
</Button>
</DialogTrigger>
Expand All @@ -67,23 +67,23 @@ export default function DialogModul() {
</DialogHeader>

{/* form to use to create new modul */}
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<label htmlFor="name">Pilih Rombel</label>
<div className='gap-4 grid py-4'>
<div className='gap-2 grid'>
<label htmlFor='name'>Pilih Rombel</label>
Comment on lines +70 to +72
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🎯 Grid Layout: The Holy Trinity of Spacing!

Nice grid game! But hold up - those nested gap-2 grid classes are getting repetitive faster than a TikTok trend. Let's DRY this up!

- <div className='gap-4 grid py-4'>
-   <div className='gap-2 grid'>
+ <div className='grid gap-4 py-4'>
+   <div className='grid gap-2'>

Also applies to: 85-86

<SelectRombel
onSelected={(e) =>
setSelected((s) => ({ ...s, rombongan_belajar_id: e }))
}
/>
</div>
<div className="grid gap-2">
<label htmlFor="name">Pilih Guru</label>
{/* <div className='gap-2 grid'>
<label htmlFor='name'>Pilih Guru</label>
<SelectTeacher
onSelected={(e) => setSelected((s) => ({ ...s, teacher_id: e }))}
/>
</div>
<div className="grid gap-2">
<label htmlFor="name">Pilih Mapel</label>
</div> */}
<div className='gap-2 grid'>
<label htmlFor='name'>Pilih Mapel</label>
<SelectMapel
onSelected={(e) => setSelected((s) => ({ ...s, mapel_id: e }))}
/>
Expand All @@ -92,7 +92,7 @@ export default function DialogModul() {
{/* button submit */}
<DialogFooter>
<Button disabled={isLoading} onClick={handleSave}>
{isLoading && <Loader className="mr-2" size={16} />}
{isLoading && <Loader className='mr-2' size={16} />}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

✨ Loading State: Micro-interaction Magic!

That loading indicator is smooth like butter! But let's make it SPIN like a DJ at a React conference!

- <Loader className='mr-2' size={16} />
+ <Loader className='mr-2 animate-spin' size={16} />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{isLoading && <Loader className='mr-2' size={16} />}
{isLoading && <Loader className='mr-2 animate-spin' size={16} />}

Simpan
</Button>
<Button variant={`ghost`} onClick={() => setOpen(false)}>
Expand Down
12 changes: 6 additions & 6 deletions src/app/(authenticated)/modul/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ export const metadata: Metadata = {

export default async function Page() {
return (
<div className='h-full flex flex-col space-y-5'>
<div className='flex flex-col space-y-5 h-full'>
<div className='flex flex-col h-full'>
<div className='flex flex-col md:flex-row justify-between'>
<div className='mt-5 space-y-1'>
<h2 className='text-2xl font-semibold tracking-tight'>Modul</h2>
<p className='text-sm text-muted-foreground'>
<div className='flex md:flex-row flex-col justify-between'>
<div className='space-y-1 mt-5'>
<h2 className='font-semibold text-2xl tracking-tight'>Modul</h2>
<p className='text-muted-foreground text-sm'>
{/* list of all modul available */}
Daftar semua modul yang telah dibuat
</p>
</div>
{/* dialog new modul */}
{(await isWakaKurikulum()) && <DialogModul />}
<DialogModul />
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

🔥 HOLD UP! We've got a security gap faster than npm installing node_modules!

The investigation reveals we've got role checks scattered across the codebase (isWakaKurikulum) but they're not being used in the DialogModul component anymore. While we do have:

  • ✅ Global auth middleware protecting all routes
  • ✅ Session checks in the [uuid] page
  • ❌ But NO role checks in the dialog component itself

This is like leaving your house keys under the doormat - technically there's a lock, but anyone who gets in can access everything!

• Add back the role check in src/app/(authenticated)/modul/page.tsx or move it into DialogModul component
• Other components like modul-list.tsx and modul-card.tsx still use role checks, so this dialog should too!

🔗 Analysis chain

🚨 Hold up! We need to verify those access controls!

The role check with isWakaKurikulum() got yeeted out faster than a JavaScript framework becomes obsolete! Let's make sure we're not accidentally giving everyone admin powers here.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Let's hunt down where the access controls went

# Check if DialogModul has internal role checks
echo "🔍 Checking DialogModul component for role checks..."
ast-grep --pattern 'isWakaKurikulum|role|permission' src/app/\(authenticated\)/modul/components/dialog-modul.tsx

# Look for any auth middleware
echo "🔍 Checking for auth middleware..."
fd middleware src/app/\(authenticated\) --exec rg -l 'auth|role|permission'

Length of output: 386


Script:

#!/bin/bash
# Let's do a more thorough investigation 🕵️

# First, let's see what's actually in DialogModul
echo "🔍 Show me the DialogModul contents..."
rg -A 5 "export.*DialogModul" src/app/\(authenticated\)/modul/components/dialog-modul.tsx

# Check for any auth hooks or context usage
echo "🔍 Looking for auth hooks..."
rg -l "useAuth|getSession|useSession" src/app/\(authenticated\)/modul/components/dialog-modul.tsx

# Check the authenticated layout for role protection
echo "🔍 Checking authenticated layout..."
rg -l "auth|role|permission" src/app/\(authenticated\)/layout.tsx

# Let's see if there's any route protection
echo "🔍 Checking route protection..."
fd "route|page" src/app/\(authenticated\)/modul --exec rg -l "auth|role|permission" {}

Length of output: 975


Script:

#!/bin/bash
# Time for the final boss battle! 🔥

# Let's see what's in those protected pages
echo "🔍 Checking modul pages for role checks..."
rg -B 2 -A 5 "auth|role|permission" src/app/\(authenticated\)/modul/page.tsx src/app/\(authenticated\)/modul/\[uuid\]/page.tsx

# Let's check if there's any middleware protecting this route
echo "🔍 Looking for middleware in action..."
fd middleware --exec cat {} \; 

# One last check for any role-based hooks in the auth system
echo "🔍 Scanning for role-based auth hooks..."
rg -l "isWakaKurikulum|useRole|getRole" src/app/\(authenticated\)

Length of output: 4032

</div>
<Separator className='my-4' />
{/* */}
Expand Down
21 changes: 21 additions & 0 deletions src/app/api/client/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { authOptions } from "@/lib/auth";
import { getServerSession } from "next-auth/next";
Comment on lines +1 to +2
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🔥 Clean imports, but let's make them BULLETPROOF!

The imports look clean AF, but we should probably add some error handling around that getServerSession call. Trust me, you don't want that bad boy failing silently in production!

 import { authOptions } from "@/lib/auth";
 import { getServerSession } from "next-auth/next";
+import { NextResponse } from "next/server";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { authOptions } from "@/lib/auth";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/auth";
import { getServerSession } from "next-auth/next";
import { NextResponse } from "next/server";
🧰 Tools
🪛 eslint

[error] 1-undefined: Parsing error: The keyword 'import' is reserved


/**
* API route to return the user session if authenticated.
*
* If the request is not authenticated, return a 401 status code without any response body.
*/
export async function GET(request: Request) {
const session = await getServerSession(authOptions);

if (session) {
return new Response(JSON.stringify(session.user?.access_token), {
status: 200,
});
} else {
return new Response(null, {
status: 401,
});
}
}
Comment on lines +9 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

⚠️ Potential issue

🔥 Level Up Your API Route with These Epic Security Features!

Listen up fam, after diving deep into the codebase like a speedrunner, here's the 200 IQ take: The suggested changes are absolutely CRACKED!

We're currently the only API route in town handling sensitive data, and we're running naked without proper security headers or structured error responses. The suggested implementation is like adding a shield potion and proper armor to our route - it's a straight upgrade that:

  • Adds try-catch for that sweet error handling (because errors in prod are NOT poggers)
  • Implements proper security headers (no-store = no cache exploits)
  • Structures the response like a pro (JSON all the way)
  • Uses NextResponse for that consistent Next.js drip

This is definitely not a false positive - it's a legitimate security enhancement that makes our API route production-ready. Time to smash that merge button!

🔗 Analysis chain

⚡ Hold up! Let's make this route BULLETPROOF!

The route works, but it's missing some CRUCIAL security headers and error handling! Let's make it production-ready!

Here's a spicier implementation:

 export async function GET(request: Request) {
-  const session = await getServerSession(authOptions);
+  try {
+    const session = await getServerSession(authOptions);
+    
+    if (!session) {
+      return NextResponse.json(
+        { error: 'Unauthorized' },
+        { 
+          status: 401,
+          headers: {
+            'Content-Type': 'application/json',
+            'Cache-Control': 'no-store',
+          }
+        }
+      );
+    }
 
-  if (session) {
-    return new Response(JSON.stringify(session.user?.access_token), {
-      status: 200,
-    });
-  } else {
-    return new Response(null, {
-      status: 401,
-    });
+    return NextResponse.json(
+      { access_token: session.user?.access_token },
+      { 
+        status: 200,
+        headers: {
+          'Content-Type': 'application/json',
+          'Cache-Control': 'no-store',
+        }
+      }
+    );
+  } catch (error) {
+    console.error('Session error:', error);
+    return NextResponse.json(
+      { error: 'Internal server error' },
+      { status: 500 }
+    );
   }
 }

This bad boy adds:

  • Proper error handling 🛠️
  • Security headers 🔒
  • Better response structure 📦
  • No-cache policy 🚫

The previous search was too specific. Let's cast a wider net to see how other routes are handling responses and if we need to maintain consistency across the codebase. This will help us verify if the suggested changes align with the existing patterns or if we're introducing a new, better pattern that should be applied across all routes.


Alright gamers, let's do one final check to see how the auth route handles its responses and if NextResponse is already a thing in our codebase. This will help us make a 200 IQ decision about whether our suggested changes are actually a pro gamer move or if we're trying to reinvent the wheel. 🎮

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Let's check if we're handling errors consistently across other routes
rg -l "NextResponse.json.*error" "src/app/api"

Length of output: 46


Script:

#!/bin/bash
# Let's check all API routes and their error handling patterns more broadly
rg -A 5 "NextResponse|Response" "src/app/api"

# Also check for any existing security headers
rg -A 2 "headers.*:" "src/app/api"

# And let's see what other route files we have
fd "route.ts" "src/app/api"

Length of output: 599


Script:

#!/bin/bash
# Let's check what the auth route is doing for error handling
rg -A 10 "export.*function" "src/app/api/auth/[...nextauth]/route.ts"

# And see if we're using NextResponse anywhere else in the codebase
# This helps ensure we're consistent with our response patterns
rg -A 5 "import.*NextResponse" "src/app"

Length of output: 112

Loading