mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-02-22 06:54:24 +01:00
feat: add urls to project modals, update filters in URL structure, hide isHidden=true categories
This commit is contained in:
1245
package-lock.json
generated
1245
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -59,6 +59,7 @@
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"marked": "^4.0.14",
|
||||
"next": "^13.0.1",
|
||||
"nexus": "^1.3.0",
|
||||
"node-sass": "^7.0.1",
|
||||
"nostr-tools": "^0.23.4",
|
||||
@@ -89,6 +90,7 @@
|
||||
"react-toastify": "^9.0.8",
|
||||
"react-tooltip": "^4.2.21",
|
||||
"react-topbar-progress-indicator": "^4.1.1",
|
||||
"react-url-modal": "^0.4.3",
|
||||
"remirror": "^1.0.77",
|
||||
"serverless-http": "^3.0.1",
|
||||
"stream": "npm:stream-browserify",
|
||||
|
||||
33
src/App.tsx
33
src/App.tsx
@@ -10,7 +10,10 @@ import ProtectedRoute from "./Components/ProtectedRoute/ProtectedRoute";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { NavbarLayout } from "./utils/routing/layouts";
|
||||
import { Loadable, PAGES_ROUTES } from "./utils/routing";
|
||||
|
||||
import { URLModal } from "react-url-modal";
|
||||
import ProjectDetailsCard from 'src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard';
|
||||
import Modal from "./Components/Modals/Modal/Modal";
|
||||
import ReactModal from "react-modal";
|
||||
|
||||
// Pages
|
||||
// const FeedPage = Loadable(React.lazy(() => import( /* webpackChunkName: "feed_page" */ "./features/Posts/pages/FeedPage/FeedPage")))
|
||||
@@ -33,6 +36,27 @@ import { Loadable, PAGES_ROUTES } from "./utils/routing";
|
||||
|
||||
const ExplorePage = Loadable(React.lazy(() => import( /* webpackChunkName: "explore_page" */ "src/features/Projects/pages/ExplorePage/ExplorePage")))
|
||||
|
||||
const ModalWrapper = ({ children, onClose, visible }: any) => {
|
||||
return <ReactModal
|
||||
isOpen={visible}
|
||||
onRequestClose={onClose}
|
||||
overlayClassName='fixed w-full inset-0 overflow-x-hidden z-[2020] no-scrollbar'
|
||||
className=' '
|
||||
closeTimeoutMS={1000}
|
||||
contentElement={(_props, children) => <div {..._props} className={`
|
||||
${_props.className}
|
||||
w-screen min-h-screen relative flex flex-col justify-center items-center inset-0
|
||||
`}>
|
||||
<div
|
||||
onClick={onClose}
|
||||
className={`absolute w-full h-full top-0 left-0 bg-gray-300 bg-opacity-50`}
|
||||
></div>
|
||||
{children}
|
||||
</div>}
|
||||
>
|
||||
{children}
|
||||
</ReactModal>
|
||||
}
|
||||
|
||||
|
||||
function App() {
|
||||
@@ -89,6 +113,13 @@ function App() {
|
||||
|
||||
/>
|
||||
</Helmet>
|
||||
<URLModal
|
||||
adapter={null}
|
||||
Wrapper={ModalWrapper}
|
||||
modals={{
|
||||
projectDetails: ProjectDetailsCard,
|
||||
}}
|
||||
/>
|
||||
<Suspense fallback={<LoadingPage />}>
|
||||
<Routes>
|
||||
{/* <Route path={PAGES_ROUTES.blog.writeStory} element={<ProtectedRoute><CreatePostPage initType="story" /></ProtectedRoute>} /> */}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
query AllCategories($filter: JSON) {
|
||||
categoryList(_filter: $filter) {
|
||||
categoryList(isHidden: false, _filter: $filter) {
|
||||
projectsCount
|
||||
id
|
||||
name
|
||||
|
||||
@@ -42,22 +42,26 @@ export default function Header(props: Props) {
|
||||
absolute top-24 left-24 md:top-1/2 md:left-40 md:-translate-y-1/2
|
||||
rounded-full text-center flex justify-center items-center">
|
||||
<FiArrowLeft className=' inline-block text-body2 lg:text-body1' /></Link> */}
|
||||
<h1
|
||||
className='text-primary-500 text-h1 font-medium'
|
||||
>{title}</h1>
|
||||
{subtitle &&
|
||||
<p className="text-gray-600 font-medium text-body1">{subtitle}</p>
|
||||
}
|
||||
{onSearchPage && <div className=" ">
|
||||
<p className="text-gray-500 font-medium text-body4 mb-24 text-center">filtered by</p>
|
||||
<div className="flex gap-8 flex-wrap">
|
||||
{filters?.yearFounded && <Badge size='sm'>📆 Founded in <span className='font-bold text-gray-700'>{filters.yearFounded}</span> <button onClick={() => removeFilter("yearFounded")} className='ml-4 text-gray-600 hover:scale-125'><MdClose /></button> </Badge>}
|
||||
{filters?.projectStatus && <Badge size='sm'>🌱 Status: <span className='font-bold text-gray-700'>{filters?.projectStatus}</span> <button onClick={() => removeFilter("projectStatus")} className='ml-4 text-gray-600 hover:scale-125'><MdClose /></button> </Badge>}
|
||||
{filters?.projectLicense && <Badge size='sm'>💻 License: <span className='font-bold text-gray-700'>{filters.projectLicense}</span> <button onClick={() => removeFilter("projectLicense")} className='ml-4 text-gray-600 hover:scale-125'><MdClose /></button> </Badge>}
|
||||
{filters?.categories && filters.categories.length > 0 && <Badge size='sm'>Category: <span className='font-bold text-gray-700'>{filters.categories[0].label}</span> <button onClick={() => removeFilter("categories")} className='ml-4 text-gray-600 hover:scale-125'><MdClose /></button> </Badge>}
|
||||
{filters?.tags && filters.tags.length > 0 && <Badge size='sm'>Tags: <span className='font-bold text-gray-700'>{filters.tags.map(t => t.label).join(', ')}</span> <button onClick={() => removeFilter("tags")} className='ml-4 text-gray-600 hover:scale-125'><MdClose /></button> </Badge>}
|
||||
<div className="content-container">
|
||||
<div className="flex flex-col justify-center items-center gap-8">
|
||||
<h1
|
||||
className='text-primary-500 text-h1 font-medium'
|
||||
>{title}</h1>
|
||||
{subtitle &&
|
||||
<p className="text-gray-600 font-medium text-body1">{subtitle}</p>
|
||||
}
|
||||
{!filtersEmpty && <div className=" ">
|
||||
<p className="text-gray-500 font-medium text-body4 mb-8 mt-8 text-center">filtered by</p>
|
||||
<div className="flex gap-8 flex-wrap">
|
||||
{filters?.yearFounded && <Badge size='sm'>📆 Founded in <span className='font-bold text-gray-700'>{filters.yearFounded}</span> <button onClick={() => removeFilter("yearFounded")} className='ml-4 text-gray-600 hover:scale-125'><MdClose /></button> </Badge>}
|
||||
{filters?.projectStatus && <Badge size='sm'>🌱 Status: <span className='font-bold text-gray-700'>{filters?.projectStatus}</span> <button onClick={() => removeFilter("projectStatus")} className='ml-4 text-gray-600 hover:scale-125'><MdClose /></button> </Badge>}
|
||||
{filters?.projectLicense && <Badge size='sm'>💻 License: <span className='font-bold text-gray-700'>{filters.projectLicense}</span> <button onClick={() => removeFilter("projectLicense")} className='ml-4 text-gray-600 hover:scale-125'><MdClose /></button> </Badge>}
|
||||
{filters?.categories && filters.categories.length > 0 && <Badge size='sm'>Category: <span className='font-bold text-gray-700'>{filters.categories[0].label}</span> <button onClick={() => removeFilter("categories")} className='ml-4 text-gray-600 hover:scale-125'><MdClose /></button> </Badge>}
|
||||
{filters?.tags && filters.tags.length > 0 && <Badge size='sm'>Tags: <span className='font-bold text-gray-700'>{filters.tags.map(t => t.label).join(', ')}</span> <button onClick={() => removeFilter("tags")} className='ml-4 text-gray-600 hover:scale-125'><MdClose /></button> </Badge>}
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
|
||||
import { openModal } from "react-url-modal";
|
||||
import ProjectCardMini from "src/features/Projects/Components/ProjectCardMini/ProjectCardMini";
|
||||
import ProjectCardMiniSkeleton from 'src/features/Projects/Components/ProjectCardMini/ProjectCardMini.Skeleton';
|
||||
import { openModal } from 'src/redux/features/modals.slice';
|
||||
// import { openModal } from 'src/redux/features/modals.slice';
|
||||
import { useAppDispatch } from 'src/utils/hooks';
|
||||
import { ProjectCard } from 'src/utils/interfaces';
|
||||
|
||||
@@ -16,13 +17,19 @@ export default function ProjectsGrid({ isLoading, isLoadingMore, projects }: Pro
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleClick = (projectId: string) => {
|
||||
dispatch(openModal({
|
||||
Modal: "ProjectDetailsCard",
|
||||
isPageModal: true,
|
||||
props: {
|
||||
// dispatch(openModal({
|
||||
// Modal: "ProjectDetailsCard",
|
||||
// isPageModal: true,
|
||||
// props: {
|
||||
// projectId
|
||||
// }
|
||||
// }))
|
||||
openModal({
|
||||
name: "projectDetails",
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -31,6 +31,18 @@ export function removeEmptyFitlers(filters: Partial<ProjectsFilters>): Partial<P
|
||||
return res;
|
||||
}
|
||||
|
||||
const extractNonFiltersParams = (params?: Record<string, any> & Partial<ProjectsFilters>) => {
|
||||
const filtersKeys: (keyof ProjectsFilters)[] = ['categories', 'projectLicense', 'tags', 'projectStatus', 'yearFounded'];
|
||||
if (!params) return {};
|
||||
let res: Record<string, any> = {};
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (filtersKeys.includes(key as any)) continue;
|
||||
res[key] = value;
|
||||
}
|
||||
console.log(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
export const useUpdateUrlWithFilters = (state?: Partial<ProjectsFilters> | null) => {
|
||||
const location = useLocation()
|
||||
@@ -38,14 +50,21 @@ export const useUpdateUrlWithFilters = (state?: Partial<ProjectsFilters> | null)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const allParams = qs.parse(window.location.search.slice(1))
|
||||
console.log(allParams);
|
||||
|
||||
const nonFiltersParams = extractNonFiltersParams(allParams);
|
||||
const filtersParams = removeEmptyFitlers({ ...state } ?? {});
|
||||
|
||||
|
||||
const queryString = qs.stringify(
|
||||
removeEmptyFitlers(state ?? {}),
|
||||
{ ...filtersParams, ...nonFiltersParams },
|
||||
{ skipNulls: true }
|
||||
)
|
||||
|
||||
navigate(`${location.pathname}?${queryString}`, { replace: true });
|
||||
navigate(`${window.location.pathname}?${queryString}`, { replace: true });
|
||||
|
||||
}, [location.pathname, location.search, navigate, state]);
|
||||
}, [navigate, state]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
|
||||
import { MdLocalFireDepartment } from 'react-icons/md';
|
||||
import { ModalCard } from 'src/Components/Modals/ModalsContainer/ModalsContainer';
|
||||
import { useAppDispatch, useAppSelector, useMediaQuery } from 'src/utils/hooks';
|
||||
import { openModal, scheduleModal } from 'src/redux/features/modals.slice';
|
||||
import { Direction, openModal, scheduleModal } from 'src/redux/features/modals.slice';
|
||||
import { setProject } from 'src/redux/features/project.slice';
|
||||
import Button from 'src/Components/Button/Button';
|
||||
import ProjectCardSkeleton from './ProjectDetailsCard.Skeleton'
|
||||
@@ -24,10 +24,13 @@ import { CgGitFork } from 'react-icons/cg';
|
||||
|
||||
|
||||
interface Props extends ModalCard {
|
||||
projectId: string;
|
||||
params: {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default function ProjectDetailsCard({ direction, projectId, ...props }: Props) {
|
||||
export default function ProjectDetailsCard({ params: { projectId }, ...props }: Props) {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const [screenshotsOpen, setScreenshotsOpen] = useState(-1);
|
||||
@@ -64,7 +67,7 @@ export default function ProjectDetailsCard({ direction, projectId, ...props }: P
|
||||
|
||||
if (error)
|
||||
return <div
|
||||
className={`modal-card max-w-[768px] ${props.isPageModal && !isMdScreen && 'rounded-0 w-full min-h-screen'}`}
|
||||
className={`modal-card max-w-[768px] ${!isMdScreen && 'rounded-0 w-full min-h-screen'}`}
|
||||
>
|
||||
<div className="p-64">
|
||||
<ErrorMessage type='fetching' message='Something Wrong happened while fetching project details, please try refreshing the page' />
|
||||
@@ -72,7 +75,7 @@ export default function ProjectDetailsCard({ direction, projectId, ...props }: P
|
||||
</div>
|
||||
|
||||
if (loading)
|
||||
return <ProjectCardSkeleton onClose={closeModal} direction={direction} isPageModal={props.isPageModal} />;
|
||||
return <ProjectCardSkeleton onClose={closeModal} isPageModal={true} />;
|
||||
|
||||
|
||||
const project = data?.getProject?.[0];
|
||||
@@ -113,7 +116,7 @@ export default function ProjectDetailsCard({ direction, projectId, ...props }: P
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`modal-card max-w-[676px] ${(props.isPageModal && !isMdScreen) && '!rounded-0 w-full min-h-screen'}`}
|
||||
className={`modal-card max-w-[676px] ${(!isMdScreen) && '!rounded-0 w-full min-h-screen'}`}
|
||||
>
|
||||
{/* Cover Image */}
|
||||
<div className="relative h-[120px] lg:h-[80px] bg-gray-400">
|
||||
@@ -132,7 +135,7 @@ export default function ProjectDetailsCard({ direction, projectId, ...props }: P
|
||||
{/* Title & Basic Info */}
|
||||
<div className="flex flex-col mt-[-80px] md:flex-row md:mt-0 gap-24 md:items-center relative">
|
||||
<div className="flex-shrink-0 w-[108px] h-[108px]">
|
||||
<img className="w-full h-full object-cover border-2 border-gray-200 rounded-24" src={logo} alt="" />
|
||||
<img className="w-full h-full object-cover border-2 bg-white border-gray-200 rounded-24" src={logo} alt="" />
|
||||
</div>
|
||||
<div className='flex flex-col gap-8 items-start justify-between'>
|
||||
<a href={project?.website!} target='_blank' rel="noreferrer"><h3 className="text-body1 font-bold">{project?.title}</h3></a>
|
||||
|
||||
@@ -133,6 +133,7 @@ export type QueryCategoryListArgs = {
|
||||
description: InputMaybe<Scalars['String']>;
|
||||
icon: InputMaybe<Scalars['String']>;
|
||||
id: InputMaybe<Scalars['String']>;
|
||||
isHidden: InputMaybe<Scalars['Boolean']>;
|
||||
name: InputMaybe<Scalars['String']>;
|
||||
projectFromData: InputMaybe<Array<InputMaybe<Scalars['String']>>>;
|
||||
projectsCount: InputMaybe<Scalars['String']>;
|
||||
@@ -318,6 +319,7 @@ export type CategoryList = {
|
||||
description: Maybe<Scalars['String']>;
|
||||
icon: Maybe<Scalars['String']>;
|
||||
id: Maybe<Scalars['String']>;
|
||||
isHidden: Maybe<Scalars['Boolean']>;
|
||||
name: Maybe<Scalars['String']>;
|
||||
projectFromData: Maybe<Array<Maybe<Scalars['String']>>>;
|
||||
projectsCount: Maybe<Scalars['String']>;
|
||||
@@ -430,7 +432,7 @@ export type ProjectDetailsQuery = { __typename?: 'Query', getProject: Array<{ __
|
||||
|
||||
export const AllCategoriesDocument = gql`
|
||||
query AllCategories($filter: JSON) {
|
||||
categoryList(_filter: $filter) {
|
||||
categoryList(isHidden: false, _filter: $filter) {
|
||||
projectsCount
|
||||
id
|
||||
name
|
||||
|
||||
@@ -9,13 +9,13 @@ import { setIsMobileScreen } from 'src/redux/features/ui.slice';
|
||||
import { isMobileScreen } from './helperFunctions';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import 'react-loading-skeleton/dist/skeleton.css'
|
||||
import THEME from './theme';
|
||||
import { NotificationsService } from 'src/services';
|
||||
import ErrorPage from 'src/Components/Errors/ErrorPage/ErrorPage';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
|
||||
THEME.injectStyles();
|
||||
|
||||
let basename = '/';
|
||||
@@ -41,7 +41,6 @@ export const useWrapperSetup = () => {
|
||||
useResizeListener(resizeListener)
|
||||
}
|
||||
|
||||
|
||||
export default function Wrapper(props: any) {
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user