Esteves Enzo commited on
Commit
5240c42
1 Parent(s): d79c631
app/api/route.ts CHANGED
@@ -17,6 +17,7 @@ export async function POST(
17
  headers: {
18
  Authorization: `Bearer ${process.env.NEXT_PUBLIC_APP_HF_TOKEN}`,
19
  'Content-Type': 'application/json',
 
20
  },
21
  })
22
 
 
17
  headers: {
18
  Authorization: `Bearer ${process.env.NEXT_PUBLIC_APP_HF_TOKEN}`,
19
  'Content-Type': 'application/json',
20
+ ['x-use-cache']: "0"
21
  },
22
  })
23
 
app/page.tsx CHANGED
@@ -4,7 +4,7 @@ import { Main } from "@/components/main";
4
 
5
  export default function Home() {
6
  return (
7
- <div className="">
8
  <Header />
9
  <Main />
10
  <Footer />
 
4
 
5
  export default function Home() {
6
  return (
7
+ <div className="pb-32">
8
  <Header />
9
  <Main />
10
  <Footer />
components/main/collections/collection.tsx CHANGED
@@ -1,35 +1,36 @@
1
  import { useMemo } from "react";
2
  import { motion } from "framer-motion";
3
 
4
- import { Collection as CollectionType } from "@/type";
5
  import { useInputGeneration } from "@/components/main/hooks/useInputGeneration";
 
6
 
7
  interface Props {
8
  index: number;
9
  collection: CollectionType;
10
  className?: string;
 
11
  }
12
 
13
  export const Collection: React.FC<Props> = ({
14
  collection,
15
  index,
16
  className,
 
17
  }) => {
18
  const { setPrompt } = useInputGeneration();
19
 
20
- const arrayBufferToBase64 = (buffer: ArrayBuffer) => {
21
- let binary = "";
22
- const bytes = [].slice.call(new Uint8Array(buffer));
23
- bytes.forEach((b: any) => (binary += String.fromCharCode(b)));
24
- return window.btoa(binary);
25
- };
26
-
27
  const bufferToBase64 = useMemo(() => {
28
  const base64Flag = "data:image/jpeg;base64,";
29
  const imageStr = arrayBufferToBase64(collection.blob.data);
30
  return base64Flag + imageStr;
31
  }, [collection]);
32
 
 
 
 
 
 
33
  return (
34
  <div className={`h-[377px] w-full relative ${className}`}>
35
  <motion.div
@@ -37,18 +38,22 @@ export const Collection: React.FC<Props> = ({
37
  animate={{ y: 0, opacity: 1 }}
38
  transition={{ duration: 0.35, delay: index * 0.1 }}
39
  className="rounded-[33px] h-[377px] cursor-pointer group overflow-hidden relative z-[1] group"
 
40
  >
41
  <div className="absolute top-0 left-0 w-full h-full translate-y-full opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100 flex items-end p-3">
42
  <div className="bg-[#292424] backdrop-blur-sm bg-opacity-60 rounded-xl p-3 border-white/20 border w-full">
43
  <p className="text-xs font-semibold text-white/60 mb-0.5">
44
- 27 October
45
  </p>
46
  <p className="text-lg font-medium text-white lowercase leading-snug">
47
  {collection.prompt}
48
  </p>
49
  <p
50
  className="text-white text-sm text-right font-semibold mt-2"
51
- onClick={() => setPrompt(collection.prompt)}
 
 
 
52
  >
53
  Try it now
54
  </p>
@@ -58,7 +63,7 @@ export const Collection: React.FC<Props> = ({
58
  style={{
59
  backgroundImage: `url(${bufferToBase64})`,
60
  }}
61
- className="rounded-[33px] bg-red-400 bg-cover absolute top-0 left-0 w-full h-full z-[-1] transition-all duration-200 group-hover:scale-125 bg-center"
62
  />
63
  </motion.div>
64
  </div>
 
1
  import { useMemo } from "react";
2
  import { motion } from "framer-motion";
3
 
4
+ import { Collection as CollectionType } from "@/utils/type";
5
  import { useInputGeneration } from "@/components/main/hooks/useInputGeneration";
6
+ import { arrayBufferToBase64 } from "@/utils";
7
 
8
  interface Props {
9
  index: number;
10
  collection: CollectionType;
11
  className?: string;
12
+ onOpen: (id: number) => void;
13
  }
14
 
15
  export const Collection: React.FC<Props> = ({
16
  collection,
17
  index,
18
  className,
19
+ onOpen,
20
  }) => {
21
  const { setPrompt } = useInputGeneration();
22
 
 
 
 
 
 
 
 
23
  const bufferToBase64 = useMemo(() => {
24
  const base64Flag = "data:image/jpeg;base64,";
25
  const imageStr = arrayBufferToBase64(collection.blob.data);
26
  return base64Flag + imageStr;
27
  }, [collection]);
28
 
29
+ const formatDate = useMemo(() => {
30
+ const date = new Date(collection.createdAt);
31
+ return date.toLocaleDateString();
32
+ }, [collection.createdAt]);
33
+
34
  return (
35
  <div className={`h-[377px] w-full relative ${className}`}>
36
  <motion.div
 
38
  animate={{ y: 0, opacity: 1 }}
39
  transition={{ duration: 0.35, delay: index * 0.1 }}
40
  className="rounded-[33px] h-[377px] cursor-pointer group overflow-hidden relative z-[1] group"
41
+ onClick={() => onOpen(collection.id)}
42
  >
43
  <div className="absolute top-0 left-0 w-full h-full translate-y-full opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100 flex items-end p-3">
44
  <div className="bg-[#292424] backdrop-blur-sm bg-opacity-60 rounded-xl p-3 border-white/20 border w-full">
45
  <p className="text-xs font-semibold text-white/60 mb-0.5">
46
+ {formatDate}
47
  </p>
48
  <p className="text-lg font-medium text-white lowercase leading-snug">
49
  {collection.prompt}
50
  </p>
51
  <p
52
  className="text-white text-sm text-right font-semibold mt-2"
53
+ onClick={(e) => {
54
+ e.stopPropagation();
55
+ setPrompt(collection.prompt);
56
+ }}
57
  >
58
  Try it now
59
  </p>
 
63
  style={{
64
  backgroundImage: `url(${bufferToBase64})`,
65
  }}
66
+ className="rounded-[33px] bg-red-400 bg-cover absolute top-0 left-0 w-full h-full z-[-1] transition-all duration-200 group-hover:scale-110 bg-center"
67
  />
68
  </motion.div>
69
  </div>
components/main/collections/index.tsx CHANGED
@@ -1,45 +1,54 @@
1
  import classNames from "classnames";
2
- import { createBreakpoint, useLocalStorage } from "react-use";
3
-
4
- import { Collection as CollectionType } from "@/type";
5
 
 
6
  import { useCollections } from "@/components/main/hooks/useCollections";
 
7
  import { Collection } from "./collection";
8
  import { CollectionLoading } from "./loading";
 
9
 
10
  const useBreakpoint = createBreakpoint({ XL: 1280, L: 1024, S: 768, XS: 640 });
11
 
12
  export const Collections: React.FC<{ category: string }> = ({ category }) => {
 
13
  const { collections, loading } = useCollections(category);
14
  const breakpoint = useBreakpoint();
15
 
16
  if (loading) return null;
17
 
18
  return (
19
- <div className="mx-auto grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-5 mt-8 lg:mt-14">
20
- {collections?.map((collection: CollectionType, i: number) =>
21
- collection?.id === -1 ? (
22
- <CollectionLoading key={i} prompt={collection.prompt} />
23
- ) : (
24
- <Collection
25
- key={category + collection.id}
26
- index={i}
27
- collection={collection}
28
- className={classNames("", {
29
- "!translate-y-12":
30
- breakpoint === "XL"
31
- ? i % 5 === 1 || i % 5 === 3
32
- : breakpoint === "L"
33
- ? i % 4 === 1 || i % 4 === 3
34
- : breakpoint === "S"
35
- ? i % 3 === 1
36
- : breakpoint === "XS"
37
- ? i % 2 === 1
38
- : false,
39
- })}
40
- />
41
- )
42
- )}
43
- </div>
 
 
 
 
 
 
44
  );
45
  };
 
1
  import classNames from "classnames";
2
+ import { createBreakpoint } from "react-use";
3
+ import { AnimatePresence } from "framer-motion";
 
4
 
5
+ import { Collection as CollectionType } from "@/utils/type";
6
  import { useCollections } from "@/components/main/hooks/useCollections";
7
+ import { Modal } from "@/components/modal/modal";
8
  import { Collection } from "./collection";
9
  import { CollectionLoading } from "./loading";
10
+ import { useState } from "react";
11
 
12
  const useBreakpoint = createBreakpoint({ XL: 1280, L: 1024, S: 768, XS: 640 });
13
 
14
  export const Collections: React.FC<{ category: string }> = ({ category }) => {
15
+ const [open, setOpen] = useState<number | null>(null);
16
  const { collections, loading } = useCollections(category);
17
  const breakpoint = useBreakpoint();
18
 
19
  if (loading) return null;
20
 
21
  return (
22
+ <>
23
+ <div className="mx-auto grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-5 mt-8 lg:mt-14">
24
+ {collections?.map((collection: CollectionType, i: number) =>
25
+ collection?.id === -1 ? (
26
+ <CollectionLoading key={i} prompt={collection.prompt} />
27
+ ) : (
28
+ <Collection
29
+ key={category + collection.id}
30
+ index={i}
31
+ collection={collection}
32
+ className={classNames("", {
33
+ "!translate-y-12":
34
+ breakpoint === "XL"
35
+ ? i % 5 === 1 || i % 5 === 3
36
+ : breakpoint === "L"
37
+ ? i % 4 === 1 || i % 4 === 3
38
+ : breakpoint === "S"
39
+ ? i % 3 === 1
40
+ : breakpoint === "XS"
41
+ ? i % 2 === 1
42
+ : false,
43
+ })}
44
+ onOpen={setOpen}
45
+ />
46
+ )
47
+ )}
48
+ </div>
49
+ <AnimatePresence initial={false} mode="wait" onExitComplete={() => null}>
50
+ {open !== null && <Modal id={open} onClose={() => setOpen(null)} />}
51
+ </AnimatePresence>
52
+ </>
53
  );
54
  };
components/main/collections/loading.tsx CHANGED
@@ -1,7 +1,7 @@
1
  import { useMemo } from "react";
2
  import { motion } from "framer-motion";
3
 
4
- import { Collection as CollectionType } from "@/type";
5
 
6
  interface Props {
7
  prompt: string;
 
1
  import { useMemo } from "react";
2
  import { motion } from "framer-motion";
3
 
4
+ import { Collection as CollectionType } from "@/utils/type";
5
 
6
  interface Props {
7
  prompt: string;
components/main/hooks/useInputGeneration.ts CHANGED
@@ -2,7 +2,7 @@ import { useState } from "react"
2
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
3
  import { useLocalStorage } from 'react-use';
4
 
5
- import { Collection } from "@/type"
6
  import list_styles from "@/assets/list_styles.json"
7
 
8
  export const useInputGeneration = () => {
 
2
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
3
  import { useLocalStorage } from 'react-use';
4
 
5
+ import { Collection } from "@/utils/type"
6
  import list_styles from "@/assets/list_styles.json"
7
 
8
  export const useInputGeneration = () => {
components/main/index.tsx CHANGED
@@ -33,21 +33,12 @@ export const Main = () => {
33
  <main className="px-6 z-[2] relative max-w-[1722px] mx-auto">
34
  <div className="py-2 pl-2 pr-4 bg-black bg-opacity-30 backdrop-blur-sm sticky top-6 z-10 rounded-full">
35
  <div className="flex flex-col lg:flex-row items-center justify-between w-full">
36
- <div className="flex items-center justify-start gap-3 w-full flex-col lg:flex-row">
37
- <InputGeneration
38
- prompt={prompt}
39
- onChange={setPrompt}
40
- onSubmit={submit}
41
- loading={loading}
42
- />
43
- <p
44
- className="text-white/70 font-medium text-sm flex items-center justify-start gap-2 hover:text-white cursor-pointer mt-2 lg:mt-0"
45
- onClick={() => setAdvancedSettings(!advancedSettings)}
46
- >
47
- <HiAdjustmentsHorizontal className="w-5 h-5" />
48
- Advanced settings
49
- </p>
50
- </div>
51
  <div className="items-center justify-center lg:justify-end gap-5 w-full mt-6 lg:mt-0 hidden lg:flex">
52
  {categories.map(({ key, label, icon }) => (
53
  <Button
@@ -61,6 +52,13 @@ export const Main = () => {
61
  ))}
62
  </div>
63
  </div>
 
 
 
 
 
 
 
64
  <Settings
65
  open={advancedSettings}
66
  style={style}
 
33
  <main className="px-6 z-[2] relative max-w-[1722px] mx-auto">
34
  <div className="py-2 pl-2 pr-4 bg-black bg-opacity-30 backdrop-blur-sm sticky top-6 z-10 rounded-full">
35
  <div className="flex flex-col lg:flex-row items-center justify-between w-full">
36
+ <InputGeneration
37
+ prompt={prompt}
38
+ onChange={setPrompt}
39
+ onSubmit={submit}
40
+ loading={loading}
41
+ />
 
 
 
 
 
 
 
 
 
42
  <div className="items-center justify-center lg:justify-end gap-5 w-full mt-6 lg:mt-0 hidden lg:flex">
43
  {categories.map(({ key, label, icon }) => (
44
  <Button
 
52
  ))}
53
  </div>
54
  </div>
55
+ <p
56
+ className="text-white/70 font-medium text-sm flex items-center justify-start gap-2 hover:text-white cursor-pointer mt-3"
57
+ onClick={() => setAdvancedSettings(!advancedSettings)}
58
+ >
59
+ <HiAdjustmentsHorizontal className="w-5 h-5" />
60
+ Advanced settings
61
+ </p>
62
  <Settings
63
  open={advancedSettings}
64
  style={style}
components/modal/modal.tsx ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMemo } from "react";
2
+ import { motion } from "framer-motion";
3
+
4
+ import { arrayBufferToBase64 } from "@/utils";
5
+ import { useCollection } from "./useCollection";
6
+
7
+ interface Props {
8
+ id: number;
9
+ onClose: () => void;
10
+ }
11
+
12
+ const dropIn = {
13
+ hidden: {
14
+ y: "-100vh",
15
+ opacity: 0,
16
+ },
17
+ visible: {
18
+ y: "0",
19
+ opacity: 1,
20
+ transition: {
21
+ duration: 0.1,
22
+ type: "spring",
23
+ damping: 25,
24
+ stiffness: 500,
25
+ },
26
+ },
27
+ exit: {
28
+ y: "100vh",
29
+ opacity: 0,
30
+ },
31
+ };
32
+
33
+ export const Modal: React.FC<Props> = ({ id, onClose }) => {
34
+ const collection = useCollection(id);
35
+
36
+ if (!collection) return null;
37
+
38
+ const bufferToBase64 = useMemo(() => {
39
+ if (!collection) return;
40
+ const base64Flag = "data:image/jpeg;base64,";
41
+ const imageStr = arrayBufferToBase64(collection.blob.data);
42
+ return base64Flag + imageStr;
43
+ }, [collection]);
44
+
45
+ const formatDate = useMemo(() => {
46
+ if (!collection) return;
47
+ const date = new Date(collection.createdAt);
48
+ return date.toLocaleDateString();
49
+ }, [collection?.createdAt]);
50
+
51
+ return (
52
+ <motion.div
53
+ onClick={onClose}
54
+ className="fixed top-0 w-screen h-screen left-0 bg-black/30 backdrop-blur-sm z-50 flex items-center justify-center p-6"
55
+ initial={{ opacity: 0 }}
56
+ animate={{ opacity: 1 }}
57
+ exit={{ opacity: 0 }}
58
+ >
59
+ <motion.div
60
+ onClick={(e) => e.stopPropagation()}
61
+ className="max-w-2xl h-2/3 w-full z-[1] rounded-3xl overflow-hidden flex items-center justify-center flex-col gap-4 bg-white/30 backdrop-blur-sm p-2"
62
+ variants={dropIn}
63
+ initial="hidden"
64
+ animate="visible"
65
+ exit="exit"
66
+ >
67
+ <div
68
+ className="bg-cover bg-center w-full h-full rounded-2xl"
69
+ style={{
70
+ backgroundImage: `url(${bufferToBase64})`,
71
+ }}
72
+ />
73
+ <div className="text-left w-full px-4 pb-3 pt-2">
74
+ <p className="text-sm font-medium text-white/60 mb-1">{formatDate}</p>
75
+ <p className="text-xl font-semibold text-white lowercase leading-snug">
76
+ {collection.prompt}
77
+ </p>
78
+ </div>
79
+ </motion.div>
80
+ </motion.div>
81
+ );
82
+ };
components/modal/useCollection.ts ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMemo, useState } from "react"
2
+ import { useQueryClient } from "@tanstack/react-query"
3
+
4
+ import { Collection } from "@/utils/type"
5
+
6
+ export const useCollection = (id: number) => {
7
+ const client = useQueryClient()
8
+
9
+ const collection = useMemo(() => {
10
+ const collections = client.getQueryData<Collection[]>(["collections"])
11
+ if (!collections) return null
12
+
13
+ return collections.find((collection) => collection.id === id)
14
+ }, [id])
15
+
16
+ return collection
17
+ }
prisma/migrations/{20231027143238_init → 20231027155221_init}/migration.sql RENAMED
@@ -2,5 +2,6 @@
2
  CREATE TABLE "Image" (
3
  "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
4
  "prompt" TEXT NOT NULL,
5
- "blob" BLOB NOT NULL
 
6
  );
 
2
  CREATE TABLE "Image" (
3
  "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
4
  "prompt" TEXT NOT NULL,
5
+ "blob" BLOB NOT NULL,
6
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
7
  );
prisma/schema.prisma CHANGED
@@ -8,7 +8,8 @@ datasource db {
8
  }
9
 
10
  model Image {
11
- id Int @id @default(autoincrement())
12
- prompt String
13
- blob Bytes
 
14
  }
 
8
  }
9
 
10
  model Image {
11
+ id Int @id @default(autoincrement())
12
+ prompt String
13
+ blob Bytes
14
+ createdAt DateTime @default(now())
15
  }
utils/index.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export const arrayBufferToBase64 = (buffer: ArrayBuffer) => {
2
+ let binary = "";
3
+ const bytes = [].slice.call(new Uint8Array(buffer));
4
+ bytes.forEach((b: any) => (binary += String.fromCharCode(b)));
5
+ return window.btoa(binary);
6
+ };
type/index.ts → utils/type.ts RENAMED
@@ -5,4 +5,5 @@ export interface Collection {
5
  data: ArrayBuffer
6
  };
7
  prompt: string;
 
8
  }
 
5
  data: ArrayBuffer
6
  };
7
  prompt: string;
8
+ createdAt: string;
9
  }