Skip to content

Commit 8890cbc

Browse files
authored
Merge pull request #51 from devashish2024/main
configs api, configs updating + deleting, configs adminstration on development server
2 parents c61a5b0 + 9d99bfd commit 8890cbc

23 files changed

+9832
-5656
lines changed

.github/workflows/types.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
node-version: '20'
2626

2727
- name: Install dependencies
28-
run: npm install
28+
run: npm install --legacy-peer-deps
2929

3030
- name: Run ESLint
3131
run: npm run lint --fix

app/api/configs/[id]/route.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import prisma from '@/lib/db';
2+
import { NextRequest, NextResponse } from 'next/server';
3+
4+
// GET /api/configs/[id]
5+
export async function GET(
6+
request: NextRequest,
7+
{ params }: { params: { id: string } },
8+
) {
9+
const { id } = params;
10+
11+
const config = await prisma.config.findUnique({
12+
where: {
13+
id: parseInt(id, 10),
14+
},
15+
select: {
16+
id: true,
17+
title: true,
18+
description: true,
19+
categories: true,
20+
config: true,
21+
user: {
22+
select: {
23+
id: true,
24+
name: true,
25+
avatar: true,
26+
},
27+
},
28+
},
29+
});
30+
31+
if (!config) {
32+
return NextResponse.json({ error: 'Config not found' }, { status: 404 });
33+
}
34+
35+
const response = {
36+
...(typeof config.config === 'object' ? config.config : {}),
37+
id: config.id.toString(),
38+
authorId: config.user.id.toString(),
39+
author: config.user.name,
40+
authorImage: config.user.avatar,
41+
name: config.title,
42+
description: config.description,
43+
categories: config.categories,
44+
};
45+
46+
return NextResponse.json(response);
47+
}

app/api/configs/route.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import prisma from '@/lib/db';
2+
import { NextRequest, NextResponse } from 'next/server';
3+
4+
export async function GET(request: NextRequest) {
5+
try {
6+
const configs = await prisma.config.findMany({
7+
select: {
8+
id: true,
9+
title: true,
10+
description: true,
11+
categories: true,
12+
config: true,
13+
user: {
14+
select: {
15+
id: true,
16+
name: true,
17+
avatar: true,
18+
},
19+
},
20+
},
21+
orderBy: {
22+
createdAt: 'desc',
23+
},
24+
});
25+
26+
const response = configs.map((config) => ({
27+
id: config.id.toString(),
28+
authorId: config.user.id.toString(),
29+
author: config.user.name,
30+
authorImage: config.user.avatar,
31+
name: config.title,
32+
description: config.description,
33+
categories: config.categories,
34+
contentsUrl: `${request.nextUrl.origin}/api/configs/${config.id}`,
35+
}));
36+
37+
return NextResponse.json(response);
38+
} catch (error) {
39+
console.error('Error fetching configs:', error);
40+
return NextResponse.json(
41+
{ error: 'Internal Server Error' },
42+
{ status: 500 },
43+
);
44+
}
45+
}

components/Sections/configs/ConfigCard.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,24 @@
33
import {
44
Card,
55
CardDescription,
6-
CardHeader,
6+
CardContent,
77
CardTitle,
88
} from '@/components/ui/card';
99
import { Badge } from '@/components/ui/badge';
1010
import { Avatar, AvatarImage, AvatarFallback } from '@radix-ui/react-avatar';
1111
import { JsonValue } from '@prisma/client/runtime/library';
12+
import ConfigCardControls from './ConfigCardControls.tsx';
1213

