Skip to content

Commit c1296ed

Browse files
committed
feature: implement barebones of plugins page
1 parent 91e6c4a commit c1296ed

File tree

6 files changed

+149
-17
lines changed

6 files changed

+149
-17
lines changed

src/components/Plugins/PluginCard.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ExternalLink } from "lucide-react";
2+
import { Plugin } from "../../interfaces/Plugin";
3+
4+
type PluginCardProps = {
5+
plugin: Plugin;
6+
}
7+
export function PluginCard({plugin}:PluginCardProps) {
8+
return <section className="border gap-4 flex flex-col max-w-[314px] hover:scale-[1.01] p-4 pt-6 m-auto w-full border-[#27272A] rounded-md h-[300px]">
9+
<div className="flex-1 flex flex-col gap-4">
10+
<div>
11+
<div>
12+
<h1 className="text-white text-[22px] font-semibold">{plugin.name}</h1>
13+
{/*
14+
// TODO: should add and fetch data about user using the userId or a github handle linked to it
15+
*/}
16+
</div>
17+
</div>
18+
<div className="text-white text-sm">
19+
{plugin.description}
20+
</div>
21+
<div className="w-full">
22+
<img className="w-full" src={plugin.imageURL} />
23+
</div>
24+
</div>
25+
<div className="flex justify-between gap-3 sm:gap-1 flex-col sm:flex-row">
26+
<button className="bg-slate-900 text-[12px] px-4 pt-[6px] pb-[7px] rounded-md text-white">
27+
Read more
28+
</button>
29+
30+
<button className="bg-[#6d28d9] text-[12px] flex justify-center items-center gap-1 px-4 pt-[6px] pb-[7px] rounded-md text-white"><span>
31+
View in NPM
32+
</span>
33+
<ExternalLink className="w-4" /></button>
34+
</div>
35+
36+
</section>
37+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Search } from "lucide-react";
2+
import { useSearchParams } from "react-router-dom";
3+
4+
export default function PluginSearchBar() {
5+
const [searchParams, setSearchParams] = useSearchParams()
6+
const searchQuery = searchParams.get('searchQuery') || "";
7+
const setSearchQuery = (input: string) => {
8+
if(input == ""){
9+
searchParams.delete("searchQuery")
10+
setSearchParams(searchParams)
11+
} else {
12+
searchParams.set("searchQuery",input)
13+
setSearchParams(searchParams)
14+
}
15+
}
16+
return <div>
17+
<div className='bg-slate-900 border focus-within:ring-2 focus-within:ring-slate-300 border-slate-300 flex rounded-md gap-2 px-3 py-2 max-w-[500px] w-full'>
18+
<Search className='text-slate-300 w-4' />
19+
<input value={searchQuery} onChange={(e)=>setSearchQuery(e.target.value)} placeholder='Search plugins...' className='bg-transparent focus:border-0 focus:outline-none focus:ring-0 placeholder:text-sm text-slate-300 rounded-md w-full' />
20+
</div>
21+
</div>
22+
}

