Spaces:
Runtime error
Runtime error
add admin rule
Browse files- app/api/collections/[id]/route.ts +7 -1
- app/api/collections/[id]/visibility/route.ts +55 -0
- app/api/collections/route.ts +12 -0
- app/api/login/route.ts +1 -1
- app/api/me/route.ts +7 -1
- app/api/route.ts +1 -1
- components/button/index.tsx +15 -2
- components/main/collections/collection.tsx +15 -3
- components/main/hooks/useCollections.ts +9 -3
- components/modal/modal.tsx +27 -9
- components/modal/useCollection.ts +63 -5
- prisma/schema.prisma +7 -6
- utils/type.ts +1 -0
app/api/collections/[id]/route.ts
CHANGED
@@ -7,7 +7,13 @@ const prisma = new PrismaClient()
|
|
7 |
export async function DELETE(request: Request, { params }: { params: { id: string } }) {
|
8 |
const { headers } = request
|
9 |
|
10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
return Response.json(
|
12 |
{
|
13 |
status: 401,
|
|
|
7 |
export async function DELETE(request: Request, { params }: { params: { id: string } }) {
|
8 |
const { headers } = request
|
9 |
|
10 |
+
const staff_flag_id = headers.get('x-staff-flag-id') ?? undefined
|
11 |
+
|
12 |
+
// @ts-ignore
|
13 |
+
const HF_ADMIN = process?.env?.HF_ADMIN.split(',') ?? []
|
14 |
+
const is_admin = staff_flag_id ? HF_ADMIN.includes(staff_flag_id) : false
|
15 |
+
|
16 |
+
if (!is_admin) {
|
17 |
return Response.json(
|
18 |
{
|
19 |
status: 401,
|
app/api/collections/[id]/visibility/route.ts
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { PrismaClient } from '@prisma/client'
|
2 |
+
|
3 |
+
const prisma = new PrismaClient()
|
4 |
+
|
5 |
+
export async function PUT(request: Request, { params }: { params: { id: string } }) {
|
6 |
+
const { headers } = request
|
7 |
+
|
8 |
+
const staff_flag_id = headers.get('x-staff-flag-id') ?? undefined
|
9 |
+
|
10 |
+
// @ts-ignore
|
11 |
+
const HF_ADMIN = process?.env?.HF_ADMIN.split(',') ?? []
|
12 |
+
const is_admin = staff_flag_id ? HF_ADMIN.includes(staff_flag_id) : false
|
13 |
+
|
14 |
+
if (!is_admin) {
|
15 |
+
return Response.json(
|
16 |
+
{
|
17 |
+
status: 401,
|
18 |
+
ok: false,
|
19 |
+
message: 'Wrong castle fam :/'
|
20 |
+
}
|
21 |
+
)
|
22 |
+
}
|
23 |
+
|
24 |
+
const collection = await prisma.collection.findUnique({
|
25 |
+
where: {
|
26 |
+
id: parseInt(params.id)
|
27 |
+
}
|
28 |
+
})
|
29 |
+
if (!collection) {
|
30 |
+
return Response.json(
|
31 |
+
{
|
32 |
+
status: 404,
|
33 |
+
ok: false
|
34 |
+
}
|
35 |
+
)
|
36 |
+
}
|
37 |
+
|
38 |
+
const new_image = await prisma.collection.update({
|
39 |
+
where: {
|
40 |
+
id: parseInt(params.id)
|
41 |
+
},
|
42 |
+
data: {
|
43 |
+
is_visible: true
|
44 |
+
}
|
45 |
+
})
|
46 |
+
|
47 |
+
return Response.json(
|
48 |
+
{
|
49 |
+
image: new_image,
|
50 |
+
message: `Image ${params.id} has been updated`,
|
51 |
+
status: 200,
|
52 |
+
ok: true
|
53 |
+
}
|
54 |
+
)
|
55 |
+
}
|
app/api/collections/route.ts
CHANGED
@@ -3,10 +3,19 @@ import { PrismaClient } from '@prisma/client'
|
|
3 |
const prisma = new PrismaClient()
|
4 |
|
5 |
export async function GET(request: Request) {
|
|
|
6 |
const { searchParams } = new URL(request.url)
|
7 |
const userId = searchParams.get('id') ?? undefined
|
8 |
const page = searchParams.get('page') ? parseInt(searchParams.get('page') as string) : 0
|
9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
const collections = await prisma.collection.findMany({
|
11 |
orderBy: {
|
12 |
id: 'desc'
|
@@ -14,6 +23,9 @@ export async function GET(request: Request) {
|
|
14 |
where: {
|
15 |
userId: {
|
16 |
equals: userId
|
|
|
|
|
|
|
17 |
}
|
18 |
},
|
19 |
take: 15,
|
|
|
3 |
const prisma = new PrismaClient()
|
4 |
|
5 |
export async function GET(request: Request) {
|
6 |
+
const { headers } = request
|
7 |
const { searchParams } = new URL(request.url)
|
8 |
const userId = searchParams.get('id') ?? undefined
|
9 |
const page = searchParams.get('page') ? parseInt(searchParams.get('page') as string) : 0
|
10 |
|
11 |
+
const staff_flag_id = headers.get('x-staff-flag-id') ?? undefined
|
12 |
+
|
13 |
+
// @ts-ignore
|
14 |
+
const HF_ADMIN = process?.env?.HF_ADMIN.split(',') ?? []
|
15 |
+
const is_admin = staff_flag_id ? HF_ADMIN.includes(staff_flag_id) : false
|
16 |
+
|
17 |
+
console.log('is_admin', HF_ADMIN, is_admin, staff_flag_id)
|
18 |
+
|
19 |
const collections = await prisma.collection.findMany({
|
20 |
orderBy: {
|
21 |
id: 'desc'
|
|
|
23 |
where: {
|
24 |
userId: {
|
25 |
equals: userId
|
26 |
+
},
|
27 |
+
is_visible: {
|
28 |
+
equals: is_admin ? undefined : true
|
29 |
}
|
30 |
},
|
31 |
take: 15,
|
app/api/login/route.ts
CHANGED
@@ -2,7 +2,7 @@ export async function GET() {
|
|
2 |
const REDIRECT_URI = `https://${process.env.SPACE_HOST}/login/callback`
|
3 |
return Response.json(
|
4 |
{
|
5 |
-
redirect: `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=openid%20profile&state=STATE&response_type=code`,
|
6 |
status: 200,
|
7 |
ok: true
|
8 |
}
|
|
|
2 |
const REDIRECT_URI = `https://${process.env.SPACE_HOST}/login/callback`
|
3 |
return Response.json(
|
4 |
{
|
5 |
+
redirect: `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=openid%20profile%20email&state=STATE&response_type=code`,
|
6 |
status: 200,
|
7 |
ok: true
|
8 |
}
|
app/api/me/route.ts
CHANGED
@@ -13,10 +13,16 @@ export async function GET() {
|
|
13 |
})
|
14 |
|
15 |
const res = await request.clone().json().catch(() => ({}));
|
|
|
|
|
|
|
16 |
|
17 |
return Response.json(
|
18 |
{
|
19 |
-
user:
|
|
|
|
|
|
|
20 |
status: 200,
|
21 |
ok: true
|
22 |
}
|
|
|
13 |
})
|
14 |
|
15 |
const res = await request.clone().json().catch(() => ({}));
|
16 |
+
// @ts-ignore
|
17 |
+
const HF_ADMIN = process?.env?.HF_ADMIN.split(',') ?? []
|
18 |
+
const is_admin = res?.sub ? HF_ADMIN.includes(res?.sub) : false
|
19 |
|
20 |
return Response.json(
|
21 |
{
|
22 |
+
user: {
|
23 |
+
...res,
|
24 |
+
is_admin,
|
25 |
+
},
|
26 |
status: 200,
|
27 |
ok: true
|
28 |
}
|
app/api/route.ts
CHANGED
@@ -41,7 +41,7 @@ export async function POST(
|
|
41 |
const imageIsNSFW = await isImageNSFW(blob, global_headers)
|
42 |
if (imageIsNSFW) return Response.json({ status: 401, ok: false, message: "Image is not safe for work." });
|
43 |
|
44 |
-
const name = Date.now() + `-${inputs.replace(/[^a-zA-Z0-9]/g, '-').
|
45 |
const { ok, message } = await UploaderDataset(blob, name)
|
46 |
|
47 |
if (!ok) return Response.json({ status: 500, ok: false, message });
|
|
|
41 |
const imageIsNSFW = await isImageNSFW(blob, global_headers)
|
42 |
if (imageIsNSFW) return Response.json({ status: 401, ok: false, message: "Image is not safe for work." });
|
43 |
|
44 |
+
const name = Date.now() + `-${inputs.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase()}`
|
45 |
const { ok, message } = await UploaderDataset(blob, name)
|
46 |
|
47 |
if (!ok) return Response.json({ status: 500, ok: false, message });
|
components/button/index.tsx
CHANGED
@@ -1,15 +1,17 @@
|
|
1 |
import classNames from "classnames";
|
|
|
2 |
|
3 |
interface Props {
|
4 |
children: React.ReactNode;
|
5 |
disabled?: boolean;
|
6 |
-
theme?: "primary" | "secondary" | "white";
|
7 |
-
onClick?: () => void;
|
8 |
}
|
9 |
export const Button: React.FC<Props> = ({
|
10 |
children,
|
11 |
disabled,
|
12 |
theme = "primary",
|
|
|
13 |
...props
|
14 |
}) => {
|
15 |
return (
|
@@ -19,11 +21,22 @@ export const Button: React.FC<Props> = ({
|
|
19 |
{
|
20 |
"bg-primary text-white border-primary": theme === "primary",
|
21 |
"bg-white text-gray-900 border-white": theme === "white",
|
|
|
|
|
22 |
"!bg-gray-400 !text-gray-600 !cursor-not-allowed !border-gray-400":
|
23 |
disabled,
|
24 |
}
|
25 |
)}
|
26 |
{...props}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
>
|
28 |
{children}
|
29 |
</button>
|
|
|
1 |
import classNames from "classnames";
|
2 |
+
import { MouseEvent } from "react";
|
3 |
|
4 |
interface Props {
|
5 |
children: React.ReactNode;
|
6 |
disabled?: boolean;
|
7 |
+
theme?: "primary" | "secondary" | "white" | "danger" | "success";
|
8 |
+
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
9 |
}
|
10 |
export const Button: React.FC<Props> = ({
|
11 |
children,
|
12 |
disabled,
|
13 |
theme = "primary",
|
14 |
+
onClick,
|
15 |
...props
|
16 |
}) => {
|
17 |
return (
|
|
|
21 |
{
|
22 |
"bg-primary text-white border-primary": theme === "primary",
|
23 |
"bg-white text-gray-900 border-white": theme === "white",
|
24 |
+
"bg-red-500 text-white border-red-500": theme === "danger",
|
25 |
+
"bg-green-500 text-white border-green-500": theme === "success",
|
26 |
"!bg-gray-400 !text-gray-600 !cursor-not-allowed !border-gray-400":
|
27 |
disabled,
|
28 |
}
|
29 |
)}
|
30 |
{...props}
|
31 |
+
onClick={
|
32 |
+
onClick
|
33 |
+
? (e) => {
|
34 |
+
e.stopPropagation();
|
35 |
+
e.preventDefault();
|
36 |
+
onClick(e);
|
37 |
+
}
|
38 |
+
: undefined
|
39 |
+
}
|
40 |
>
|
41 |
{children}
|
42 |
</button>
|
components/main/collections/collection.tsx
CHANGED
@@ -1,9 +1,11 @@
|
|
1 |
import { useMemo } from "react";
|
2 |
import { motion } from "framer-motion";
|
|
|
|
|
3 |
|
4 |
-
import {
|
5 |
import { useInputGeneration } from "@/components/main/hooks/useInputGeneration";
|
6 |
-
import {
|
7 |
|
8 |
interface Props {
|
9 |
index: number;
|
@@ -63,8 +65,18 @@ export const Collection: React.FC<Props> = ({
|
|
63 |
style={{
|
64 |
backgroundImage: `url(https://huggingface.co/datasets/enzostvs/stable-diffusion-tpu-generations/resolve/main/images/${collection.file_name}.png)`,
|
65 |
}}
|
66 |
-
className=
|
|
|
|
|
|
|
|
|
|
|
67 |
/>
|
|
|
|
|
|
|
|
|
|
|
68 |
</motion.div>
|
69 |
</div>
|
70 |
);
|
|
|
1 |
import { useMemo } from "react";
|
2 |
import { motion } from "framer-motion";
|
3 |
+
import classNames from "classnames";
|
4 |
+
import { AiFillEyeInvisible } from "react-icons/ai";
|
5 |
|
6 |
+
import { Image } from "@/utils/type";
|
7 |
import { useInputGeneration } from "@/components/main/hooks/useInputGeneration";
|
8 |
+
import { Button } from "@/components/button";
|
9 |
|
10 |
interface Props {
|
11 |
index: number;
|
|
|
65 |
style={{
|
66 |
backgroundImage: `url(https://huggingface.co/datasets/enzostvs/stable-diffusion-tpu-generations/resolve/main/images/${collection.file_name}.png)`,
|
67 |
}}
|
68 |
+
className={classNames(
|
69 |
+
"rounded-[33px] bg-gray-950 bg-cover absolute top-0 left-0 w-full h-full z-[-1] transition-all duration-200 group-hover:scale-110 bg-center",
|
70 |
+
{
|
71 |
+
"opacity-40": !collection.is_visible,
|
72 |
+
}
|
73 |
+
)}
|
74 |
/>
|
75 |
+
{!collection.is_visible && (
|
76 |
+
<div className="flex items-center justify-end px-5 py-6">
|
77 |
+
<AiFillEyeInvisible className="text-white text-2xl" />
|
78 |
+
</div>
|
79 |
+
)}
|
80 |
</motion.div>
|
81 |
</div>
|
82 |
);
|
components/main/hooks/useCollections.ts
CHANGED
@@ -1,14 +1,13 @@
|
|
1 |
import { useState } from "react";
|
2 |
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
3 |
-
import {
|
4 |
import _ from "lodash";
|
5 |
|
6 |
import { useUser } from "@/utils/useUser";
|
7 |
|
8 |
export const useCollections = (category: string) => {
|
9 |
const [loading, setLoading] = useState(false);
|
10 |
-
const { user } = useUser();
|
11 |
-
const [myGenerationsId] = useLocalStorage<any>('my-own-generations', []);
|
12 |
|
13 |
const client = useQueryClient();
|
14 |
|
@@ -24,6 +23,9 @@ export const useCollections = (category: string) => {
|
|
24 |
queryParams.append('page', '0');
|
25 |
|
26 |
const response = await fetch(`/api/collections?${queryParams.toString()}`, {
|
|
|
|
|
|
|
27 |
method: "GET",
|
28 |
})
|
29 |
|
@@ -38,6 +40,7 @@ export const useCollections = (category: string) => {
|
|
38 |
};
|
39 |
},
|
40 |
{
|
|
|
41 |
refetchOnMount: false,
|
42 |
refetchOnWindowFocus: false,
|
43 |
refetchOnReconnect: false,
|
@@ -51,6 +54,9 @@ export const useCollections = (category: string) => {
|
|
51 |
queryParams.append('page', data?.pagination?.page,);
|
52 |
|
53 |
const response = await fetch(`/api/collections?${queryParams.toString()}`, {
|
|
|
|
|
|
|
54 |
method: "GET",
|
55 |
})
|
56 |
|
|
|
1 |
import { useState } from "react";
|
2 |
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
3 |
+
import { useUpdateEffect } from "react-use";
|
4 |
import _ from "lodash";
|
5 |
|
6 |
import { useUser } from "@/utils/useUser";
|
7 |
|
8 |
export const useCollections = (category: string) => {
|
9 |
const [loading, setLoading] = useState(false);
|
10 |
+
const { user, loading: userLoading } = useUser();
|
|
|
11 |
|
12 |
const client = useQueryClient();
|
13 |
|
|
|
23 |
queryParams.append('page', '0');
|
24 |
|
25 |
const response = await fetch(`/api/collections?${queryParams.toString()}`, {
|
26 |
+
headers: {
|
27 |
+
'x-staff-flag-id': user?.sub
|
28 |
+
},
|
29 |
method: "GET",
|
30 |
})
|
31 |
|
|
|
40 |
};
|
41 |
},
|
42 |
{
|
43 |
+
enabled: !userLoading,
|
44 |
refetchOnMount: false,
|
45 |
refetchOnWindowFocus: false,
|
46 |
refetchOnReconnect: false,
|
|
|
54 |
queryParams.append('page', data?.pagination?.page,);
|
55 |
|
56 |
const response = await fetch(`/api/collections?${queryParams.toString()}`, {
|
57 |
+
headers: {
|
58 |
+
'x-staff-flag-id': user?.sub
|
59 |
+
},
|
60 |
method: "GET",
|
61 |
})
|
62 |
|
components/modal/modal.tsx
CHANGED
@@ -1,9 +1,12 @@
|
|
1 |
import { useMemo } from "react";
|
2 |
import { motion } from "framer-motion";
|
3 |
import Image from "next/image";
|
|
|
|
|
4 |
|
5 |
-
import { arrayBufferToBase64 } from "@/utils";
|
6 |
import { useCollection } from "./useCollection";
|
|
|
|
|
7 |
|
8 |
interface Props {
|
9 |
id: string;
|
@@ -30,16 +33,17 @@ const dropIn = {
|
|
30 |
};
|
31 |
|
32 |
export const Modal: React.FC<Props> = ({ id, onClose }) => {
|
33 |
-
const { collection } = useCollection(id);
|
34 |
-
|
35 |
-
if (!collection) return null;
|
36 |
|
37 |
const formatDate = useMemo(() => {
|
38 |
if (!collection) return;
|
39 |
-
const date = new Date(collection
|
40 |
return date.toLocaleDateString();
|
41 |
}, [collection?.createdAt]);
|
42 |
|
|
|
|
|
43 |
return (
|
44 |
<motion.div
|
45 |
onClick={onClose}
|
@@ -50,14 +54,28 @@ export const Modal: React.FC<Props> = ({ id, onClose }) => {
|
|
50 |
>
|
51 |
<motion.div
|
52 |
onClick={(e) => e.stopPropagation()}
|
53 |
-
className="max-w-2xl h-auto w-full z-[1] rounded-3xl overflow-hidden flex items-center justify-center flex-col gap-4 bg-white/30 backdrop-blur-sm px-2 pb-2 pt-2"
|
54 |
variants={dropIn}
|
55 |
initial="hidden"
|
56 |
animate="visible"
|
57 |
exit="exit"
|
58 |
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
<Image
|
60 |
-
src={`https://huggingface.co/datasets/enzostvs/stable-diffusion-tpu-generations/resolve/main/images/${collection
|
61 |
alt="Generated image"
|
62 |
className="object-center object-contain w-full h-full rounded-2xl"
|
63 |
width={1024}
|
@@ -66,13 +84,13 @@ export const Modal: React.FC<Props> = ({ id, onClose }) => {
|
|
66 |
<div
|
67 |
className="bg-cover bg-center w-full h-full rounded-2xl bg-no-repeat z-[-1] absolute top-0 left-0 opacity-90 blur-xl"
|
68 |
style={{
|
69 |
-
backgroundImage: `url(https://huggingface.co/datasets/enzostvs/stable-diffusion-tpu-generations/resolve/main/images/${collection
|
70 |
}}
|
71 |
/>
|
72 |
<div className="text-left w-full px-4 pb-3 pt-2">
|
73 |
<p className="text-sm font-medium text-white/60 mb-1">{formatDate}</p>
|
74 |
<p className="text-xl font-semibold text-white lowercase leading-snug">
|
75 |
-
{collection
|
76 |
</p>
|
77 |
</div>
|
78 |
</motion.div>
|
|
|
1 |
import { useMemo } from "react";
|
2 |
import { motion } from "framer-motion";
|
3 |
import Image from "next/image";
|
4 |
+
import { BsFillTrashFill } from "react-icons/bs";
|
5 |
+
import { AiFillCheckCircle } from "react-icons/ai";
|
6 |
|
|
|
7 |
import { useCollection } from "./useCollection";
|
8 |
+
import { Button } from "../button";
|
9 |
+
import { useUser } from "@/utils/useUser";
|
10 |
|
11 |
interface Props {
|
12 |
id: string;
|
|
|
33 |
};
|
34 |
|
35 |
export const Modal: React.FC<Props> = ({ id, onClose }) => {
|
36 |
+
const { collection, updateVisibility, remove } = useCollection(id);
|
37 |
+
const { user } = useUser();
|
|
|
38 |
|
39 |
const formatDate = useMemo(() => {
|
40 |
if (!collection) return;
|
41 |
+
const date = new Date(collection?.createdAt);
|
42 |
return date.toLocaleDateString();
|
43 |
}, [collection?.createdAt]);
|
44 |
|
45 |
+
console.log(user);
|
46 |
+
|
47 |
return (
|
48 |
<motion.div
|
49 |
onClick={onClose}
|
|
|
54 |
>
|
55 |
<motion.div
|
56 |
onClick={(e) => e.stopPropagation()}
|
57 |
+
className="max-w-2xl h-auto w-full z-[1] rounded-3xl overflow-hidden relative flex items-center justify-center flex-col gap-4 bg-white/30 backdrop-blur-sm px-2 pb-2 pt-2"
|
58 |
variants={dropIn}
|
59 |
initial="hidden"
|
60 |
animate="visible"
|
61 |
exit="exit"
|
62 |
>
|
63 |
+
{user?.is_admin && (
|
64 |
+
<div className="absolute p-2 rounded-full top-4 left-4 flex items-center justify-start gap-2 bg-black/20 backdrop-blur">
|
65 |
+
{!collection?.is_visible && (
|
66 |
+
<Button theme="white" onClick={updateVisibility}>
|
67 |
+
<AiFillCheckCircle />
|
68 |
+
Validate
|
69 |
+
</Button>
|
70 |
+
)}
|
71 |
+
<Button theme="danger" onClick={remove}>
|
72 |
+
<BsFillTrashFill />
|
73 |
+
Delete
|
74 |
+
</Button>
|
75 |
+
</div>
|
76 |
+
)}
|
77 |
<Image
|
78 |
+
src={`https://huggingface.co/datasets/enzostvs/stable-diffusion-tpu-generations/resolve/main/images/${collection?.file_name}.png?raw=true`}
|
79 |
alt="Generated image"
|
80 |
className="object-center object-contain w-full h-full rounded-2xl"
|
81 |
width={1024}
|
|
|
84 |
<div
|
85 |
className="bg-cover bg-center w-full h-full rounded-2xl bg-no-repeat z-[-1] absolute top-0 left-0 opacity-90 blur-xl"
|
86 |
style={{
|
87 |
+
backgroundImage: `url(https://huggingface.co/datasets/enzostvs/stable-diffusion-tpu-generations/resolve/main/images/${collection?.file_name}.png?raw=true)`,
|
88 |
}}
|
89 |
/>
|
90 |
<div className="text-left w-full px-4 pb-3 pt-2">
|
91 |
<p className="text-sm font-medium text-white/60 mb-1">{formatDate}</p>
|
92 |
<p className="text-xl font-semibold text-white lowercase leading-snug">
|
93 |
+
{collection?.prompt}
|
94 |
</p>
|
95 |
</div>
|
96 |
</motion.div>
|
components/modal/useCollection.ts
CHANGED
@@ -1,9 +1,14 @@
|
|
1 |
import { useMemo, useState } from "react"
|
2 |
import { useQuery, useQueryClient } from "@tanstack/react-query"
|
3 |
|
4 |
-
import { Collection } from "@/utils/type"
|
|
|
|
|
5 |
|
6 |
export const useCollection = (id?: string) => {
|
|
|
|
|
|
|
7 |
const { data: open } = useQuery(["modal"], () => {
|
8 |
return null
|
9 |
}, {
|
@@ -18,14 +23,67 @@ export const useCollection = (id?: string) => {
|
|
18 |
|
19 |
const collection = useMemo(() => {
|
20 |
const collections = client.getQueryData<Collection>(["collections"])
|
21 |
-
if (!collections?.images)
|
22 |
-
|
|
|
|
|
|
|
23 |
return collections?.images?.find((collection) => collection.id === id)
|
24 |
-
}, [id])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
return {
|
27 |
collection,
|
28 |
open,
|
29 |
-
setOpen
|
|
|
|
|
30 |
}
|
31 |
}
|
|
|
1 |
import { useMemo, useState } from "react"
|
2 |
import { useQuery, useQueryClient } from "@tanstack/react-query"
|
3 |
|
4 |
+
import { Collection, Image } from "@/utils/type"
|
5 |
+
import { useUser } from "@/utils/useUser"
|
6 |
+
import { set } from "lodash"
|
7 |
|
8 |
export const useCollection = (id?: string) => {
|
9 |
+
const { user } = useUser()
|
10 |
+
const [loading, setLoading] = useState(false)
|
11 |
+
|
12 |
const { data: open } = useQuery(["modal"], () => {
|
13 |
return null
|
14 |
}, {
|
|
|
23 |
|
24 |
const collection = useMemo(() => {
|
25 |
const collections = client.getQueryData<Collection>(["collections"])
|
26 |
+
if (!collections?.images) {
|
27 |
+
setOpen(null)
|
28 |
+
return null
|
29 |
+
}
|
30 |
+
|
31 |
return collections?.images?.find((collection) => collection.id === id)
|
32 |
+
}, [id, loading])
|
33 |
+
|
34 |
+
const updateVisibility = async () => {
|
35 |
+
setLoading(true)
|
36 |
+
const response = await fetch(`/api/collections/${collection?.id}/visibility`, {
|
37 |
+
method: "PUT",
|
38 |
+
headers: {
|
39 |
+
'x-staff-flag-id': user?.sub
|
40 |
+
}
|
41 |
+
})
|
42 |
+
|
43 |
+
const data = await response.json()
|
44 |
+
if (data.ok) {
|
45 |
+
client.setQueryData(["collections"], (old: any) => {
|
46 |
+
return {
|
47 |
+
...old,
|
48 |
+
images: old.images.map((collection: Image) => {
|
49 |
+
if (collection.id === data.image.id) {
|
50 |
+
return data.image
|
51 |
+
}
|
52 |
+
return collection
|
53 |
+
})
|
54 |
+
}
|
55 |
+
})
|
56 |
+
}
|
57 |
+
setLoading(false)
|
58 |
+
}
|
59 |
+
|
60 |
+
const remove = async () => {
|
61 |
+
setLoading(true)
|
62 |
+
const response = await fetch(`/api/collections/${collection?.id}`, {
|
63 |
+
method: "DELETE",
|
64 |
+
headers: {
|
65 |
+
'x-staff-flag-id': user?.sub
|
66 |
+
}
|
67 |
+
})
|
68 |
+
|
69 |
+
const data = await response.json()
|
70 |
+
if (data.ok) {
|
71 |
+
client.setQueryData(["collections"], (old: any) => {
|
72 |
+
return {
|
73 |
+
...old,
|
74 |
+
images: old.images.filter((col: Image) => col.id !== collection?.id)
|
75 |
+
}
|
76 |
+
})
|
77 |
+
setOpen(null)
|
78 |
+
}
|
79 |
+
setLoading(false)
|
80 |
+
}
|
81 |
|
82 |
return {
|
83 |
collection,
|
84 |
open,
|
85 |
+
setOpen,
|
86 |
+
updateVisibility,
|
87 |
+
remove
|
88 |
}
|
89 |
}
|
prisma/schema.prisma
CHANGED
@@ -4,13 +4,14 @@ generator client {
|
|
4 |
|
5 |
datasource db {
|
6 |
provider = "sqlite"
|
7 |
-
url = "file
|
8 |
}
|
9 |
|
10 |
model Collection {
|
11 |
-
id
|
12 |
-
prompt
|
13 |
-
file_name
|
14 |
-
|
15 |
-
|
|
|
16 |
}
|
|
|
4 |
|
5 |
datasource db {
|
6 |
provider = "sqlite"
|
7 |
+
url = "file:../data/dev.db"
|
8 |
}
|
9 |
|
10 |
model Collection {
|
11 |
+
id Int @id @default(autoincrement())
|
12 |
+
prompt String
|
13 |
+
file_name String
|
14 |
+
is_visible Boolean @default(false)
|
15 |
+
createdAt DateTime @default(now())
|
16 |
+
userId String? @default("")
|
17 |
}
|
utils/type.ts
CHANGED
@@ -12,6 +12,7 @@ export interface Image {
|
|
12 |
file_name: string;
|
13 |
prompt: string;
|
14 |
createdAt: string;
|
|
|
15 |
error?: string;
|
16 |
loading?: boolean;
|
17 |
}
|
|
|
12 |
file_name: string;
|
13 |
prompt: string;
|
14 |
createdAt: string;
|
15 |
+
is_visible: boolean;
|
16 |
error?: string;
|
17 |
loading?: boolean;
|
18 |
}
|