mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-01-03 22:44:24 +01:00
Merge branch 'dev' into nostr
This commit is contained in:
@@ -17,7 +17,7 @@ export default function TrendingCard() {
|
||||
<ul className='flex flex-col'>
|
||||
{
|
||||
trendingPosts.loading ?
|
||||
Array(4).fill(0).map((_, idx) => <li key={idx} className="flex items-start gap-8">
|
||||
Array(4).fill(0).map((_, idx) => <li key={idx} className="flex items-start gap-8 border-b py-16 last-of-type:border-b-0">
|
||||
<Skeleton circle width={24} height={24} />
|
||||
<p className="text-body5 font-medium flex-grow"><Skeleton width={'80%'} />
|
||||
<Skeleton width={`${random(30, 65)}%`} /></p>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import dayjs from "dayjs";
|
||||
import { Link } from "react-router-dom";
|
||||
import Button from "src/Components/Button/Button";
|
||||
import { Author } from "src/features/Posts/types";
|
||||
import Avatar from "src/features/Profiles/Components/Avatar/Avatar";
|
||||
import { trimText } from "src/utils/helperFunctions";
|
||||
import { createRoute } from "src/utils/routing";
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
|
||||
|
||||
export default function AuthorCardSkeleton() {
|
||||
return (
|
||||
<div className="bg-white p-16 border-2 border-gray-200 rounded-12">
|
||||
<div className='flex gap-8'>
|
||||
<Skeleton circle width={48} height={48} />
|
||||
<div className="overflow-hidden">
|
||||
<p className={`'text-body4' text-black font-medium overflow-hidden text-ellipsis whitespace-nowrap`}><Skeleton width={'15ch'} /></p>
|
||||
<p className={`text-body6 text-gray-600`}><Skeleton width={'20ch'} /></p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
fullWidth
|
||||
color="gray"
|
||||
className="mt-16">
|
||||
<div className="opacity-0">Hidden</div>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
import Skeleton from "react-loading-skeleton";
|
||||
import Badge from "src/Components/Badge/Badge";
|
||||
import HeaderSkeleton from "src/features/Posts/Components/PostCard/Header/Header.Skeleton";
|
||||
|
||||
|
||||
|
||||
export default function PageContentSkeleton() {
|
||||
return <div id="content" className="bg-white md:p-32 md:border-2 border-gray-200 rounded-16 relative">
|
||||
<div className="flex flex-col gap-24 relative">
|
||||
<h1 className="text-[42px] leading-[58px] font-bolder">
|
||||
<Skeleton width={'min(80%,16ch)'} />
|
||||
</h1>
|
||||
<HeaderSkeleton />
|
||||
<div className="flex flex-wrap gap-8">
|
||||
{Array(3).fill(0).map(i => <Badge key={i} size='sm'>
|
||||
<div className="opacity-0">hidden</div>
|
||||
</Badge>)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`mt-42 text-body4`}>
|
||||
<Skeleton width={'100%'} />
|
||||
<Skeleton width={'90%'} />
|
||||
<Skeleton width={'92%'} />
|
||||
<Skeleton width={'70%'} />
|
||||
<div className="mt-32"></div>
|
||||
<Skeleton width={'100%'} />
|
||||
<Skeleton width={'92%'} />
|
||||
<Skeleton width={'95%'} />
|
||||
<Skeleton width={'40%'} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { BsBookmark } from "react-icons/bs"
|
||||
import { FiArrowLeft } from "react-icons/fi"
|
||||
import { MdIosShare } from "react-icons/md"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import VoteButton from "src/Components/VoteButton/VoteButton"
|
||||
import { Post } from "src/features/Posts/types"
|
||||
import { Vote_Item_Type } from "src/graphql"
|
||||
import { useVote } from "src/utils/hooks"
|
||||
|
||||
|
||||
export default function PostActionsSkeleton() {
|
||||
|
||||
const actions = [
|
||||
{
|
||||
icon: MdIosShare,
|
||||
value: '--'
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button className={`
|
||||
hidden md:flex w-full aspect-square bg-white rounded-12 border-2 border-gray-200 justify-around items-center text-gray-500 hover:bg-gray-50 active:bg-gray-100
|
||||
`}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<FiArrowLeft className={"text-body1"} />
|
||||
</button>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export default function StoryPageContent({ story }: Props) {
|
||||
|
||||
{story.cover_image &&
|
||||
<img src={story.cover_image}
|
||||
className='w-full h-[120px] md:h-[240px] object-cover rounded-12 mb-16'
|
||||
className='w-full object-cover rounded-12 md:rounded-16 mb-16'
|
||||
alt="" />}
|
||||
<div className="flex flex-col gap-24 relative">
|
||||
{curUser?.id === story.author.id && <Menu
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import LoadingPage from 'src/Components/LoadingPage/LoadingPage'
|
||||
import NotFoundPage from 'src/features/Shared/pages/NotFoundPage/NotFoundPage'
|
||||
import { usePostDetailsQuery } from 'src/graphql'
|
||||
import { capitalize } from 'src/utils/helperFunctions'
|
||||
import { useAppSelector, } from 'src/utils/hooks'
|
||||
import { PostCardSkeleton } from '../../Components/PostCard'
|
||||
import TrendingCard from '../../Components/TrendingCard/TrendingCard'
|
||||
import AuthorCard from './Components/AuthorCard/AuthorCard'
|
||||
import AuthorCardSkeleton from './Components/AuthorCard/AuthorCard.skeleton'
|
||||
import PageContent from './Components/PageContent/PageContent'
|
||||
import PageContentSkeleton from './Components/PageContent/PageContent.skeleton'
|
||||
import PostActions from './Components/PostActions/PostActions'
|
||||
import PostActionsSkeleton from './Components/PostActions/PostActions.skeleton'
|
||||
import styles from './styles.module.scss'
|
||||
|
||||
|
||||
export default function PostDetailsPageSkeleton() {
|
||||
|
||||
|
||||
const { navHeight } = useAppSelector((state) => ({
|
||||
navHeight: state.ui.navHeight
|
||||
}));
|
||||
|
||||
return (
|
||||
|
||||
<div
|
||||
className={`page-container grid pt-16 w-full gap-32 ${styles.grid}`}
|
||||
>
|
||||
<aside id='actions' className='no-scrollbar'>
|
||||
<div className="sticky"
|
||||
style={{
|
||||
top: `${navHeight + 16}px`,
|
||||
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
|
||||
}}>
|
||||
<PostActionsSkeleton />
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
|
||||
<PageContentSkeleton />
|
||||
<aside id='author' className='no-scrollbar min-w-0'>
|
||||
<div className="flex flex-col gap-24"
|
||||
style={{
|
||||
top: `${navHeight + 16}px`,
|
||||
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
|
||||
overflowY: "scroll",
|
||||
}}>
|
||||
<AuthorCardSkeleton />
|
||||
<TrendingCard />
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import LoadingPage from 'src/Components/LoadingPage/LoadingPage'
|
||||
import NotFoundPage from 'src/features/Shared/pages/NotFoundPage/NotFoundPage'
|
||||
import { Post_Type, usePostDetailsQuery } from 'src/graphql'
|
||||
import { capitalize } from 'src/utils/helperFunctions'
|
||||
import { useAppSelector, } from 'src/utils/hooks'
|
||||
import { CommentsSection } from '../../Components/Comments'
|
||||
import ScrollToTop from 'src/utils/routing/scrollToTop'
|
||||
import TrendingCard from '../../Components/TrendingCard/TrendingCard'
|
||||
import AuthorCard from './Components/AuthorCard/AuthorCard'
|
||||
import PageContent from './Components/PageContent/PageContent'
|
||||
import PostActions from './Components/PostActions/PostActions'
|
||||
import PostDetailsPageSkeleton from './PostDetailsPage.skeleton'
|
||||
import styles from './styles.module.scss'
|
||||
|
||||
|
||||
@@ -32,7 +33,7 @@ export default function PostDetailsPage() {
|
||||
}));
|
||||
|
||||
if (postDetailsQuery.loading)
|
||||
return <LoadingPage />
|
||||
return <PostDetailsPageSkeleton />
|
||||
|
||||
const post = postDetailsQuery.data?.getPostById;
|
||||
|
||||
@@ -45,6 +46,7 @@ export default function PostDetailsPage() {
|
||||
<title>{post.title}</title>
|
||||
<meta property="og:title" content={post.title} />
|
||||
</Helmet>
|
||||
<ScrollToTop />
|
||||
<div
|
||||
className={`page-container grid pt-16 w-full gap-32 ${styles.grid}`}
|
||||
>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
}
|
||||
|
||||
@include gt-lg {
|
||||
grid-template-columns: auto 1fr calc(min(30%, 326px));
|
||||
grid-template-columns: 116px 1fr calc(min(30%, 326px));
|
||||
grid-template-areas:
|
||||
"actions content author"
|
||||
". comments ."
|
||||
|
||||
38
src/utils/routing/FallbackProvider.tsx
Normal file
38
src/utils/routing/FallbackProvider.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import * as React from 'react';
|
||||
import LoadingPage from 'src/Components/LoadingPage/LoadingPage';
|
||||
|
||||
export type FallbackType = NonNullable<React.ReactNode> | null;
|
||||
|
||||
export interface FallbackContextType {
|
||||
updateFallback: (fallback: FallbackType) => void;
|
||||
}
|
||||
|
||||
export const FallbackContext = React.createContext<FallbackContextType>({
|
||||
updateFallback: () => { },
|
||||
});
|
||||
|
||||
interface FabllbackProviderProps {
|
||||
}
|
||||
|
||||
export const FallbackProvider: React.FC<React.PropsWithChildren<FabllbackProviderProps>> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [fallback, setFallback] = React.useState<FallbackType>(null);
|
||||
|
||||
const updateFallback = React.useCallback((fallback: FallbackType) => {
|
||||
setFallback(() => <>
|
||||
<LoadingPage />
|
||||
{fallback}
|
||||
</>);
|
||||
}, []);
|
||||
|
||||
const renderChildren = React.useMemo(() => {
|
||||
return children;
|
||||
}, [children]);
|
||||
|
||||
return (
|
||||
<FallbackContext.Provider value={{ updateFallback }}>
|
||||
<React.Suspense fallback={fallback}>{renderChildren}</React.Suspense>
|
||||
</FallbackContext.Provider>
|
||||
);
|
||||
};
|
||||
21
src/utils/routing/Page.tsx
Normal file
21
src/utils/routing/Page.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as React from 'react';
|
||||
import { usePage } from './usePage';
|
||||
|
||||
interface LoadablePagePageProps { }
|
||||
|
||||
const LoadablePage: React.FC<React.PropsWithChildren<LoadablePagePageProps>> = ({ children }) => {
|
||||
const { onLoad } = usePage();
|
||||
|
||||
const render = React.useMemo(() => {
|
||||
return <>{children}</>;
|
||||
}, [children]);
|
||||
|
||||
React.useEffect(() => {
|
||||
onLoad(render);
|
||||
}, [onLoad, render]);
|
||||
|
||||
|
||||
return render;
|
||||
};
|
||||
|
||||
export default LoadablePage;
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Outlet, } from 'react-router-dom';
|
||||
import Navbar from "src/Components/Navbar/Navbar";
|
||||
import { FallbackProvider } from '../FallbackProvider';
|
||||
|
||||
export const NavbarLayout = () => {
|
||||
return <>
|
||||
<Navbar />
|
||||
<Outlet />
|
||||
<FallbackProvider>
|
||||
<Outlet />
|
||||
</FallbackProvider>
|
||||
</>
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Suspense } from "react";
|
||||
import LoadingPage from "src/Components/LoadingPage/LoadingPage";
|
||||
import LoadablePage from "./Page";
|
||||
|
||||
export const Loadable = (Component: any, Loading = LoadingPage) => (props: any) => (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<LoadablePage>
|
||||
<Component {...props} />
|
||||
</Suspense>
|
||||
</LoadablePage>
|
||||
);
|
||||
|
||||
12
src/utils/routing/scrollToTop.tsx
Normal file
12
src/utils/routing/scrollToTop.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useEffect } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
export default function ScrollToTop() {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, [pathname]);
|
||||
|
||||
return null;
|
||||
}
|
||||
16
src/utils/routing/usePage.ts
Normal file
16
src/utils/routing/usePage.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as React from 'react';
|
||||
import { FallbackContext, FallbackType } from './FallbackProvider';
|
||||
|
||||
export const usePage = () => {
|
||||
const { updateFallback } = React.useContext(FallbackContext);
|
||||
|
||||
const onLoad = React.useCallback(
|
||||
(component: FallbackType | undefined) => {
|
||||
if (component === undefined) component = null;
|
||||
updateFallback(component);
|
||||
},
|
||||
[updateFallback]
|
||||
);
|
||||
|
||||
return { onLoad };
|
||||
};
|
||||
Reference in New Issue
Block a user