feat: add lazy loading to app pages, pull out tw theme to a module, add filters to getFeed api

This commit is contained in:
MTG2000
2022-04-20 12:15:43 +03:00
parent 5c81dc2a7e
commit 622a98b5f4
22 changed files with 221 additions and 85 deletions

View File

@@ -443,7 +443,9 @@ export interface NexusGenArgTypes {
id: number; // Int!
}
getFeed: { // args
category: string | null; // String
skip?: number | null; // Int
sortBy: string | null; // String
take: number | null; // Int
}
getLnurlDetailsForProject: { // args

View File

@@ -102,7 +102,7 @@ type Query {
allCategories: [Category!]!
allProjects(skip: Int = 0, take: Int = 50): [Project!]!
getCategory(id: Int!): Category!
getFeed(skip: Int = 0, take: Int = 10): [Post!]!
getFeed(category: String = "all", skip: Int = 0, sortBy: String = "all", take: Int = 10): [Post!]!
getLnurlDetailsForProject(project_id: Int!): LnurlDetails!
getPostById(id: Int!, type: POST_TYPE!): Post!
getProject(id: Int!): Project!

View File

@@ -125,7 +125,13 @@ const getFeed = extendType({
t.nonNull.list.nonNull.field('getFeed', {
type: "Post",
args: {
...paginationArgs({ take: 10 })
...paginationArgs({ take: 10 }),
sortBy: stringArg({
default: "all"
}),
category: stringArg({
default: "all"
})
},
resolve(_, { take, skip }) {
const feed = []

30
package-lock.json generated
View File

@@ -51,6 +51,7 @@
"react-responsive-carousel": "^3.2.22",
"react-router-dom": "^6.2.2",
"react-scripts": "4.0.3",
"react-topbar-progress-indicator": "^4.1.1",
"typescript": "^4.4.4",
"web-vitals": "^1.1.2",
"webln": "^0.2.2"
@@ -53156,6 +53157,17 @@
"react": "^16.8.0 || ^17.0.0"
}
},
"node_modules/react-topbar-progress-indicator": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/react-topbar-progress-indicator/-/react-topbar-progress-indicator-4.1.1.tgz",
"integrity": "sha512-Oy3ENNKfymt16zoz5SYy/WOepMurB0oeZEyvuHm8JZ3jrTCe1oAUD7fG6HhYt5sg8Wcg5gdkzSWItaFF6c6VhA==",
"dependencies": {
"topbar": "^0.1.3"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/read-pkg": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
@@ -57847,6 +57859,11 @@
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/topbar": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/topbar/-/topbar-0.1.4.tgz",
"integrity": "sha512-P3n4WnN4GFd2mQXDo30rQmsAGe4V1bVkggtTreSbNyL50Fyc+eVkW5oatSLeGQmJoan2TLIgoXUZypN+6nw4MQ=="
},
"node_modules/tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
@@ -101774,6 +101791,14 @@
"use-latest": "^1.0.0"
}
},
"react-topbar-progress-indicator": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/react-topbar-progress-indicator/-/react-topbar-progress-indicator-4.1.1.tgz",
"integrity": "sha512-Oy3ENNKfymt16zoz5SYy/WOepMurB0oeZEyvuHm8JZ3jrTCe1oAUD7fG6HhYt5sg8Wcg5gdkzSWItaFF6c6VhA==",
"requires": {
"topbar": "^0.1.3"
}
},
"read-pkg": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
@@ -105463,6 +105488,11 @@
"ieee754": "^1.2.1"
}
},
"topbar": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/topbar/-/topbar-0.1.4.tgz",
"integrity": "sha512-P3n4WnN4GFd2mQXDo30rQmsAGe4V1bVkggtTreSbNyL50Fyc+eVkW5oatSLeGQmJoan2TLIgoXUZypN+6nw4MQ=="
},
"tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",

View File

@@ -46,6 +46,7 @@
"react-responsive-carousel": "^3.2.22",
"react-router-dom": "^6.2.2",
"react-scripts": "4.0.3",
"react-topbar-progress-indicator": "^4.1.1",
"typescript": "^4.4.4",
"web-vitals": "^1.1.2",
"webln": "^0.2.2"

View File