src/constants/Endpoints.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// endpoints for making requests
22
const Endpoints = {
33
fetchApiThemes: `${import.meta.env.VITE_GALLERY_API_URL}/api/v1/themes`,
4+
fetchApiPlugins: `${import.meta.env.VITE_GALLERY_API_URL}/api/v1/plugins`,
45
fetchUserProfile: `${import.meta.env.VITE_GALLERY_API_URL}/api/v1/users/profile`,
56
fetchCacheThemes: import.meta.env.VITE_GITHUB_THEMES_CACHE_URL,
67
loginUser: `${import.meta.env.VITE_GALLERY_API_URL}/api/v1/auth/login/process`,

src/hooks/useFetchPlugins.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { useEffect, useState } from "react";
2+
import { galleryApiFetch } from "../services/apiService";
3+
import { Plugin } from "../interfaces/Plugin";
4+
5+
/**
6+
* Fetches plugins from the backend api.
7+
*
8+
* @param url url to fetch plugins from
9+
* @param pageSize number of plugins to fetch each page
10+
* @param pageNum page number to fetch for
11+
* @param searchQuery search query for filtering plugins
12+
*/
13+
function useFetchPlugins(
14+
url: string,
15+
pageSize: number,
16+
pageNum: number,
17+
searchQuery?: string
18+
) {
19+
const [plugins, setPlugins] = useState<Plugin[]>([]);
20+
const [isLoading, setIsLoading] = useState(false);
21+
const [error, setError] = useState("");
22+
useEffect(function(){
23+
24+
const fetchPlugins = async() => {
25+
try{
26+
setIsLoading(true)
27+
if(searchQuery){
28+
url += `?searchQuery=${searchQuery}`
29+
}
30+
console.log(url)
31+
const data = await galleryApiFetch(url);
32+
const plugins = await data.json()
33+
setPlugins(plugins.data);
34+
} catch(e){
35+
36+
}
37+
finally{
38+
setIsLoading(false)
39+
}
40+
}
41+
42+
fetchPlugins();
43+
44+
},[searchQuery])
45+
46+
47+
48+
return {plugins, isLoading, error }
49+
}
50+
51+
52+
export default useFetchPlugins

src/interfaces/Plugin.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// plugin data fetched from both backend api
2+
export interface Plugin {
3+
id: string;
4+
name: string;
5+
description: string;
6+
favoritesCount: number;
7+
imageURL: string;
8+
createdAt: string;
9+
updatedAt: string;
10+
userId: string;
11+
}

src/pages/Plugins.tsx

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,38 @@
11
import React from 'react';
22

3-
import { Link } from 'react-router-dom';
3+
import { Endpoints } from '../constants/Endpoints';
4+
import useFetchPlugins from '../hooks/useFetchPlugins';
45

6+
import Skeleton from 'react-loading-skeleton';
7+
import { PluginCard } from '../components/Plugins/PluginCard';
8+
import PluginSearchBar from '../components/Plugins/PluginSearchBar';
9+
import { useSearchParams } from 'react-router-dom';
510
/**
611
* Displays plugins for users to search, browse and rate.
712
* // todo: dynamically load plugins as user scrolls instead of fetching wholesale from backend
813
*/
914
const Plugins: React.FC = () => {
15+
const [searchParams] = useSearchParams()
16+
const searchQuery = searchParams.get('searchQuery') || "";
17+
18+
const { plugins, isLoading, error } = useFetchPlugins(Endpoints.fetchApiPlugins, 30, 1,searchQuery );
19+
20+
1021
return (
11-
<div className="flex items-center justify-center h-screen bg-black">
12-
<div className="bg-white p-10 rounded-lg shadow-lg text-center max-w-screen-md">
13-
<h1 className="text-4xl font-bold mb-4 text-gray-800">Coming Soon</h1>
14-
<p className="text-lg text-gray-600 mb-8">
15-
Plugins are not ready. Please check back later!
16-
</p>
17-
<p className="text-md text-gray-500 mb-4">
18-
In the meantime, feel free to browse our available themes.
19-
</p>
20-
<Link
21-
to="/themes"
22-
className="inline-block bg-blue-500 text-white py-2 px-4 rounded-lg
23-
hover:bg-blue-600 transition duration-300"
24-
>
25-
Browse Themes
26-
</Link>
22+
<div className=" min-h-screen bg-black">
23+
<div className='p-8 flex flex-col gap-8 max-w-[1024px] m-auto'>
24+
<PluginSearchBar />
25+
<div className='grid xs:grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-16 gap-y-8'>
26+
{isLoading ? Array.from({length : 9}).map((_,i)=><Skeleton width="300px" containerClassName='m-auto' height="300px" />) :
27+
plugins.length > 0 && plugins.map(plugin =>{
28+
return <PluginCard plugin={plugin} />
29+
})}
30+
</div>
31+
{
32+
!isLoading && plugins.length == 0 &&
33+
<h1 className='text-white text-lg flex items-center justify-center '>No search results found...</h1>
34+
35+
}
2736
</div>
2837
</div>
2938
);

0 commit comments

Comments
 (0)