feat: default project cover images tab on the change cover popover (#2375)

* feat: default project cover images tab

* chore: remove unnecessary env vars from turbo.json
This commit is contained in:
Aaryan Khandelwal 2023-10-04 20:04:35 +05:30 committed by GitHub
parent 64af5e2e75
commit 77c1b90e6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 148 additions and 101 deletions

View File

@ -74,24 +74,6 @@ class FileServices extends APIService {
throw error?.response?.data; throw error?.response?.data;
}); });
} }
async getUnsplashImages(page: number = 1, query?: string): Promise<UnSplashImage[]> {
const url = "/api/unsplash";
return this.request({
method: "get",
url,
params: {
page,
per_page: 20,
query,
},
})
.then((response) => response?.data?.results ?? response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
} }
const fileServices = new FileServices(); const fileServices = new FileServices();

View File

@ -12,8 +12,6 @@
"NEXT_PUBLIC_GITHUB_APP_NAME", "NEXT_PUBLIC_GITHUB_APP_NAME",
"NEXT_PUBLIC_ENABLE_SENTRY", "NEXT_PUBLIC_ENABLE_SENTRY",
"NEXT_PUBLIC_ENABLE_OAUTH", "NEXT_PUBLIC_ENABLE_OAUTH",
"NEXT_PUBLIC_UNSPLASH_ACCESS",
"NEXT_PUBLIC_UNSPLASH_ENABLED",
"NEXT_PUBLIC_TRACK_EVENTS", "NEXT_PUBLIC_TRACK_EVENTS",
"NEXT_PUBLIC_PLAUSIBLE_DOMAIN", "NEXT_PUBLIC_PLAUSIBLE_DOMAIN",
"NEXT_PUBLIC_CRISP_ID", "NEXT_PUBLIC_CRISP_ID",

View File