@@ -1,15 +1,18 @@
import { useEffect } from "react";
import React, { Suspense, useEffect } from "react";
import Navbar from "src/Components/Navbar/Navbar";
import ExplorePage from "src/features/Projects/pages/ExplorePage";
import ModalsContainer from "src/Components/Modals/ModalsContainer/ModalsContainer";
import { useAppSelector } from './utils/hooks';
import { Wallet_Service } from "./services";
import { Route, Routes } from "react-router-dom";
import CategoryPage from "src/features/Projects/pages/CategoryPage/CategoryPage";
import { useWrapperSetup } from "./utils/Wrapper";
import HottestPage from "src/features/Projects/pages/HottestPage/HottestPage";
import FeedPage from "./features/Posts/pages/FeedPage/FeedPage";
import PostDetailsPage from "./features/Posts/pages/PostDetailsPage/PostDetailsPage";
import LoadingPage from "./Components/LoadingPage/LoadingPage";
// Pages
const FeedPage = React.lazy(() => import("./features/Posts/pages/FeedPage/FeedPage"))
const HottestPage = React.lazy(() => import("src/features/Projects/pages/HottestPage/HottestPage"))
const PostDetailsPage = React.lazy(() => import("./features/Posts/pages/PostDetailsPage/PostDetailsPage"))
const CategoryPage = React.lazy(() => import("src/features/Projects/pages/CategoryPage/CategoryPage"))
const ExplorePage = React.lazy(() => import("src/features/Projects/pages/ExplorePage"))
function App() {
const { isWalletConnected } = useAppSelector(state => ({
@@ -38,13 +41,15 @@ function App() {
return <div id="app" className=''>
<Navbar />
<Routes>
<Route path="/hottest" element={<HottestPage />} />
<Route path="/category/:id" element={<CategoryPage />} />
<Route path="/blog/post/:type/:id" element={<PostDetailsPage />} />
<Route path="/blog" element={<FeedPage />} />
<Route path="/" element={<ExplorePage />} />
</Routes>
<Suspense fallback={<LoadingPage />}>
<Routes>
<Route path="/hottest" element={<HottestPage />} />
<Route path="/category/:id" element={<CategoryPage />} />
<Route path="/blog/post/:type/:id" element={<PostDetailsPage />} />
<Route path="/blog" element={<FeedPage />} />
<Route path="/" element={<ExplorePage />} />
</Routes>
</Suspense>
<ModalsContainer />
</div>;
}

View File

@@ -0,0 +1,14 @@
import TopBarProgress from "react-topbar-progress-indicator";
import THEME from "src/utils/theme";
TopBarProgress.config({
barColors: {
0: THEME.colors.primary[400],
".5": THEME.colors.primary[500],
"1.0": THEME.colors.primary[700],
},
});
export default function LoadingPage() {
return <TopBarProgress />;
}

View File

@@ -24,7 +24,7 @@ export default function TrendingCard() {
)
:
trendingPosts.data?.getTrendingPosts.map(post => {
return <Link key={post.id} to={`/post-details-page/${post.id}`} className="border-b pb-4 last-of-type:border-b-0">
return <Link key={post.id} to={`/blog/post/${post.__typename}/${post.id}`} className="border-b pb-4 last-of-type:border-b-0">
<li className="flex items-start gap-8">
<Avatar width={24} src={post.author.image} />
<p className="text-body5 font-medium">{post.title}</p>

View File

@@ -1,4 +1,5 @@
import { useReducer, useState } from 'react'
import { useFeedQuery } from 'src/graphql'
import { useAppSelector, useInfiniteQuery } from 'src/utils/hooks'
import PostsList from '../../Components/PostsList/PostsList'
@@ -10,10 +11,16 @@ import styles from './styles.module.css'
export default function FeedPage() {
const [sortByFilter, setSortByFilter] = useState('all')
const [categoryFilter, setCategoryFilter] = useState('all')
const feedQuery = useFeedQuery({
variables: {
take: 10,
skip: 0
skip: 0,
sortBy: sortByFilter,
category: categoryFilter
},
})
const { fetchMore, isFetchingMore } = useInfiniteQuery(feedQuery, 'getFeed')
@@ -32,9 +39,13 @@ export default function FeedPage() {
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
overflowY: "scroll",
}}>
<SortBy />
<SortBy
filterChanged={setSortByFilter}
/>
<hr className="my-24 bg-gray-100" />
<PopularCategories />
<PopularCategories
filterChanged={setCategoryFilter}
/>
</div>
</aside>
<PostsList

View File

@@ -1,5 +1,5 @@
query Feed($skip: Int, $take: Int) {
getFeed(skip: $skip, take: $take) {
query Feed($take: Int, $skip: Int, $sortBy: String, $category: String) {
getFeed(take: $take, skip: $skip, sortBy: $sortBy, category: $category) {
... on Story {
id
title

View File

@@ -2,6 +2,7 @@ import { isBounty, isQuestion, isStory, Post } from "src/features/Posts/types"
import StoryPageContent from "./StoryPageContent";
import BountyPageContent from "./BountyPageContent";
import { PostDetailsQuery } from "src/graphql";
import QuestionPageContent from "./QuestionPageContent";
interface Props {
@@ -15,8 +16,8 @@ export default function PageContent({ post }: Props) {
if (isBounty(post))
return <BountyPageContent bounty={post} />
// if (isQuestion(post))
// return <QuestionCard question={post} />
if (isQuestion(post))
return <QuestionPageContent question={post} />
return null

View File

@@ -0,0 +1,39 @@
import Header from "src/features/Posts/Components/PostCard/Header/Header"
import { Question } from "src/features/Posts/types"
import { marked } from 'marked';
import styles from './styles.module.css'
import Badge from "src/Components/Badge/Badge";
import { BiComment } from "react-icons/bi";
import { RiFlashlightLine } from "react-icons/ri";
interface Props {
question: Question
}
export default function QuestionPageContent({ question }: Props) {
return (
<div className="bg-white p-32 border rounded-16">
<div className="flex flex-col gap-24">
<Header size="lg" showTimeAgo={false} author={question.author} date={question.date} />
<h1 className="text-h2 font-bolder">{question.title}</h1>
<div className="flex gap-8">
{question.tags.map(tag => <Badge key={tag.id} size='sm'>
{tag.title}
</Badge>)}
</div>
<div className="flex gap-24">
<div className="text-black font-medium">
<RiFlashlightLine /> <span className="align-middle text-body5">{question.votes_count} votes</span>
</div>
<div className="text-black font-medium">
<BiComment /> <span className="align-middle text-body5">32 Comments</span>
</div>
</div>
</div>
<div className={`mt-42 ${styles.body}`} dangerouslySetInnerHTML={{ __html: marked.parse(question.body) }}>
</div>
</div>
)
}

View File

@@ -1,5 +1,6 @@
import { useParams } from 'react-router-dom'
import LoadingPage from 'src/Components/LoadingPage/LoadingPage'
import { usePostDetailsQuery } from 'src/graphql'
import { useAppSelector, } from 'src/utils/hooks'
import TrendingCard from '../../Components/TrendingCard/TrendingCard'
@@ -17,7 +18,8 @@ export default function PostDetailsPage() {
id: Number(id!),
type: type as any
},
skip: isNaN(Number(id))
skip: isNaN(Number(id)),
})
const { navHeight } = useAppSelector((state) => ({
@@ -25,7 +27,7 @@ export default function PostDetailsPage() {
}));
if (postDetailsQuery.loading)
return <h2>Loading</h2>
return <LoadingPage />
const post = postDetailsQuery.data?.getPostById;

View File

@@ -2,7 +2,7 @@ import { useMediaQuery } from "@react-hookz/web";
import Carousel from "react-multi-carousel";
import Assets from "src/assets";
import Button from "src/Components/Button/Button";
import { THEME } from "src/utils/theme";
import THEME from "src/utils/theme";
import { MEDIA_QUERIES } from "src/utils/theme/media_queries";
import CustomDot from "./CustomDot/CustomDot";
import styles from './styles.module.css'

View File

@@ -160,7 +160,9 @@ export type QueryGetCategoryArgs = {
export type QueryGetFeedArgs = {
category?: InputMaybe<Scalars['String']>;
skip?: InputMaybe<Scalars['Int']>;
sortBy?: InputMaybe<Scalars['String']>;
take?: InputMaybe<Scalars['Int']>;
};
@@ -278,8 +280,10 @@ export type TrendingPostsQueryVariables = Exact<{ [key: string]: never; }>;
export type TrendingPostsQuery = { __typename?: 'Query', getTrendingPosts: Array<{ __typename?: 'Bounty', id: number, title: string, author: { __typename?: 'User', id: number, image: string } } | { __typename?: 'Question', id: number, title: string, author: { __typename?: 'User', id: number, image: string } } | { __typename?: 'Story', id: number, title: string, author: { __typename?: 'User', id: number, image: string } }> };
export type FeedQueryVariables = Exact<{
skip: InputMaybe<Scalars['Int']>;
take: InputMaybe<Scalars['Int']>;
skip: InputMaybe<Scalars['Int']>;
sortBy: InputMaybe<Scalars['String']>;
category: InputMaybe<Scalars['String']>;
}>;
@@ -475,8 +479,8 @@ export type TrendingPostsQueryHookResult = ReturnType<typeof useTrendingPostsQue
export type TrendingPostsLazyQueryHookResult = ReturnType<typeof useTrendingPostsLazyQuery>;
export type TrendingPostsQueryResult = Apollo.QueryResult<TrendingPostsQuery, TrendingPostsQueryVariables>;
export const FeedDocument = gql`
query Feed($skip: Int, $take: Int) {
getFeed(skip: $skip, take: $take) {
query Feed($take: Int, $skip: Int, $sortBy: String, $category: String) {
getFeed(take: $take, skip: $skip, sortBy: $sortBy, category: $category) {
... on Story {
id
title
@@ -561,8 +565,10 @@ export const FeedDocument = gql`
* @example
* const { data, loading, error } = useFeedQuery({
* variables: {
* skip: // value for 'skip'
* take: // value for 'take'
* skip: // value for 'skip'
* sortBy: // value for 'sortBy'
* category: // value for 'category'
* },
* });
*/

View File

@@ -153,5 +153,9 @@ const feedRandomer = new Chance('feed')
export const feed: Post[] = Array(30).fill(0).map((_, idx) => {
const post = feedRandomer.pickone([posts.bounties[0], posts.questions[0], posts.stories[0]])
return { ...post, id: idx + 1, title: `${post.type} Title ${idx + 1}` }
return {
...post, id: idx + 1, title: feedRandomer.sentence({
words: feedRandomer.integer({ min: 4, max: 7 })
})
}
})

View File

@@ -50,7 +50,7 @@ export const apolloClient = new ApolloClient({
typePolicies: {
Query: {
fields: {
getFeed: offsetLimitPagination()
getFeed: offsetLimitPagination(['sortBy', 'category'])
},
},
},

47
src/utils/theme/colors.js Normal file
View File

@@ -0,0 +1,47 @@
const colors = {
gray: {
25: "#FCFCFD",
50: "#F9FAFB",
100: "#F2F4F7",
200: "#E4E7EC",
300: "#D0D5DD",
400: "#98A2B3",
500: "#667085",
600: "#475467",
700: "#344054",
800: "#1D2939",
900: "#101828",
},
primary: {
25: "#FAF8FF",
50: "#F5F2FF",
100: "#E6DFFF",
200: "#B3A0FF",
300: "#B3A0FF",
400: "#9E88FF",
500: "#7B61FF",
600: "#5C46DB",
700: "#4230B7",
800: "#2C1E93",
900: "#1C127A",
},
sucess: {
300: "#6CE9A6",
400: "#32D583",
500: "#12B76A",
600: "#039855",
700: "#027A48",
},
warning: {
25: "#FFFCF5",
50: "#FFFAEB",
100: "#FEF0C7",
200: "#FEDF89",
},
// Custom Colors
thunder: "#ffd400",
fire: "#ff6a00",
}
module.exports = { colors };

11
src/utils/theme/index.js Normal file
View File

@@ -0,0 +1,11 @@
const { colors } = require('./colors')
const { MEDIA_QUERIES, screens } = require('./media_queries')
const THEME = {
colors,
screens,
MEDIA_QUERIES
}
module.exports = THEME

View File

@@ -1,6 +0,0 @@
export * as THEME from './media_queries'
const THEME = {}
export default THEME;

View File

@@ -1,4 +1,4 @@
export const screens = {
const screens = {
sm: 640,
md: 768,
lg: 1024,
@@ -6,9 +6,14 @@ export const screens = {
'2xl': 1536,
}
export const MEDIA_QUERIES = {
const MEDIA_QUERIES = {
isSmall: `only screen and (min-width : ${screens.sm}px)`,
isMedium: `only screen and (min-width : ${screens.md}px)`,
isLarge: `only screen and (min-width : ${screens.lg}px)`,
isXLarge: `only screen and (min-width : ${screens["2xl"]}px))`,
}
module.exports = {
screens,
MEDIA_QUERIES
}

View File

@@ -1,3 +1,5 @@
const THEME = require('./src/utils/theme')
module.exports = {
mode: "jit",
purge: [
@@ -10,51 +12,7 @@ module.exports = {
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
colors: {
gray: {
25: "#FCFCFD",
50: "#F9FAFB",
100: "#F2F4F7",
200: "#E4E7EC",
300: "#D0D5DD",
400: "#98A2B3",
500: "#667085",
600: "#475467",
700: "#344054",
800: "#1D2939",
900: "#101828",
},
primary: {
25: "#FAF8FF",
50: "#F5F2FF",
100: "#E6DFFF",
200: "#B3A0FF",
300: "#B3A0FF",
400: "#9E88FF",
500: "#7B61FF",
600: "#5C46DB",
700: "#4230B7",
800: "#2C1E93",
900: "#1C127A",
},
sucess: {
300: "#6CE9A6",
400: "#32D583",
500: "#12B76A",
600: "#039855",
700: "#027A48",
},
warning: {
25: "#FFFCF5",
50: "#FFFAEB",
100: "#FEF0C7",
200: "#FEDF89",
},
// Custom Colors
thunder: "#ffd400",
fire: "#ff6a00",
},
colors: THEME.colors,
boxShadow: {
xs: "0px 1px 2px rgba(16, 24, 40, 0.05)",
sm: