Skip to content

configs api, configs updating + deleting, configs adminstration on development server #51

New issue

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

By clicking “No 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? No Sign in to your account

Open
wants to merge 8 commits 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
47 changes: 47 additions & 0 deletions app/api/configs/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import prisma from '@/lib/db';
import { NextRequest, NextResponse } from 'next/server';

// GET /api/configs/[id]
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } },
) {
const { id } = params;

const config = await prisma.config.findUnique({
where: {
id: parseInt(id, 10),
},
select: {
id: true,
title: true,
description: true,
categories: true,
config: true,
user: {
select: {
id: true,
name: true,
avatar: true,
},
},
},
});

if (!config) {
return NextResponse.json({ error: 'Config not found' }, { status: 404 });
}

const response = {
...(typeof config.config === 'object' ? config.config : {}),
id: config.id.toString(),
authorId: config.user.id.toString(),
author: config.user.name,
authorImage: config.user.avatar,
name: config.title,
description: config.description,
categories: config.categories,
};

return NextResponse.json(response);
}
45 changes: 45 additions & 0 deletions app/api/configs/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import prisma from '@/lib/db';
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
try {
const configs = await prisma.config.findMany({
select: {
id: true,
title: true,
description: true,
categories: true,
config: true,
user: {
select: {
id: true,
name: true,
avatar: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
});

const response = configs.map((config) => ({
id: config.id.toString(),
authorId: config.user.id.toString(),
author: config.user.name,
authorImage: config.user.avatar,
name: config.title,
description: config.description,
categories: config.categories,
contentsUrl: `${request.nextUrl.origin}/api/configs/${config.id}`,
}));

return NextResponse.json(response);
} catch (error) {
console.error('Error fetching configs:', error);
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 },
);
}
}
19 changes: 9 additions & 10 deletions components/Sections/configs/ConfigCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@
import {
Card,
CardDescription,
CardHeader,
CardContent,
CardTitle,
} from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Avatar, AvatarImage, AvatarFallback } from '@radix-ui/react-avatar';
import { JsonValue } from '@prisma/client/runtime/library';
import ConfigCardControls from './ConfigCardControls.tsx';