@ -1,32 +1,23 @@
import React, { useEffect, useState, useRef, useCallback } from "react"; import React, { useEffect, useState, useRef, useCallback } from "react";
// next
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// swr
import useSWR from "swr"; import useSWR from "swr";
// react-dropdown
import { useDropzone } from "react-dropzone"; import { useDropzone } from "react-dropzone";
// headless ui
import { Tab, Transition, Popover } from "@headlessui/react"; import { Tab, Transition, Popover } from "@headlessui/react";
// services // services
import fileService from "services/file.service"; import fileService from "services/file.service";
// components
import { Input, Spinner, PrimaryButton, SecondaryButton } from "components/ui";
// hooks // hooks
import useWorkspaceDetails from "hooks/use-workspace-details"; import useWorkspaceDetails from "hooks/use-workspace-details";
import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useOutsideClickDetector from "hooks/use-outside-click-detector";
// components
const unsplashEnabled = import { Input, PrimaryButton, SecondaryButton, Loader } from "components/ui";
process.env.NEXT_PUBLIC_UNSPLASH_ENABLED === "true" ||
process.env.NEXT_PUBLIC_UNSPLASH_ENABLED === "1";
const tabOptions = [ const tabOptions = [
{
key: "unsplash",
title: "Unsplash",
},
{ {
key: "images", key: "images",
title: "Images", title: "Images",
@ -64,8 +55,22 @@ export const ImagePickerPopover: React.FC<Props> = ({
search: "", search: "",
}); });
const { data: images } = useSWR(`UNSPLASH_IMAGES_${searchParams}`, () => const { data: unsplashImages, error: unsplashError } = useSWR(
fileService.getUnsplashImages(1, searchParams) `UNSPLASH_IMAGES_${searchParams}`,
() => fileService.getUnsplashImages(searchParams),
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
const { data: projectCoverImages } = useSWR(
`PROJECT_COVER_IMAGES`,
() => fileService.getProjectCoverImages(),
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
); );
const imagePickerRef = useRef<HTMLDivElement>(null); const imagePickerRef = useRef<HTMLDivElement>(null);
@ -115,18 +120,17 @@ export const ImagePickerPopover: React.FC<Props> = ({
}; };
useEffect(() => { useEffect(() => {
if (!images || value !== null) return; if (!unsplashImages || value !== null) return;
onChange(images[0].urls.regular);
}, [value, onChange, images]); onChange(unsplashImages[0].urls.regular);
}, [value, onChange, unsplashImages]);
useOutsideClickDetector(imagePickerRef, () => setIsOpen(false)); useOutsideClickDetector(imagePickerRef, () => setIsOpen(false));
if (!unsplashEnabled) return null;
return ( return (
<Popover className="relative z-[2]" ref={ref}> <Popover className="relative z-[2]" ref={ref}>
<Popover.Button <Popover.Button
className="rounded-sm border border-custom-border-300 bg-custom-background-100 px-2 py-1 text-xs text-custom-text-200 hover:text-custom-text-100" className="rounded border border-custom-border-300 bg-custom-background-100 px-2 py-1 text-xs text-custom-text-200 hover:text-custom-text-100"
onClick={() => setIsOpen((prev) => !prev)} onClick={() => setIsOpen((prev) => !prev)}
disabled={disabled} disabled={disabled}
> >
@ -141,15 +145,19 @@ export const ImagePickerPopover: React.FC<Props> = ({
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Popover.Panel className="absolute right-0 z-10 mt-2 rounded-md border border-custom-border-200 bg-custom-background-80 shadow-lg"> <Popover.Panel className="absolute right-0 z-10 mt-2 rounded-md border border-custom-border-200 bg-custom-background-100 shadow-custom-shadow-sm">
<div <div
ref={imagePickerRef} ref={imagePickerRef}
className="h-96 md:h-[28rem] w-80 md:w-[36rem] flex flex-col overflow-auto rounded border border-custom-border-300 bg-custom-background-100 p-3 shadow-2xl" className="h-96 md:h-[28rem] w-80 md:w-[36rem] flex flex-col overflow-auto rounded border border-custom-border-300 bg-custom-background-100 p-3 shadow-2xl"
> >
<Tab.Group> <Tab.Group>
<div> <Tab.List as="span" className="inline-block rounded bg-custom-background-80 p-1">
<Tab.List as="span" className="inline-block rounded bg-custom-background-80 p-1"> {tabOptions.map((tab) => {
{tabOptions.map((tab) => ( if (!unsplashImages && unsplashError && tab.key === "unsplash") return null;
if (projectCoverImages && projectCoverImages.length === 0 && tab.key === "images")
return null;
return (
<Tab <Tab
key={tab.key} key={tab.key}
className={({ selected }) => className={({ selected }) =>
@ -160,50 +168,106 @@ export const ImagePickerPopover: React.FC<Props> = ({
> >
{tab.title} {tab.title}
</Tab> </Tab>
))} );
</Tab.List> })}
</div> </Tab.List>
<Tab.Panels className="h-full w-full flex-1 overflow-y-auto overflow-x-hidden"> <Tab.Panels className="h-full w-full flex-1 overflow-y-auto overflow-x-hidden">
<Tab.Panel className="h-full w-full space-y-4"> {(unsplashImages || !unsplashError) && (
<div className="flex gap-x-2 pt-7"> <Tab.Panel className="h-full w-full space-y-4 mt-4">
<Input <div className="flex gap-x-2">
name="search" <Input
className="text-sm" name="search"
id="search" className="text-sm"
value={formData.search} id="search"
onChange={(e) => setFormData({ ...formData, search: e.target.value })} value={formData.search}
placeholder="Search for images" onChange={(e) => setFormData({ ...formData, search: e.target.value })}
/> placeholder="Search for images"
<PrimaryButton onClick={() => setSearchParams(formData.search)} size="sm"> />
Search <PrimaryButton onClick={() => setSearchParams(formData.search)} size="sm">
</PrimaryButton> Search
</div> </PrimaryButton>
{images ? ( </div>
<div className="grid grid-cols-4 gap-4"> {unsplashImages ? (
{images.map((image) => ( unsplashImages.length > 0 ? (
<div <div className="grid grid-cols-4 gap-4">
key={image.id} {unsplashImages.map((image) => (
className="relative col-span-2 aspect-video md:col-span-1" <div
> key={image.id}
<img className="relative col-span-2 aspect-video md:col-span-1"
src={image.urls.small} onClick={() => {
alt={image.alt_description} setIsOpen(false);
className="cursor-pointer rounded absolute top-0 left-0 h-full w-full object-cover" onChange(image.urls.regular);
onClick={() => { }}
setIsOpen(false); >
onChange(image.urls.regular); <img
}} src={image.urls.small}
/> alt={image.alt_description}
className="cursor-pointer rounded absolute top-0 left-0 h-full w-full object-cover"
/>
</div>
))}
</div> </div>
))} ) : (
</div> <p className="text-center text-custom-text-300 text-xs pt-7">
) : ( No images found.
<div className="flex justify-center pt-20"> </p>
<Spinner /> )
</div> ) : (
)} <Loader className="grid grid-cols-4 gap-4">
</Tab.Panel> <Loader.Item height="80px" width="100%" />
<Tab.Panel className="h-full w-full pt-5"> <Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
</Loader>
)}
</Tab.Panel>
)}
{(!projectCoverImages || projectCoverImages.length !== 0) && (
<Tab.Panel className="h-full w-full space-y-4 mt-4">
{projectCoverImages ? (
projectCoverImages.length > 0 ? (
<div className="grid grid-cols-4 gap-4">
{projectCoverImages.map((image, index) => (
<div
key={image}
className="relative col-span-2 aspect-video md:col-span-1"
onClick={() => {
setIsOpen(false);
onChange(image);
}}
>
<img
src={image}
alt={`Default project cover image- ${index}`}
className="cursor-pointer rounded absolute top-0 left-0 h-full w-full object-cover"
/>
</div>
))}
</div>
) : (
<p className="text-center text-custom-text-300 text-xs pt-7">
No images found.
</p>
)
) : (
<Loader className="grid grid-cols-4 gap-4 pt-4">
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
</Loader>
)}
</Tab.Panel>
)}
<Tab.Panel className="h-full w-full mt-4">
<div className="w-full h-full flex flex-col gap-y-2"> <div className="w-full h-full flex flex-col gap-y-2">
<div className="flex items-center gap-3 w-full flex-1"> <div className="flex items-center gap-3 w-full flex-1">
<div <div

View File

@ -393,7 +393,7 @@ export const CreateProjectModal: React.FC<Props> = ({
value={value} value={value}
onChange={onChange} onChange={onChange}
options={options} options={options}
buttonClassName="!px-2 shadow-md" buttonClassName="border-[0.5px] !px-2 shadow-md"
label={ label={
<div className="flex items-center justify-center gap-2 py-[1px]"> <div className="flex items-center justify-center gap-2 py-[1px]">
{value ? ( {value ? (

View File

@ -15,6 +15,7 @@ const nextConfig = {
"vinci-web.s3.amazonaws.com", "vinci-web.s3.amazonaws.com",
"planefs-staging.s3.ap-south-1.amazonaws.com", "planefs-staging.s3.ap-south-1.amazonaws.com",
"planefs.s3.amazonaws.com", "planefs.s3.amazonaws.com",
"planefs-staging.s3.amazonaws.com",
"images.unsplash.com", "images.unsplash.com",
"avatars.githubusercontent.com", "avatars.githubusercontent.com",
"localhost", "localhost",

View File

@ -76,21 +76,23 @@ class FileServices extends APIService {
}); });
} }
async getUnsplashImages(page: number = 1, query?: string): Promise<UnSplashImage[]> { async getUnsplashImages(query?: string): Promise<UnSplashImage[]> {
const url = "/api/unsplash"; return this.get(`/api/unsplash/`, {
return this.request({
method: "get",
url,
params: { params: {
page,
per_page: 20,
query, query,
}, },
}) })
.then((response) => response?.data?.results ?? response?.data) .then((res) => res?.data?.results ?? res?.data)
.catch((error) => { .catch((err) => {
throw error?.response?.data; throw err?.response?.data;
});
}
async getProjectCoverImages(): Promise<string[]> {
return this.get(`/api/project-covers/`)
.then((res) => res?.data)
.catch((err) => {
throw err?.response?.data;
}); });
} }
} }