13-
type Config = {
14-
id?: number | string;
14+
export type Config = {
15+
id: number;
1516
title: string;
1617
description: string;
1718
user: any;
1819
categories: string[];
1920
config: JsonValue;
2021
};
2122

22-
export default function ConfigCard({ config }: { config: Config }) {
23+
export default async function ConfigCard({ config }: { config: Config }) {
2324
const handleDownload = () => {
2425
const blob = new Blob([JSON.stringify(config.config, null, 2)], {
2526
type: 'application/json',
@@ -38,11 +39,8 @@ export default function ConfigCard({ config }: { config: Config }) {
3839

3940
return (
4041
<div>
41-
<Card
42-
className="cursor-pointer hover:bg-slate-100/60 transition-colors flex flex-col"
43-
onClick={handleDownload}
44-
>
45-
<CardHeader className="flex-grow space-y-2 p-4">
42+
<Card className="cursor-pointer hover:bg-slate-100/60 transition-colors flex flex-col h-full">
43+
<CardContent className="flex-grow space-y-2 p-4">
4644
<CardTitle className="text-lg">{config.title}</CardTitle>
4745
<div className="flex flex-wrap gap-2 -ml-1">
4846
{config.categories.map((category, index) => (
@@ -78,7 +76,8 @@ export default function ConfigCard({ config }: { config: Config }) {
7876
</>
7977
)}
8078
</div>
81-
</CardHeader>
79+
</CardContent>
80+
<ConfigCardControls downloadAction={handleDownload} config={config} />
8281
</Card>
8382
</div>
8483
);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { CardFooter } from '@/components/ui/card';
2+
import { useSession } from 'next-auth/react';
3+
import UpdateAction from './UpdateActionButton';
4+
import { Config } from './ConfigCard';
5+
import DownloadButton from './DownloadButton';
6+
7+
export default function ConfigCardControls({
8+
config,
9+
downloadAction,
10+
}: {
11+
config: Config;
12+
downloadAction: () => void;
13+
}) {
14+
const { data: session } = useSession();
15+
16+
const userId = config?.user?.id.toString();
17+
const sessionUserId =
18+
(session?.user?.image ?? '').match(/avatars\/(\d+)\//)?.[1] ?? '0';
19+
20+
return (
21+
<CardFooter className="p-0">
22+
<DownloadButton downloadAction={downloadAction} />
23+
{(process.env.NODE_ENV === 'development' || userId === sessionUserId) && (
24+
<UpdateAction config={config} />
25+
)}
26+
</CardFooter>
27+
);
28+
}
Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
1+
import prisma from '@/lib/db';
12
import ConfigsGrid from './ConfigsGrid';
2-
import SkeletonCard from './SkeletonCard';
3-
import { Suspense } from 'react';
43

5-
export default function Configs() {
6-
return (
7-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 auto-rows-fr">
8-
<Suspense fallback={<Fallback />}>
9-
<ConfigsGrid />
10-
</Suspense>
11-
</div>
12-
);
13-
}
4+
export default async function Configs() {
5+
const configs = await prisma.config.findMany({
6+
select: {
7+
id: true,
8+
title: true,
9+
description: true,
10+
categories: true,
11+
config: true,
12+
user: true,
13+
},
14+
orderBy: {
15+
createdAt: 'desc',
16+
},
17+
});
1418

15-
function Fallback() {
16-
return (
17-
<>
18-
{Array(6)
19-
.fill(null)
20-
.map((_, index: number) => (
21-
<SkeletonCard key={index} />
22-
))}
23-
</>
24-
);
19+
return <ConfigsGrid configs={configs} />;
2520
}
Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,95 @@
1+
'use client';
2+
13
import NewConfigCard from './NewConfig';
24
import prisma from '@/lib/db';
35
import ConfigCard from './ConfigCard';
6+
import { Input } from '@/components/ui/input';
7+
import SearchCategoryMenu from './SearchCategoryMenu';
8+
import SkeletonCard from './SkeletonCard';
9+
import { Suspense, useState } from 'react';
10+
import { faXmarkCircle } from '@fortawesome/free-solid-svg-icons';
11+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
412

5-
export default async function ConfigsGrid() {
6-
const configs = await prisma.config.findMany({
7-
select: {
8-
id: true,
9-
title: true,
10-
description: true,
11-
categories: true,
12-
config: true,
13-
user: true,
14-
},
15-
orderBy: {
16-
createdAt: 'desc',
13+
export default function ConfigsGrid({ configs }: { configs: any }) {
14+
const [category, setCategory] = useState('All');
15+
const [filter, setFilter] = useState('');
16+
17+
const updateCategory = (value: string) => {
18+
setCategory(value);
19+
};
20+
21+
const updateFilter = (e: any) => {
22+
setFilter(e.target.value);
23+
};
24+
25+
const filteredConfigs = configs.filter(
26+
({ title, description, categories, user }: any) => {
27+
const matchesCategory =
28+
category === 'All' ||
29+
categories
30+
.map((cate: string) => cate.toLowerCase())
31+
.includes(category.toLowerCase());
32+
const matchesSearchQuery =
33+
title.toLowerCase().includes(filter.toLowerCase()) ||
34+
description.toLowerCase().includes(filter.toLowerCase());
35+
categories
36+
.map((cat: string) => cat.toLowerCase())
37+
.includes(filter.toLowerCase());
38+
return matchesCategory && matchesSearchQuery;
1739
},
18-
});
40+
);
1941

2042
return (
21-
<>
22-
<NewConfigCard />
43+
<div>
44+
<div className="flex mb-4 gap-4">
45+
<SearchCategoryMenu value={category} onChange={updateCategory} />
46+
<Input
47+
type="text"
48+
placeholder="Filter Configs"
49+
value={filter}
50+
onChange={updateFilter}
51+
/>
52+
</div>
53+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 auto-rows-fr">
54+
<Suspense fallback={<Fallback />}>
55+
{filteredConfigs.length === 0 ? (
56+
<div className="flex flex-col col-span-full text-center py-10 gap-2">
57+
<FontAwesomeIcon
58+
icon={faXmarkCircle}
59+
className="size-8 text-black left-0 right-0 mx-auto"
60+
/>
61+
<h2 className="text-2xl font-semibold text-gray-600 font-sans tracking-tight">
62+
No scripts found
63+
</h2>
64+
<div className="flex justify-center">
65+
<p className="text-gray-500 text-center max-w-4xl">
66+
Try adjusting your search or filters to find what you&apos;re
67+
looking for.
68+
</p>
69+
</div>
70+
</div>
71+
) : (
72+
<>
73+
<NewConfigCard />
74+
{filteredConfigs.map((config: any) => (
75+
<ConfigCard key={config.id} config={config} />
76+
))}
77+
</>
78+
)}
79+
</Suspense>
80+
</div>
81+
</div>
82+
);
83+
}
2384

24-
{configs.map((config) => (
25-
<ConfigCard key={config.id} config={config} />
26-
))}
85+
function Fallback() {
86+
return (
87+
<>
88+
{Array(6)
89+
.fill(null)
90+
.map((_, index: number) => (
91+
<SkeletonCard key={index} />
92+
))}
2793
</>
2894
);
2995
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Button } from '@/components/ui/button';
2+
import { deleteConfig } from './DeleteServerAction';
3+
4+
const DeleteAction = ({ configId }: { configId: number }) => {
5+
return (
6+
<>
7+
<Button
8+
variant="destructive"
9+
onClick={() => deleteConfig(configId)}
10+
className="w-full m-0 rounded-tl-none rounded-br-none rounded-tr-none hover:opacity-80 transition-all duration-100"
11+
>
12+
Delete
13+
</Button>
14+
</>
15+
);
16+
};
17+
18+
export default DeleteAction;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use server';
2+
3+
import prisma from '@/lib/db';
4+
import { revalidatePath } from 'next/cache';
5+
6+
type ConfigID = number;
7+
8+
export async function deleteConfig(configId: ConfigID) {
9+
if (!configId) {
10+
throw new Error('Config ID is required');
11+
}
12+
13+
try {
14+
await prisma.config.delete({
15+
where: {
16+
id: configId,
17+
},
18+
});
19+
20+
revalidatePath('/configs');
21+
22+
return true;
23+
} catch (error) {
24+
console.error('Error deleting config:', error);
25+
return false;
26+
}
27+
}

0 commit comments

Comments
 (0)