Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions space/services/file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,24 +74,6 @@ class FileServices extends APIService {
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();
Expand Down
2 changes: 0 additions & 2 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
"NEXT_PUBLIC_GITHUB_APP_NAME",
"NEXT_PUBLIC_ENABLE_SENTRY",
"NEXT_PUBLIC_ENABLE_OAUTH",
"NEXT_PUBLIC_UNSPLASH_ACCESS",
"NEXT_PUBLIC_UNSPLASH_ENABLED",
"NEXT_PUBLIC_TRACK_EVENTS",
"NEXT_PUBLIC_PLAUSIBLE_DOMAIN",
"NEXT_PUBLIC_CRISP_ID",
Expand Down
202 changes: 133 additions & 69 deletions web/components/core/image-picker-popover.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,23 @@
import React, { useEffect, useState, useRef, useCallback } from "react";

// next
import Image from "next/image";
import { useRouter } from "next/router";

// swr
import useSWR from "swr";

// react-dropdown
import { useDropzone } from "react-dropzone";

// headless ui
import { Tab, Transition, Popover } from "@headlessui/react";

// services
import fileService from "services/file.service";

// components
import { Input, Spinner, PrimaryButton, SecondaryButton } from "components/ui";
// hooks
import useWorkspaceDetails from "hooks/use-workspace-details";
import useOutsideClickDetector from "hooks/use-outside-click-detector";

const unsplashEnabled =
process.env.NEXT_PUBLIC_UNSPLASH_ENABLED === "true" ||
process.env.NEXT_PUBLIC_UNSPLASH_ENABLED === "1";
// components
import { Input, PrimaryButton, SecondaryButton, Loader } from "components/ui";

const tabOptions = [
{
key: "unsplash",
title: "Unsplash",
},
{
key: "images",
title: "Images",
Expand Down Expand Up @@ -64,8 +55,22 @@ export const ImagePickerPopover: React.FC<Props> = ({
search: "",
});

const { data: images } = useSWR(`UNSPLASH_IMAGES_${searchParams}`, () =>
fileService.getUnsplashImages(1, searchParams)
const { data: unsplashImages, error: unsplashError } = useSWR(
`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);
Expand Down Expand Up @@ -115,18 +120,17 @@ export const ImagePickerPopover: React.FC<Props> = ({
};

useEffect(() => {
if (!images || value !== null) return;
onChange(images[0].urls.regular);
}, [value, onChange, images]);
if (!unsplashImages || value !== null) return;

useOutsideClickDetector(imagePickerRef, () => setIsOpen(false));
onChange(unsplashImages[0].urls.regular);
}, [value, onChange, unsplashImages]);

if (!unsplashEnabled) return null;
useOutsideClickDetector(imagePickerRef, () => setIsOpen(false));

return (
<Popover className="relative z-[2]" ref={ref}>
<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)}
disabled={disabled}
>
Expand All @@ -141,15 +145,19 @@ export const ImagePickerPopover: React.FC<Props> = ({
leaveFrom="transform opacity-100 scale-100"
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
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"
>
<Tab.Group>
<div>
<Tab.List as="span" className="inline-block rounded bg-custom-background-80 p-1">
{tabOptions.map((tab) => (
<Tab.List as="span" className="inline-block rounded bg-custom-background-80 p-1">
{tabOptions.map((tab) => {
if (!unsplashImages && unsplashError && tab.key === "unsplash") return null;
if (projectCoverImages && projectCoverImages.length === 0 && tab.key === "images")
return null;

return (
<Tab
key={tab.key}
className={({ selected }) =>
Expand All @@ -160,50 +168,106 @@ export const ImagePickerPopover: React.FC<Props> = ({
>
{tab.title}
</Tab>
))}
</Tab.List>
</div>
);
})}
</Tab.List>
<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">
<div className="flex gap-x-2 pt-7">
<Input
name="search"
className="text-sm"
id="search"
value={formData.search}
onChange={(e) => setFormData({ ...formData, search: e.target.value })}
placeholder="Search for images"
/>
<PrimaryButton onClick={() => setSearchParams(formData.search)} size="sm">
Search
</PrimaryButton>
</div>
{images ? (
<div className="grid grid-cols-4 gap-4">
{images.map((image) => (
<div
key={image.id}
className="relative col-span-2 aspect-video md:col-span-1"
>
<img
src={image.urls.small}
alt={image.alt_description}
className="cursor-pointer rounded absolute top-0 left-0 h-full w-full object-cover"
onClick={() => {
setIsOpen(false);
onChange(image.urls.regular);
}}
/>
</div>
))}
</div>
) : (
<div className="flex justify-center pt-20">
<Spinner />
{(unsplashImages || !unsplashError) && (
<Tab.Panel className="h-full w-full space-y-4 mt-4">
<div className="flex gap-x-2">
<Input
name="search"
className="text-sm"
id="search"
value={formData.search}
onChange={(e) => setFormData({ ...formData, search: e.target.value })}
placeholder="Search for images"
/>
<PrimaryButton onClick={() => setSearchParams(formData.search)} size="sm">
Search
</PrimaryButton>
</div>
)}
</Tab.Panel>
<Tab.Panel className="h-full w-full pt-5">
{unsplashImages ? (
unsplashImages.length > 0 ? (
<div className="grid grid-cols-4 gap-4">
{unsplashImages.map((image) => (
<div
key={image.id}
className="relative col-span-2 aspect-video md:col-span-1"
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>
) : (
<p className="text-center text-custom-text-300 text-xs pt-7">
No images found.
</p>
)
) : (
<Loader className="grid grid-cols-4 gap-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>
)}
{(!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="flex items-center gap-3 w-full flex-1">
<div
Expand Down
2 changes: 1 addition & 1 deletion web/components/project/create-project-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ export const CreateProjectModal: React.FC<Props> = ({
value={value}
onChange={onChange}
options={options}
buttonClassName="!px-2 shadow-md"
buttonClassName="border-[0.5px] !px-2 shadow-md"
label={
<div className="flex items-center justify-center gap-2 py-[1px]">
{value ? (
Expand Down
1 change: 1 addition & 0 deletions web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const nextConfig = {
"vinci-web.s3.amazonaws.com",
"planefs-staging.s3.ap-south-1.amazonaws.com",
"planefs.s3.amazonaws.com",
"planefs-staging.s3.amazonaws.com",
"images.unsplash.com",
"avatars.githubusercontent.com",
"localhost",
Expand Down
24 changes: 13 additions & 11 deletions web/services/file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,23 @@ class FileServices extends APIService {
});
}

async getUnsplashImages(page: number = 1, query?: string): Promise<UnSplashImage[]> {
const url = "/api/unsplash";

return this.request({
method: "get",
url,
async getUnsplashImages(query?: string): Promise<UnSplashImage[]> {
return this.get(`/api/unsplash/`, {
params: {
page,
per_page: 20,
query,
},
})
.then((response) => response?.data?.results ?? response?.data)
.catch((error) => {
throw error?.response?.data;
.then((res) => res?.data?.results ?? res?.data)
.catch((err) => {
throw err?.response?.data;
});
}

async getProjectCoverImages(): Promise<string[]> {
return this.get(`/api/project-covers/`)
.then((res) => res?.data)
.catch((err) => {
throw err?.response?.data;
});
}
}
Expand Down