mirror of
https://github.com/D4M13N-D3V/neroshitron.git
synced 2025-03-14 10:05:04 +00:00
feat: tags
* fix: navigation bar code is minimized, started seperating out components and refactoring them * feat: search components * Update search.tsx * fix: autofocus * fix: tags and search
This commit is contained in:
parent
92c2317af1
commit
f2bd76487e
@ -54,8 +54,8 @@ export async function GET(
|
|||||||
.select('*')
|
.select('*')
|
||||||
.eq('user_id', userId)
|
.eq('user_id', userId)
|
||||||
.single();
|
.single();
|
||||||
console.log(subscription)
|
//console.log(subscription)
|
||||||
console.log(gallery.tier)
|
//console.log(gallery.tier)
|
||||||
switch(gallery.tier){
|
switch(gallery.tier){
|
||||||
case "Tier 3":
|
case "Tier 3":
|
||||||
if(subscription?.tier!="Tier 3"){
|
if(subscription?.tier!="Tier 3"){
|
||||||
|
@ -1,85 +1,22 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { createClient } from "@/utils/supabase/client";
|
import { createClient } from "@/utils/supabase/client";
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
import GalleryThumbnail from "@/components/ui/gallery_thumbnail";
|
|
||||||
|
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { User } from "@supabase/supabase-js";
|
import Search from "@/components/neroshitron/search";
|
||||||
import Gallery from "@/components/ui/gallery";
|
|
||||||
|
|
||||||
function PageComponent() {
|
function PageComponent() {
|
||||||
|
|
||||||
const supabase = createClient();
|
const supabase = createClient();
|
||||||
|
|
||||||
const [showNSFW, setShowNSFW] = useState<boolean>(true);
|
|
||||||
const [randomIds, setRandomIds] = useState<string[]>([]); // replace any with your gallery type
|
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
|
||||||
const [galleries, setGalleries] = useState<any[]>([]); // replace any with your gallery type
|
|
||||||
const [user, setUser] = useState<User | null>(null);
|
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
|
||||||
const [selectedGallery, setSelectedGallery] = useState<string | null>(null);
|
|
||||||
const [tags, setTags] = useState<any[]>([]);
|
|
||||||
const [search, setSearch] = useState<string>('');
|
|
||||||
const [galleryColumns, setColumns] = useState<number>(0);
|
|
||||||
const [selectedTags, setSelectedTags] = useState<number[]>([]);
|
|
||||||
const generateRandomString = function (length: number) {
|
|
||||||
let result = '';
|
|
||||||
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
||||||
let charactersLength = characters.length;
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectGallery = (gallery: string, columns: number) => {
|
|
||||||
setRandomIds([generateRandomString(3), generateRandomString(3), generateRandomString(3), generateRandomString(3)]);
|
|
||||||
setSelectedGallery(gallery);
|
|
||||||
setColumns(columns);
|
|
||||||
setIsOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeGallery = () => {
|
|
||||||
setSelectedGallery(null);
|
|
||||||
setColumns(0);
|
|
||||||
setIsOpen(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
let { data: { user } } = await supabase.auth.getUser();
|
|
||||||
const galleriesResponse = await fetch(`/api/galleries?search=` + search + '&nsfw=' + showNSFW, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ tags: selectedTags })
|
|
||||||
});
|
|
||||||
//console.log(galleriesResponse)
|
|
||||||
const galleriesData = await galleriesResponse.json();
|
|
||||||
const tagsResponse = await fetch(`/api/galleries/tags`);
|
|
||||||
const tagsData = await tagsResponse.json();
|
|
||||||
setGalleries(galleriesData);
|
|
||||||
setTags(tagsData);
|
|
||||||
setUser(user);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getData();
|
getData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
}, [selectedTags, search, showNSFW]);
|
|
||||||
|
|
||||||
const handleTagClick = (tag: number) => {
|
|
||||||
if (selectedTags.includes(tag)) {
|
|
||||||
setSelectedTags(selectedTags.filter((selectedTag) => selectedTag !== tag));
|
|
||||||
} else {
|
|
||||||
setSelectedTags([...selectedTags, tag]);
|
|
||||||
}
|
|
||||||
//console.log(selectedTags)
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="w-full">
|
||||||
<div className="fixed w-full h-full overflow-hidden z-0 animate-fade-left animate-fade-left animate-once animate-duration-[2000ms] animate-normal animate-fill-forwards">
|
<div className="fixed w-full h-full overflow-hidden z-0 animate-fade-left animate-fade-left animate-once animate-duration-[2000ms] animate-normal animate-fill-forwards">
|
||||||
<img
|
<img
|
||||||
src="gallery_girl.png"
|
src="gallery_girl.png"
|
||||||
@ -87,114 +24,7 @@ function PageComponent() {
|
|||||||
alt="Background"
|
alt="Background"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<Search/>
|
||||||
|
|
||||||
{/*
|
|
||||||
THIS IS THE SEARCH BAR AND TAGS SECTION
|
|
||||||
THIS IS THE SEARCH BAR AND TAGS SECTION
|
|
||||||
THIS IS THE SEARCH BAR AND TAGS SECTION
|
|
||||||
THIS IS THE SEARCH BAR AND TAGS SECTION
|
|
||||||
THIS IS THE SEARCH BAR AND TAGS SECTION
|
|
||||||
THIS IS THE SEARCH BAR AND TAGS SECTION
|
|
||||||
*/}
|
|
||||||
<section className="flex items-center w-full p-8 pt-20 opacity-90 animate-jump-in animate-once animate-duration-500">
|
|
||||||
{(tags.length > 0) ? (
|
|
||||||
<div className="container mx-auto py-8">
|
|
||||||
<div className="relative w-full mx-auto">
|
|
||||||
<input
|
|
||||||
className="w-full text-neroshi-blue-950 h-16 px-3 rounded mb-8 focus:outline-none focus:shadow-outline text-xl px-8 shadow-lg"
|
|
||||||
type="search"
|
|
||||||
placeholder="Search by title..."
|
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
|
||||||
style={{
|
|
||||||
animation: 'expandFromLeft 2s ease-out forwards',
|
|
||||||
paddingRight: '2rem', // make room for the checkbox
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label htmlFor="toggleNSFW" className="absolute right-0 top-0 h-full mr-2 flex items-center text-neroshi-blue-950 animate-fade animate-once animate-duration-500 animate-delay-[2000ms] animate-ease-out">
|
|
||||||
Censor NSFW
|
|
||||||
<input
|
|
||||||
id="toggleNSFW"
|
|
||||||
type="checkbox"
|
|
||||||
checked={showNSFW}
|
|
||||||
onChange={() => setShowNSFW(!showNSFW)}
|
|
||||||
className="form-checkbox h-5 w-5 text-neroshi-blue-950 ml-2 "
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{(tags.length > 0) ? (
|
|
||||||
<nav className="grid grid-cols-3 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10 gap-4 justify-items-center">
|
|
||||||
{tags.map((tag, index) => (
|
|
||||||
<a
|
|
||||||
key={index}
|
|
||||||
className={`w-full rounded-lg no-underline text-white py-3 px-4 font-medium text-center animate-jump-in animate-once animate-duration-500 animate-ease-out ${selectedTags.includes(tag.name) ? 'bg-neroshi-blue-950 hover:bg-neroshi-blue-900' : 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700'
|
|
||||||
}`}
|
|
||||||
href="#"
|
|
||||||
onClick={() => handleTagClick(tag.name)}
|
|
||||||
>
|
|
||||||
{tag.name}
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</nav>
|
|
||||||
) : (
|
|
||||||
<div className="flex justify-center">
|
|
||||||
<p className="text-white">Loading Tags...</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="animate-pulse bg-neroshi-blue-950 rounded-3xl w-full p-8 mt-10 h-48" ></div>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/*
|
|
||||||
These are the thumbnails for the gallery below the search bar
|
|
||||||
*/}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-y-60 gap-x-5 h-full mb-96 animate-in">
|
|
||||||
{galleries && galleries.map((gallery, index) => (
|
|
||||||
<div className="mx-auto">
|
|
||||||
<GalleryThumbnail
|
|
||||||
key={gallery.name + " " + showNSFW}
|
|
||||||
id={gallery.name}
|
|
||||||
title={gallery.name}
|
|
||||||
tags={gallery.tags}
|
|
||||||
columns={gallery.columns}
|
|
||||||
showNsfw={showNSFW}
|
|
||||||
subscription={gallery.tier as string}
|
|
||||||
onSelect={selectGallery}
|
|
||||||
nsfw={gallery.nsfw}
|
|
||||||
></GalleryThumbnail>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{isOpen ? (
|
|
||||||
<>
|
|
||||||
{/*
|
|
||||||
This is the modal for holding the gallery
|
|
||||||
*/}
|
|
||||||
<div
|
|
||||||
className={`fixed inset-0 transition-opacity z-30 ${isOpen ? 'animate-in' : 'fade-out'
|
|
||||||
}`}
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 bg-neroshi-blue-900 opacity-70 z-30"
|
|
||||||
onClick={() => setIsOpen(false)}
|
|
||||||
></div>
|
|
||||||
<div className="absolute inset-0 overflow-y-auto overflow-x-hidden no-scrollbar pt-2 w-full p-20 z-30">
|
|
||||||
<Gallery
|
|
||||||
id={selectedGallery as string}
|
|
||||||
columns={galleryColumns}
|
|
||||||
closeMenu={() => closeGallery()}
|
|
||||||
></Gallery>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
131
app/gallery/page_old.tsx
Normal file
131
app/gallery/page_old.tsx
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
"use client";
|
||||||
|
import { createClient } from "@/utils/supabase/client";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import GalleryThumbnail from "@/components/ui/gallery_thumbnail";
|
||||||
|
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { User } from "@supabase/supabase-js";
|
||||||
|
import Gallery from "@/components/ui/gallery";
|
||||||
|
import Search from "@/components/ui/search";
|
||||||
|
|
||||||
|
function PageComponent() {
|
||||||
|
|
||||||
|
const supabase = createClient();
|
||||||
|
|
||||||
|
const [showNSFW, setShowNSFW] = useState<boolean>(true);
|
||||||
|
const [randomIds, setRandomIds] = useState<string[]>([]); // replace any with your gallery type
|
||||||
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||||
|
const [galleries, setGalleries] = useState<any[]>([]); // replace any with your gallery type
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
const [selectedGallery, setSelectedGallery] = useState<string | null>(null);
|
||||||
|
const [search, setSearch] = useState<string>('');
|
||||||
|
const [galleryColumns, setColumns] = useState<number>(0);
|
||||||
|
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||||
|
const generateRandomString = function (length: number) {
|
||||||
|
let result = '';
|
||||||
|
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
let charactersLength = characters.length;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectGallery = (gallery: string, columns: number) => {
|
||||||
|
setRandomIds([generateRandomString(3), generateRandomString(3), generateRandomString(3), generateRandomString(3)]);
|
||||||
|
setSelectedGallery(gallery);
|
||||||
|
setColumns(columns);
|
||||||
|
setIsOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeGallery = () => {
|
||||||
|
setSelectedGallery(null);
|
||||||
|
setColumns(0);
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
let { data: { user } } = await supabase.auth.getUser();
|
||||||
|
const galleriesResponse = await fetch(`/api/galleries?search=` + search + '&nsfw=' + showNSFW, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ tags: selectedTags })
|
||||||
|
});
|
||||||
|
const galleriesData = await galleriesResponse.json();
|
||||||
|
setGalleries(galleriesData);
|
||||||
|
setUser(user);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
}, [selectedTags, search, showNSFW]);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="fixed w-full h-full overflow-hidden z-0 animate-fade-left animate-fade-left animate-once animate-duration-[2000ms] animate-normal animate-fill-forwards">
|
||||||
|
<img
|
||||||
|
src="gallery_girl.png"
|
||||||
|
className="float-right object-cover h-screen w-full lg:w-5/6 xl:w-3/6 opacity-50 overflow-hidden"
|
||||||
|
alt="Background"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Search
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
{/*
|
||||||
|
These are the thumbnails for the gallery below the search bar
|
||||||
|
*/}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-y-60 gap-x-5 h-full mb-96 animate-in">
|
||||||
|
{galleries && galleries.map((gallery, index) => (
|
||||||
|
<div className="mx-auto">
|
||||||
|
<GalleryThumbnail
|
||||||
|
key={gallery.name + " " + showNSFW}
|
||||||
|
id={gallery.name}
|
||||||
|
title={gallery.name}
|
||||||
|
tags={gallery.tags}
|
||||||
|
columns={gallery.columns}
|
||||||
|
showNsfw={showNSFW}
|
||||||
|
subscription={gallery.tier as string}
|
||||||
|
onSelect={selectGallery}
|
||||||
|
nsfw={gallery.nsfw}
|
||||||
|
></GalleryThumbnail>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{isOpen ? (
|
||||||
|
<>
|
||||||
|
{/*
|
||||||
|
This is the modal for holding the gallery
|
||||||
|
*/}
|
||||||
|
<div
|
||||||
|
className={`fixed inset-0 transition-opacity z-30 ${isOpen ? 'animate-in' : 'fade-out'
|
||||||
|
}`}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-neroshi-blue-900 opacity-70 z-30"
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
></div>
|
||||||
|
<div className="absolute inset-0 overflow-y-auto overflow-x-hidden no-scrollbar pt-2 w-full p-20 z-30">
|
||||||
|
<Gallery
|
||||||
|
id={selectedGallery as string}
|
||||||
|
columns={galleryColumns}
|
||||||
|
closeMenu={() => closeGallery()}
|
||||||
|
></Gallery>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PageComponent;
|
@ -1,6 +1,6 @@
|
|||||||
import { GeistSans } from "geist/font/sans";
|
import { GeistSans } from "geist/font/sans";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import NavigationBar from "@/components/NavigationBar";
|
import NavigationBar from "@/components/neroshitron/navigation_bar";
|
||||||
import { SpeedInsights } from "@vercel/speed-insights/next"
|
import { SpeedInsights } from "@vercel/speed-insights/next"
|
||||||
import { Analytics } from "@vercel/analytics/react"
|
import { Analytics } from "@vercel/analytics/react"
|
||||||
const defaultUrl = process.env.VERCEL_URL
|
const defaultUrl = process.env.VERCEL_URL
|
||||||
|
@ -1,150 +0,0 @@
|
|||||||
import { createClient } from "@/utils/supabase/server";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { redirect, useRouter } from "next/navigation";
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import { headers } from "next/headers";
|
|
||||||
|
|
||||||
|
|
||||||
export default async function AuthButton() {
|
|
||||||
const supabase = createClient();
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: { user },
|
|
||||||
} = await supabase.auth.getUser();
|
|
||||||
|
|
||||||
const signOut = async () => {
|
|
||||||
"use server";
|
|
||||||
|
|
||||||
const supabase = createClient();
|
|
||||||
await supabase.auth.signOut();
|
|
||||||
return redirect("/login");
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
const heads = headers()
|
|
||||||
const currentPage = heads.get('x-path')
|
|
||||||
if(user){
|
|
||||||
let email = user.email;
|
|
||||||
if(email != null){
|
|
||||||
const emailHash = crypto.createHash('md5').update(email.trim().toLowerCase()).digest('hex');
|
|
||||||
const gravatarUrl = `https://www.gravatar.com/avatar/${emailHash}`;
|
|
||||||
return(
|
|
||||||
<div className="flex justify-center items-center pt-2 ">
|
|
||||||
<nav className="w-auto bg-neroshi-blue-300 bg-opacity-10 flex justify-center z-10 h-16 animate-in rounded-md" style={{ backdropFilter: 'blur(10px)' }}>
|
|
||||||
<div className="w-full max-w-2xl flex justify-between items-center p-3 text-sm">
|
|
||||||
<div className="flex items-center gap-2 z-10">
|
|
||||||
<Link
|
|
||||||
href="/gallery"
|
|
||||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<span className="hidden lg:block">Gallery</span>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/livestream"
|
|
||||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" />
|
|
||||||
</svg>
|
|
||||||
<span className="hidden lg:block">Livestream</span>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/commissions"
|
|
||||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 10.5V6a3.75 3.75 0 1 0-7.5 0v4.5m11.356-1.993 1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 0 1-1.12-1.243l1.264-12A1.125 1.125 0 0 1 5.513 7.5h12.974c.576 0 1.059.435 1.119 1.007ZM8.625 10.5a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm7.5 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
|
|
||||||
</svg>
|
|
||||||
<span className="hidden lg:block">Commissions</span>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/subscriptions"
|
|
||||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
|
||||||
</svg>
|
|
||||||
<span className="hidden lg:block">Subscription</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<form action={signOut}>
|
|
||||||
<button className="py-2 px-4 ml-2 rounded-md no-underline bg-pink-950 hover:bg-pink-900">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 md:hidden ">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 10.5V6.75a4.5 4.5 0 1 1 9 0v3.75M3.75 21.75h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H3.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z" />
|
|
||||||
</svg>
|
|
||||||
<span className="hidden lg:block">Logout</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<img src={gravatarUrl} alt="Profile" className="w-10 h-10 object-cover rounded-full cursor-pointer" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return( <div className="flex justify-center items-center pt-2 ">
|
|
||||||
<nav className="w-auto bg-neroshi-blue-300 bg-opacity-10 flex justify-center h-16 animate-in rounded-md" style={{ backdropFilter: 'blur(10px)' }}>
|
|
||||||
|
|
||||||
<div className="w-full max-w-2xl flex justify-between items-center p-3 text-sm">
|
|
||||||
<div className="flex items-center gap-2 ">
|
|
||||||
<Link
|
|
||||||
href="/gallery"
|
|
||||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<span className="hidden lg:block">Gallery</span>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/livestream"
|
|
||||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" />
|
|
||||||
</svg>
|
|
||||||
<span className="hidden lg:block">Livestream</span>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/commissions"
|
|
||||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 10.5V6a3.75 3.75 0 1 0-7.5 0v4.5m11.356-1.993 1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 0 1-1.12-1.243l1.264-12A1.125 1.125 0 0 1 5.513 7.5h12.974c.576 0 1.059.435 1.119 1.007ZM8.625 10.5a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm7.5 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
|
|
||||||
</svg>
|
|
||||||
<span className="hidden lg:block">Commissions</span>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/subscriptions"
|
|
||||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
|
||||||
</svg>
|
|
||||||
<span className="hidden lg:block">Subscription</span>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/subscriptions"
|
|
||||||
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
|
||||||
</svg>
|
|
||||||
<span className="hidden lg:block">Login</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>)
|
|
||||||
}
|
|
||||||
}
|
|
61
components/neroshitron/galleries.tsx
Normal file
61
components/neroshitron/galleries.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
"use client;"
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import GalleryThumbnail from './gallery_thumbnail';
|
||||||
|
|
||||||
|
interface TagProps {
|
||||||
|
nsfw: boolean;
|
||||||
|
tags: string[];
|
||||||
|
search: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Galleries = ({ nsfw, tags, search }:TagProps) => {
|
||||||
|
|
||||||
|
const [galleries, setGalleries] = useState([]);
|
||||||
|
const [nsfwState, setNsfwState] = useState<boolean>(nsfw);
|
||||||
|
const [tagsState, setTagsState] = useState<string[]>(tags);
|
||||||
|
const [searchState, setSearchState] = useState<string>(search);
|
||||||
|
|
||||||
|
const [selectedGallery, setSelectedGallery] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const selectGallery = (gallery: string) => {
|
||||||
|
setSelectedGallery(gallery);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(tags)
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const galleriesResponse = await fetch(`/api/galleries?search=` + searchState + '&nsfw=' + nsfwState, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ tags: tagsState })
|
||||||
|
});
|
||||||
|
const galleriesData = await galleriesResponse.json();
|
||||||
|
setGalleries(galleriesData);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
}, [tagsState]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="absolute inset-0 mx-auto ml-16 md:ml-0 pt-48 p-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-60 gap-x-4 animate-in overflow-y-scroll no-scrollbar z-0">
|
||||||
|
{galleries && galleries.map((gallery, index) => (
|
||||||
|
<GalleryThumbnail
|
||||||
|
key={gallery.name + " " + nsfw}
|
||||||
|
id={gallery.name}
|
||||||
|
title={gallery.name}
|
||||||
|
tags={gallery.tags}
|
||||||
|
columns={gallery.columns}
|
||||||
|
showNsfw={nsfw}
|
||||||
|
subscription={gallery.tier as string}
|
||||||
|
onSelect={selectGallery}
|
||||||
|
nsfw={gallery.nsfw}
|
||||||
|
></GalleryThumbnail>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Galleries;
|
@ -12,18 +12,11 @@ interface GalleryProps {
|
|||||||
|
|
||||||
const Gallery = ({ id, columns, closeMenu }: GalleryProps) => {
|
const Gallery = ({ id, columns, closeMenu }: GalleryProps) => {
|
||||||
|
|
||||||
const [isSingle, setIsSingle] = useState<boolean>(false);
|
|
||||||
const [loaded, setLoaded] = useState({})
|
|
||||||
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||||||
const [images, setImages] = useState<string[]>([]);
|
const [images, setImages] = useState<string[]>([]);
|
||||||
const [galleryId, setGalleryId] = useState(id as string);
|
const [galleryId, setGalleryId] = useState(id as string);
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
const panZoomRef = useRef<any>(null);
|
||||||
const getData = async () => {
|
|
||||||
const thumbnailResponse = await fetch('/api/galleries/' + String(galleryId) + '/images');
|
|
||||||
const thumbnailUrl = await thumbnailResponse.json() as string[];
|
|
||||||
setImages(thumbnailUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
if (currentIndex < images.length - 1) {
|
if (currentIndex < images.length - 1) {
|
||||||
@ -41,6 +34,76 @@ const Gallery = ({ id, columns, closeMenu }: GalleryProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDownload = (image: string) => {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = image;
|
||||||
|
link.download = 'image.jpg'; // or any other filename
|
||||||
|
link.style.display = 'none';
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const thumbnailResponse = await fetch('/api/galleries/' + String(galleryId) + '/images');
|
||||||
|
const thumbnailUrl = await thumbnailResponse.json() as string[];
|
||||||
|
setImages(thumbnailUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowLeft':
|
||||||
|
case 'a':
|
||||||
|
case 'A':
|
||||||
|
previous();
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
case 'd':
|
||||||
|
case 'D':
|
||||||
|
next();
|
||||||
|
break;
|
||||||
|
case 'Escape':
|
||||||
|
close();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setSelectedImage(images[currentIndex]);
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
|
||||||
|
// Clean up the event listener when the component is unmounted
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
|
||||||
|
}, [selectedImage, currentIndex]);
|
||||||
|
|
||||||
|
const handleClick = (image: string) => {
|
||||||
|
setSelectedImage(image);
|
||||||
|
setCurrentIndex(images.indexOf(image));
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetPanZoom = (event: any) => {
|
||||||
|
if (panZoomRef.current && event.target.id != "image-container") {
|
||||||
|
panZoomRef.current.autoCenter();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
if (selectedImage != null) {
|
||||||
|
setSelectedImage(null);
|
||||||
|
setImages([]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
closeMenu();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const renderButtons = () => {
|
const renderButtons = () => {
|
||||||
return (
|
return (
|
||||||
<div className="z-20 bottom-10 fixed pt-4 bg-purple-900 bg-opacity-40 animate-in rounded-2xl" style={{ backdropFilter: 'blur(10px)' }}>
|
<div className="z-20 bottom-10 fixed pt-4 bg-purple-900 bg-opacity-40 animate-in rounded-2xl" style={{ backdropFilter: 'blur(10px)' }}>
|
||||||
@ -85,84 +148,7 @@ const Gallery = ({ id, columns, closeMenu }: GalleryProps) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const handleDownload = (image: string) => {
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = image;
|
|
||||||
link.download = 'image.jpg'; // or any other filename
|
|
||||||
link.style.display = 'none';
|
|
||||||
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
};
|
|
||||||
useEffect(() => {
|
|
||||||
getData();
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
|
||||||
switch (event.key) {
|
|
||||||
case 'ArrowLeft':
|
|
||||||
case 'a':
|
|
||||||
case 'A':
|
|
||||||
previous();
|
|
||||||
break;
|
|
||||||
case 'ArrowRight':
|
|
||||||
case 'd':
|
|
||||||
case 'D':
|
|
||||||
next();
|
|
||||||
break;
|
|
||||||
case 'Escape':
|
|
||||||
close();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setSelectedImage(images[currentIndex]);
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
|
||||||
|
|
||||||
// Clean up the event listener when the component is unmounted
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
|
||||||
};
|
|
||||||
|
|
||||||
}, [selectedImage,currentIndex]);
|
|
||||||
|
|
||||||
const handleClick = (image: string) => {
|
|
||||||
setSelectedImage(image);
|
|
||||||
setCurrentIndex(images.indexOf(image));
|
|
||||||
};
|
|
||||||
|
|
||||||
const open = () => {
|
|
||||||
if (selectedImage === null) return;
|
|
||||||
//console.log(selectedImage)
|
|
||||||
let base64Image = selectedImage.split(';base64,').pop();
|
|
||||||
if (!base64Image) return;
|
|
||||||
let blob = new Blob([Uint8Array.from(atob(base64Image), c => c.charCodeAt(0))], { type: 'image/jpeg' }); // adjust the type as needed
|
|
||||||
let url = URL.createObjectURL(blob);
|
|
||||||
window.open(url, '_blank');
|
|
||||||
}
|
|
||||||
|
|
||||||
const panZoomRef = useRef<any>(null);
|
|
||||||
|
|
||||||
const resetPanZoom = (event: any) => {
|
|
||||||
if (panZoomRef.current && event.target.id != "image-container") {
|
|
||||||
panZoomRef.current.autoCenter();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
if (selectedImage != null) {
|
|
||||||
setSelectedImage(null);
|
|
||||||
setImages([]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
closeMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const breakpointColumnsObj = {
|
|
||||||
default: 3
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<div >
|
<div >
|
||||||
<div className="z-20"
|
<div className="z-20"
|
||||||
@ -177,10 +163,6 @@ const Gallery = ({ id, columns, closeMenu }: GalleryProps) => {
|
|||||||
autoCenter={true}
|
autoCenter={true}
|
||||||
ref={panZoomRef}
|
ref={panZoomRef}
|
||||||
>
|
>
|
||||||
{/*
|
|
||||||
<div
|
|
||||||
onClick={() => resetPanZoom()} className='w-full h-full z-10'>
|
|
||||||
</div> */}
|
|
||||||
<div id="image-container" >
|
<div id="image-container" >
|
||||||
<img
|
<img
|
||||||
src={images[currentIndex]}
|
src={images[currentIndex]}
|
||||||
@ -216,8 +198,6 @@ const Gallery = ({ id, columns, closeMenu }: GalleryProps) => {
|
|||||||
))}
|
))}
|
||||||
</Masonry>
|
</Masonry>
|
||||||
</div>
|
</div>
|
||||||
<>
|
|
||||||
</>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
110
components/neroshitron/navigation_bar.tsx
Normal file
110
components/neroshitron/navigation_bar.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { createClient } from "@/utils/supabase/server";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { redirect, useRouter } from "next/navigation";
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
|
|
||||||
|
export default async function AuthButton() {
|
||||||
|
const supabase = createClient();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { user },
|
||||||
|
} = await supabase.auth.getUser();
|
||||||
|
|
||||||
|
const signOut = async () => {
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
const supabase = createClient();
|
||||||
|
await supabase.auth.signOut();
|
||||||
|
return redirect("/login");
|
||||||
|
};
|
||||||
|
|
||||||
|
const heads = headers()
|
||||||
|
const currentPage = heads.get('x-path')
|
||||||
|
|
||||||
|
const getGravatarUrl = () => {
|
||||||
|
if(user==null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let email = user.email;
|
||||||
|
if(email != null){
|
||||||
|
const emailHash = crypto.createHash('md5').update(email.trim().toLowerCase()).digest('hex');
|
||||||
|
return `https://www.gravatar.com/avatar/${emailHash}`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const url = getGravatarUrl();
|
||||||
|
return(
|
||||||
|
<div className="flex justify-center items-center pt-2 ">
|
||||||
|
<nav className="w-auto bg-neroshi-blue-300 bg-opacity-10 flex justify-center z-10 h-16 animate-in rounded-md" style={{ backdropFilter: 'blur(10px)' }}>
|
||||||
|
<div className="w-full max-w-2xl flex justify-between items-center p-3 text-sm">
|
||||||
|
<div className="flex items-center gap-2 z-10">
|
||||||
|
<Link
|
||||||
|
href="/gallery"
|
||||||
|
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<span className="hidden lg:block">Gallery</span>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/livestream"
|
||||||
|
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" />
|
||||||
|
</svg>
|
||||||
|
<span className="hidden lg:block">Livestream</span>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/commissions"
|
||||||
|
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 10.5V6a3.75 3.75 0 1 0-7.5 0v4.5m11.356-1.993 1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 0 1-1.12-1.243l1.264-12A1.125 1.125 0 0 1 5.513 7.5h12.974c.576 0 1.059.435 1.119 1.007ZM8.625 10.5a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm7.5 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
|
||||||
|
</svg>
|
||||||
|
<span className="hidden lg:block">Commissions</span>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/subscriptions"
|
||||||
|
className={`py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||||
|
</svg>
|
||||||
|
<span className="hidden lg:block">Subscription</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{(user!=null) ? (
|
||||||
|
<>
|
||||||
|
<form action={signOut}>
|
||||||
|
<button className="py-2 px-4 ml-2 rounded-md no-underline bg-pink-950 hover:bg-pink-900">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 md:hidden ">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 10.5V6.75a4.5 4.5 0 1 1 9 0v3.75M3.75 21.75h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H3.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z" />
|
||||||
|
</svg>
|
||||||
|
<span className="hidden lg:block">Logout</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<img src={url} alt="Profile" className="w-10 h-10 object-cover rounded-full cursor-pointer" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
href="/subscriptions"
|
||||||
|
className={`ml-2 py-2 px-3 flex rounded-md no-underline ${currentPage === '/gallery' ? 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700' : 'bg-neroshi-blue-900 hover:bg-neroshi-blue-800'}`}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6 lg:hidden block">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||||
|
</svg>
|
||||||
|
<span className="hidden lg:block">Login</span>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>)
|
||||||
|
}
|
42
components/neroshitron/search.tsx
Normal file
42
components/neroshitron/search.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"use client;"
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import SearchInput from '@/components/neroshitron/search_input';
|
||||||
|
import Galleries from './galleries';
|
||||||
|
|
||||||
|
interface SearchProps { }
|
||||||
|
|
||||||
|
const Search = ({ }:SearchProps) => {
|
||||||
|
const [tags, setTags] = useState<string[]>([]);
|
||||||
|
const [search, setSearch] = useState<string>('');
|
||||||
|
const [nsfw, setNsfw] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
}, [search]);
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
}, [nsfw]);
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
}, [tags]);
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Galleries key={search+"-"+tags.length+"-"+nsfw} search={search} nsfw={nsfw} tags={tags} />
|
||||||
|
<section className="fixed flex items-center w-full p-8 pt-20 opacity-90 animate-in animate-once animate-duration-500">
|
||||||
|
<div className="container mx-auto py-8">
|
||||||
|
<SearchInput searchChanged={(search)=>{setSearch(search)}} nsfwChanged={(nsfw)=>{setNsfw(nsfw)}} tagsChanged={(tags)=>{setTags(tags);}} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Search;
|
109
components/neroshitron/search_input.tsx
Normal file
109
components/neroshitron/search_input.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
"use client;"
|
||||||
|
import React, { useState, useEffect, useRef,forwardRef } from 'react';
|
||||||
|
import TagSelector from '../neroshitron/tag_selector';
|
||||||
|
|
||||||
|
interface SearchInputProps {
|
||||||
|
tagsChanged: (tags: string[]) => void;
|
||||||
|
searchChanged: (search: string) => void;
|
||||||
|
nsfwChanged: (nsfw: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SearchInput = ({ tagsChanged, searchChanged, nsfwChanged}: SearchInputProps) => {
|
||||||
|
|
||||||
|
const [search, setSearch] = useState<string>('');
|
||||||
|
const [tagSearch, setTagSearch] = useState<string>('');
|
||||||
|
const [nsfw, setNsfw] = useState<boolean>(false);
|
||||||
|
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||||
|
const [selectingTags, setSelectingTags] = useState<boolean>(false);
|
||||||
|
const tagSelectorRef = React.useRef(null);
|
||||||
|
const [tags, setTags] = useState<any[]>([]);
|
||||||
|
|
||||||
|
|
||||||
|
const updateTags = (newTags: string[]) => {
|
||||||
|
setSelectedTags(newTags)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTagsClosed = (tags:string[]) => {
|
||||||
|
setSelectingTags(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const openTags = () => {
|
||||||
|
setSelectingTags(true);
|
||||||
|
if(selectingTags){
|
||||||
|
onTagsClosed(selectedTags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const tagsResponse = await fetch(`/api/galleries/tags`);
|
||||||
|
const tagsData = await tagsResponse.json();
|
||||||
|
setTags(tagsData);
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
searchChanged(search);
|
||||||
|
}, [search]);
|
||||||
|
useEffect(() => {
|
||||||
|
tagsChanged(selectedTags);
|
||||||
|
}, [selectedTags]);
|
||||||
|
useEffect(() => {
|
||||||
|
nsfwChanged(nsfw);
|
||||||
|
}, [nsfw]);
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="relative md:w-full lg:w-1/2 mx-auto flex flex-col items-center justify-center z-10">
|
||||||
|
<div className="search-box mx-auto my-auto w-full sm:w-full md:w-full lg:w-3/4 xl:w-3/4">
|
||||||
|
<div className="flex flex-row">
|
||||||
|
|
||||||
|
{(selectingTags) ? (
|
||||||
|
<>
|
||||||
|
<input autoFocus value={tagSearch} onChange={(e) => setTagSearch(e.target.value)} className="rounded-l-md h-16 bg-gray-100 text-grey-darker py-2 font-normal text-grey-darkest border border-gray-100 font-bold w-full py-1 px-2 outline-none text-lg text-gray-600" type="text" placeholder="Looking for specific tag?" />
|
||||||
|
|
||||||
|
<span className="flex items-center bg-gray-100 rounded rounded-l-none border-0 px-3 font-bold text-grey-100">
|
||||||
|
<button key="back" onClick={()=>{openTags()}} type="button" className={`animate-in bg-pink-900 hover:bg-pink-800 text-lg text-white font-bold py-3 px-6 rounded`}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
:(
|
||||||
|
<>
|
||||||
|
<input autoFocus value={search} onChange={(e) => setSearch(e.target.value)} className="rounded-l-md h-16 bg-gray-100 text-grey-darker py-2 font-normal text-grey-darkest border border-gray-100 font-bold w-full py-1 px-2 outline-none text-lg text-gray-600" type="text" placeholder="Looking for a specific collection?" />
|
||||||
|
|
||||||
|
<span className="flex items-center bg-gray-100 rounded rounded-l-none border-0 px-3 font-bold text-grey-100">
|
||||||
|
<button key="tags" data-tip={selectedTags.join(',')} onClick={()=>{openTags()}} type="button" className={`bg-neroshi-blue-900 hover:bg-neroshi-blue-800 text-lg text-white font-bold py-3 px-6 rounded ${selectedTags.length == 0 ? 'animate-pulse animate-infinite animate-ease-out' : 'animate-in'}`}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z" />
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M6 6h.008v.008H6V6Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={()=>{ setNsfw(!nsfw) }}
|
||||||
|
type="button"
|
||||||
|
className={`animate-in text-sm w-28 text-lg text-white font-bold py-3 px-6 rounded ml-2 ${nsfw ? "bg-pink-900 hover:bg-pink-800":"bg-green-900 hover:bg-green-800"}`}
|
||||||
|
|
||||||
|
>
|
||||||
|
{nsfw ? "NSFW" : "SFW"}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{(selectingTags) &&
|
||||||
|
<TagSelector tagsInput={tags} key={tagSearch} tagSearch={tagSearch} tagsChanged={(newTags:string[])=>{ updateTags(newTags) }} selectedTagsInput={selectedTags} ref={tagSelectorRef} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchInput;
|
19
components/neroshitron/tag_pill.tsx
Normal file
19
components/neroshitron/tag_pill.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
"use client;"
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface TagProps { onTagClicked: (tag: string ) => void, selected:boolean, tag:string }
|
||||||
|
|
||||||
|
const Tag = ({ onTagClicked, selected, tag, }:TagProps) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`animate-in w-full h-8 rounded-md no-underline text-sm text-white py-1 font-medium text-center ${selected ? 'hover:bg-pink-800 bg-pink-900' : 'hover:bg-pink-600 bg-neroshi-blue-800 border-neroshi-blue-900 border-2'}`}
|
||||||
|
onClick={() => onTagClicked(tag)}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tag;
|
60
components/neroshitron/tag_selector.tsx
Normal file
60
components/neroshitron/tag_selector.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
"use client;"
|
||||||
|
import React, { forwardRef, useState, useEffect } from 'react';
|
||||||
|
import Tag from './tag_pill';
|
||||||
|
|
||||||
|
interface TagSelectorProps {
|
||||||
|
tagsInput: any[],
|
||||||
|
tagSearch: string,
|
||||||
|
selectedTagsInput: string[],
|
||||||
|
tagsChanged: (tags: string[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const TagSelector = forwardRef<TagSelectorProps, {tagsInput:any[], tagSearch: string, selectedTagsInput: string[], tagsChanged: (tags: string[]) => void }>((props, ref) => {
|
||||||
|
|
||||||
|
const [tags, setTags] = useState<any[]>(props.tagsInput);
|
||||||
|
const [tagSearch, setTagSearch] = useState<string>(props.tagSearch);
|
||||||
|
const [selectedTags, setSelectedTags] = useState<string[]>(props.selectedTagsInput);
|
||||||
|
|
||||||
|
console.log()
|
||||||
|
|
||||||
|
const handleTag = (tag: string) => {
|
||||||
|
if (selectedTags.includes(tag)) {
|
||||||
|
setSelectedTags(selectedTags.filter(t => t !== tag));
|
||||||
|
} else {
|
||||||
|
setSelectedTags([...selectedTags, tag]);
|
||||||
|
}
|
||||||
|
setTags(selectedTags);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
props.tagsChanged(selectedTags);
|
||||||
|
getData();
|
||||||
|
}, [selectedTags,tagSearch,tags]);
|
||||||
|
|
||||||
|
const generateRandomString = () => {
|
||||||
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
(tags.length > 0)? (
|
||||||
|
<div className="animate-in flex md:w-full animate-in pt-4 justify-center items-center">
|
||||||
|
<div className="z-10 grid p-4 grid-cols-2 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-1 w-full h-max-72 overflow-y-scroll no-scrollbar pt-4 bg-neroshi-blue-900 rounded-md opacity-90 backdrop-filter backdrop-blur-md mx-auto">
|
||||||
|
{props.tagsInput.map((tag: any) => (
|
||||||
|
(tagSearch === '' || tag.name.toLowerCase().includes(tagSearch.toLowerCase())) && // Updated condition
|
||||||
|
<Tag key={generateRandomString()} tag={tag.name} selected={selectedTags.includes(tag.name)} onTagClicked={(tag) => handleTag(tag)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
):(<></>)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TagSelector;
|
96
components/ui/search.tsx
Normal file
96
components/ui/search.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
"use client;"
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import TagSelector from './tag_selector';
|
||||||
|
|
||||||
|
interface SearchProps { }
|
||||||
|
|
||||||
|
const Search = ({ }:SearchProps) => {
|
||||||
|
const [nsfw, setNsfw] = useState<boolean>(false);
|
||||||
|
const [tags, setTags] = useState<any[]>([]);
|
||||||
|
const [search, setSearch] = useState<string>('');
|
||||||
|
const [selectedTags, setSelectedTagsState] = useState<string[]>([]);
|
||||||
|
const [selectingTags, setSelectingTags] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const getNsfw = () => {
|
||||||
|
return nsfw;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTags = () => {
|
||||||
|
return tags;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSearch = () => {
|
||||||
|
return search;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelectedTags = () => {
|
||||||
|
return selectedTags;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const tagsResponse = await fetch(`/api/galleries/tags`);
|
||||||
|
const tagsData = await tagsResponse.json();
|
||||||
|
setTags(tagsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
}, [selectingTags]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className="flex items-center w-full p-8 pt-20 opacity-90 animate-jump-in animate-once animate-duration-500">
|
||||||
|
{(tags.length > 0) ? (
|
||||||
|
<div className="container mx-auto py-8">
|
||||||
|
<div className="relative w-full mx-auto">
|
||||||
|
<input
|
||||||
|
className="animate-in animate-delay-[2000ms] w-full text-neroshi-blue-950 h-16 px-3 rounded mb-8 focus:outline-none focus:shadow-outline text-xl px-8 shadow-lg"
|
||||||
|
type="search"
|
||||||
|
placeholder="Search by title..."
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
<div className="absolute right-0 top-0 h-full mr-2 flex items-center">
|
||||||
|
<label htmlFor="toggleNSFW" className="text-neroshi-blue-950 animate-in animate-delay-[2000ms] animate-ease-out">
|
||||||
|
Censor NSFW
|
||||||
|
<input
|
||||||
|
id="toggleNSFW"
|
||||||
|
type="checkbox"
|
||||||
|
onChange={(e) => setNsfw(e.target.checked)}
|
||||||
|
className="form-checkbox h-5 w-5 text-neroshi-blue-950 ml-2"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav className="pt-6 grid grid-cols-3 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10 gap-4 justify-items-center">
|
||||||
|
{tags.map((tag, index) => (
|
||||||
|
<a
|
||||||
|
key={index}
|
||||||
|
className={`w-full rounded-lg no-underline text-white py-3 px-4 font-medium text-center animate-jump-in animate-once animate-duration-500 animate-ease-out ${selectedTags.includes(tag.name) ? 'bg-neroshi-blue-950 hover:bg-neroshi-blue-900' : 'bg-neroshi-blue-800 hover:bg-neroshi-blue-700'
|
||||||
|
}`}
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
{tag.name}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
<a
|
||||||
|
key={"select-tags"}
|
||||||
|
className={`w-full rounded-lg no-underline text-white py-3 px-4 font-medium text-center animate-jump-in animate-once animate-duration-500 animate-ease-out bg-pink-800 hover:bg-pink-700`}
|
||||||
|
href="#"
|
||||||
|
onClick={() => setSelectingTags(true)}
|
||||||
|
>
|
||||||
|
Select Tags : {selectingTags}
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="animate-pulse bg-neroshi-blue-950 rounded-3xl w-full p-8 mt-10 h-48"></div>
|
||||||
|
)}
|
||||||
|
{(selectingTags) ??(
|
||||||
|
<TagSelector/>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Search;
|
27
components/ui/tag_selector.tsx
Normal file
27
components/ui/tag_selector.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
"use client;"
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface SearchProps { }
|
||||||
|
|
||||||
|
const TagSelector = ({ }:SearchProps) => {
|
||||||
|
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`fixed inset-0 transition-opacity z-30 animate-in`} aria-hidden="true" >
|
||||||
|
<div className="absolute inset-0 bg-neroshi-blue-900 opacity-70 z-30">
|
||||||
|
</div>
|
||||||
|
<div className="absolute inset-0 overflow-y-auto overflow-x-hidden no-scrollbar pt-2 w-full p-20 z-30">
|
||||||
|
Test
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TagSelector;
|
Loading…
x
Reference in New Issue
Block a user