type Config = {
id?: number | string;
export type Config = {
id: number;
title: string;
description: string;
user: any;
categories: string[];
config: JsonValue;
};

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

return (
<div>
<Card
className="cursor-pointer hover:bg-slate-100/60 transition-colors flex flex-col"
onClick={handleDownload}
>
<CardHeader className="flex-grow space-y-2 p-4">
<Card className="cursor-pointer hover:bg-slate-100/60 transition-colors flex flex-col h-full">
<CardContent className="flex-grow space-y-2 p-4">
<CardTitle className="text-lg">{config.title}</CardTitle>
<div className="flex flex-wrap gap-2 -ml-1">
{config.categories.map((category, index) => (
Expand Down Expand Up @@ -78,7 +76,8 @@ export default function ConfigCard({ config }: { config: Config }) {
</>
)}
</div>
</CardHeader>
</CardContent>
<ConfigCardControls downloadAction={handleDownload} config={config} />
</Card>
</div>
);
Expand Down
28 changes: 28 additions & 0 deletions components/Sections/configs/ConfigCardControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { CardFooter } from '@/components/ui/card';
import { useSession } from 'next-auth/react';
import UpdateAction from './UpdateActionButton';
import { Config } from './ConfigCard';
import DownloadButton from './DownloadButton';

export default function ConfigCardControls({
config,
downloadAction,
}: {
config: Config;
downloadAction: () => void;
}) {
const { data: session } = useSession();

const userId = config?.user?.id.toString();
const sessionUserId =
(session?.user?.image ?? '').match(/avatars\/(\d+)\//)?.[1] ?? '0';

return (
<CardFooter className="p-0">
<DownloadButton downloadAction={downloadAction} />
{(process.env.NODE_ENV === 'development' || userId === sessionUserId) && (
<UpdateAction config={config} />
)}
</CardFooter>
);
}
37 changes: 16 additions & 21 deletions components/Sections/configs/Configs.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
import prisma from '@/lib/db';
import ConfigsGrid from './ConfigsGrid';
import SkeletonCard from './SkeletonCard';
import { Suspense } from 'react';

export default function Configs() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 auto-rows-fr">
<Suspense fallback={<Fallback />}>
<ConfigsGrid />
</Suspense>
</div>
);
}
export default async function Configs() {
const configs = await prisma.config.findMany({
select: {
id: true,
title: true,
description: true,
categories: true,
config: true,
user: true,
},
orderBy: {
createdAt: 'desc',
},
});

function Fallback() {
return (
<>
{Array(6)
.fill(null)
.map((_, index: number) => (
<SkeletonCard key={index} />
))}
</>
);
return <ConfigsGrid configs={configs} />;
}
102 changes: 84 additions & 18 deletions components/Sections/configs/ConfigsGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,95 @@
'use client';

import NewConfigCard from './NewConfig';
import prisma from '@/lib/db';
import ConfigCard from './ConfigCard';
import { Input } from '@/components/ui/input';
import SearchCategoryMenu from './SearchCategoryMenu';
import SkeletonCard from './SkeletonCard';
import { Suspense, useState } from 'react';
import { faXmarkCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

export default async function ConfigsGrid() {
const configs = await prisma.config.findMany({
select: {
id: true,
title: true,
description: true,
categories: true,
config: true,
user: true,
},
orderBy: {
createdAt: 'desc',
export default function ConfigsGrid({ configs }: { configs: any }) {
const [category, setCategory] = useState('All');
const [filter, setFilter] = useState('');

const updateCategory = (value: string) => {
setCategory(value);
};

const updateFilter = (e: any) => {
setFilter(e.target.value);
};

const filteredConfigs = configs.filter(
({ title, description, categories, user }: any) => {
const matchesCategory =
category === 'All' ||
categories
.map((cate: string) => cate.toLowerCase())
.includes(category.toLowerCase());
const matchesSearchQuery =
title.toLowerCase().includes(filter.toLowerCase()) ||
description.toLowerCase().includes(filter.toLowerCase());
categories
.map((cat: string) => cat.toLowerCase())
.includes(filter.toLowerCase());
return matchesCategory && matchesSearchQuery;
},
});
);

return (
<>
<NewConfigCard />
<div>
<div className="flex mb-4 gap-4">
<SearchCategoryMenu value={category} onChange={updateCategory} />
<Input
type="text"
placeholder="Filter Configs"
value={filter}
onChange={updateFilter}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 auto-rows-fr">
<Suspense fallback={<Fallback />}>
{filteredConfigs.length === 0 ? (
<div className="flex flex-col col-span-full text-center py-10 gap-2">
<FontAwesomeIcon
icon={faXmarkCircle}
className="size-8 text-black left-0 right-0 mx-auto"
/>
<h2 className="text-2xl font-semibold text-gray-600 font-sans tracking-tight">
No scripts found
</h2>
<div className="flex justify-center">
<p className="text-gray-500 text-center max-w-4xl">
Try adjusting your search or filters to find what you&apos;re
looking for.
</p>
</div>
</div>
) : (
<>
<NewConfigCard />
{filteredConfigs.map((config: any) => (
<ConfigCard key={config.id} config={config} />
))}
</>
)}
</Suspense>
</div>
</div>
);
}

{configs.map((config) => (
<ConfigCard key={config.id} config={config} />
))}
function Fallback() {
return (
<>
{Array(6)
.fill(null)
.map((_, index: number) => (
<SkeletonCard key={index} />
))}
</>
);
}
18 changes: 18 additions & 0 deletions components/Sections/configs/DeleteAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Button } from '@/components/ui/button';
import { deleteConfig } from './DeleteServerAction';

const DeleteAction = ({ configId }: { configId: number }) => {
return (
<>
<Button
variant="destructive"
onClick={() => deleteConfig(configId)}
className="w-full m-0 rounded-tl-none rounded-br-none rounded-tr-none hover:opacity-80 transition-all duration-100"
>
Delete
</Button>
</>
);
};

export default DeleteAction;
27 changes: 27 additions & 0 deletions components/Sections/configs/DeleteServerAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use server';

import prisma from '@/lib/db';
import { revalidatePath } from 'next/cache';

type ConfigID = number;

export async function deleteConfig(configId: ConfigID) {
if (!configId) {
throw new Error('Config ID is required');
}

try {
await prisma.config.delete({
where: {
id: configId,
},
});

revalidatePath('/configs');

return true;
} catch (error) {
console.error('Error deleting config:', error);
return false;
}
}
18 changes: 18 additions & 0 deletions components/Sections/configs/DownloadButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use client';

import { Button } from '@/components/ui/button';

export default function DownloadButton({
downloadAction,
}: {
downloadAction: () => void;
}) {
return (
<Button
onClick={downloadAction}
className="w-full m-0 rounded-tl-none rounded-tr-none hover:opacity-80 transition-all duration-100 bg-[#2b65ca] text-white hover:bg-[#2359b6] rounded-bl-none rounded-br-none"
>
Download
</Button>
);
}
Loading