diff --git a/pages/[username]/pagina/[page]/index.public.js b/pages/[username]/comentarios/[page]/index.public.js similarity index 56% rename from pages/[username]/pagina/[page]/index.public.js rename to pages/[username]/comentarios/[page]/index.public.js index f2464a2da..1359fcd01 100644 --- a/pages/[username]/pagina/[page]/index.public.js +++ b/pages/[username]/comentarios/[page]/index.public.js @@ -1,24 +1,35 @@ import { getStaticPropsRevalidate } from 'next-swr'; -import { ContentList, DefaultLayout } from '@/TabNewsUI'; +import { ContentList, DefaultLayout, UserHeader } from '@/TabNewsUI'; +import { FaUser } from '@/TabNewsUI/icons'; import { NotFoundError } from 'errors'; import authorization from 'models/authorization.js'; import content from 'models/content.js'; import removeMarkdown from 'models/remove-markdown.js'; import user from 'models/user.js'; import validator from 'models/validator.js'; +import { useUser } from 'pages/interface'; + +export default function ChildContent({ contentListFound, pagination, username }) { + const { user, isLoading } = useUser(); + const isAuthenticatedUser = user && user.username === username; -export default function Home({ contentListFound, pagination, username }) { return ( - <> - - - - + + + + + ); } @@ -57,6 +68,7 @@ export const getStaticProps = getStaticPropsRevalidate(async (context) => { where: { owner_id: secureUserFound.id, status: 'published', + $not_null: ['parent_id'], }, page: context.params.page, per_page: context.params.per_page, @@ -76,12 +88,20 @@ export const getStaticProps = getStaticPropsRevalidate(async (context) => { const secureContentListFound = authorization.filterOutput(userTryingToGet, 'read:content:list', contentListFound); + if (secureContentListFound.length === 0 && context.params.page !== 1) { + const lastValidPage = `/${secureUserFound.username}/comentarios/${results.pagination.lastPage || 1}`; + const revalidate = context.params.page > results.pagination.lastPage + 1 ? 60 : 1; + + return { + redirect: { + destination: lastValidPage, + }, + revalidate, + }; + } + for (const content of secureContentListFound) { - if (content.parent_id) { - content.body = removeMarkdown(content.body, { maxLength: 255 }); - } else { - delete content.body; - } + content.body = removeMarkdown(content.body, { maxLength: 255 }); } return { diff --git a/pages/[username]/conteudos/[page]/index.public.js b/pages/[username]/conteudos/[page]/index.public.js new file mode 100644 index 000000000..bc0320a16 --- /dev/null +++ b/pages/[username]/conteudos/[page]/index.public.js @@ -0,0 +1,120 @@ +import { useRouter } from 'next/router'; +import { getStaticPropsRevalidate } from 'next-swr'; + +import { ContentList, DefaultLayout, UserHeader } from '@/TabNewsUI'; +import { FaUser } from '@/TabNewsUI/icons'; +import { NotFoundError } from 'errors'; +import authorization from 'models/authorization.js'; +import content from 'models/content.js'; +import user from 'models/user.js'; +import validator from 'models/validator.js'; +import { useUser } from 'pages/interface'; + +export default function RootContent({ contentListFound, pagination, username }) { + const { push } = useRouter(); + const { user, isLoading } = useUser(); + const isAuthenticatedUser = user && user.username === username; + + return ( + + + + push('/publicar'), + }, + }} + /> + + ); +} + +export async function getStaticPaths() { + return { + paths: [], + fallback: 'blocking', + }; +} + +export const getStaticProps = getStaticPropsRevalidate(async (context) => { + const userTryingToGet = user.createAnonymous(); + + try { + context.params = validator(context.params, { + username: 'required', + page: 'optional', + per_page: 'optional', + }); + } catch (error) { + return { + notFound: true, + }; + } + + let results; + let secureUserFound; + + try { + const userFound = await user.findOneByUsername(context.params.username); + + secureUserFound = authorization.filterOutput(userTryingToGet, 'read:user', userFound); + + results = await content.findWithStrategy({ + strategy: 'new', + where: { + parent_id: null, + owner_id: secureUserFound.id, + status: 'published', + }, + attributes: { + exclude: ['body'], + }, + page: context.params.page, + per_page: context.params.per_page, + }); + } catch (error) { + if (error instanceof NotFoundError) { + return { + notFound: true, + revalidate: 1, + }; + } + + throw error; + } + + const contentListFound = results.rows; + + const secureContentListFound = authorization.filterOutput(userTryingToGet, 'read:content:list', contentListFound); + + if (secureContentListFound.length === 0 && context.params.page !== 1) { + const lastValidPage = `/${secureUserFound.username}/conteudos/${results.pagination.lastPage || 1}`; + const revalidate = context.params.page > results.pagination.lastPage + 1 ? 60 : 1; + + return { + redirect: { + destination: lastValidPage, + }, + revalidate, + }; + } + + return { + props: { + contentListFound: JSON.parse(JSON.stringify(secureContentListFound)), + pagination: results.pagination, + username: secureUserFound.username, + }, + + revalidate: 10, + }; +}); diff --git a/pages/[username]/index.public.js b/pages/[username]/index.public.js index 15c94630c..71b6c56a3 100644 --- a/pages/[username]/index.public.js +++ b/pages/[username]/index.public.js @@ -1,49 +1,43 @@ -import { useRouter } from 'next/router'; import { getStaticPropsRevalidate } from 'next-swr'; import { useState } from 'react'; import useSWR from 'swr'; import { - ActionList, - ActionMenu, Box, - ContentList, + Button, DefaultLayout, Flash, - IconButton, Label, LabelGroup, - NavItem, - NavList, - Pagehead, + Link, PastTime, - TabCashCount, - TabCoinCount, Text, useConfirm, + UserHeader, Viewer, } from '@/TabNewsUI'; -import { CircleSlashIcon, FaUser, GearIcon, KebabHorizontalIcon } from '@/TabNewsUI/icons'; +import { CircleSlashIcon, GearIcon, SquareFillIcon } from '@/TabNewsUI/icons'; import { NotFoundError } from 'errors'; import authorization from 'models/authorization.js'; import content from 'models/content.js'; -import removeMarkdown from 'models/remove-markdown.js'; import user from 'models/user.js'; import validator from 'models/validator.js'; import { useUser } from 'pages/interface'; -export default function Home({ contentListFound, pagination, userFound: userFoundFallback }) { +export default function Page({ userFound: userFoundFallback }) { const { data: userFound, mutate: userFoundMutate } = useSWR(`/api/v1/users/${userFoundFallback.username}`, { fallbackData: userFoundFallback, revalidateOnMount: false, }); - const { user, isLoading } = useUser(); - const { push } = useRouter(); + const { user } = useUser(); const confirm = useConfirm(); const [globalErrorMessage, setGlobalErrorMessage] = useState(false); const isAuthenticatedUser = user && user.username === userFound.username; + const canNuke = + !isAuthenticatedUser && user?.features?.includes('ban:user') && !userFound?.features?.includes('nuked'); + const canUpdate = isAuthenticatedUser && user?.features?.includes('update:user'); async function handleClickNuke() { setGlobalErrorMessage(null); @@ -90,115 +84,84 @@ export default function Home({ contentListFound, pagination, userFound: userFoun return; } - function OptionsMenu() { - const canNuke = - !isAuthenticatedUser && user?.features?.includes('ban:user') && !userFound?.features?.includes('nuked'); - const canUpdate = isAuthenticatedUser && user?.features?.includes('update:user'); - if (!canNuke && !canUpdate) { - return null; - } - - return ( - - - - - - - {canUpdate && ( - - - - - Editar perfil - - )} - {canNuke && ( - - - - - Nuke - - )} - - - - ); - } - function UserFeatures() { if (!userFound?.features?.length) return null; return ( - + {userFound.features.includes('nuked') && } ); } return ( - <> - - {globalErrorMessage && ( - - {globalErrorMessage} - - )} - - - - - {userFound.username} + + {globalErrorMessage && ( + + {globalErrorMessage} + + )} + + + + + + + + + + + {(userFound.tabcoins || 0).toLocaleString('pt-BR')} TabCoin{userFound.tabcoins !== 1 && 's'} - - - - - {' · '} - `Membro há ${date}.`} sx={{ pl: 2 }} /> - - {OptionsMenu()} - - - {userFound.description && ( - - + + + {(userFound.tabcash || 0).toLocaleString('pt-BR')} TabCash - )} - - push('/publicar'), - }, - }} - /> - - + + `Membro há ${date}`} direction="ne" /> + + + + + {canUpdate && ( + + )} + {canNuke && ( + + )} + + + + {userFound.description && ( + + + + )} + ); } @@ -245,23 +208,12 @@ export const getStaticProps = getStaticPropsRevalidate(async (context) => { }; } - let results; let secureUserFound; try { const userFound = await user.findOneByUsername(context.params.username, { withBalance: true }); secureUserFound = authorization.filterOutput(userTryingToGet, 'read:user', userFound); - - results = await content.findWithStrategy({ - strategy: 'new', - where: { - owner_id: secureUserFound.id, - status: 'published', - }, - page: context.params.page, - per_page: context.params.per_page, - }); } catch (error) { // `user` model will throw a `NotFoundError` if the user is not found. if (error instanceof NotFoundError) { @@ -274,22 +226,8 @@ export const getStaticProps = getStaticPropsRevalidate(async (context) => { throw error; } - const contentListFound = results.rows; - - const secureContentListFound = authorization.filterOutput(userTryingToGet, 'read:content:list', contentListFound); - - for (const content of secureContentListFound) { - if (content.parent_id) { - content.body = removeMarkdown(content.body, { maxLength: 255 }); - } else { - delete content.body; - } - } - return { props: { - contentListFound: JSON.parse(JSON.stringify(secureContentListFound)), - pagination: results.pagination, userFound: JSON.parse(JSON.stringify(secureUserFound)), }, diff --git a/pages/interface/components/EmptyState/index.js b/pages/interface/components/EmptyState/index.js index 90dc24d06..93908de83 100644 --- a/pages/interface/components/EmptyState/index.js +++ b/pages/interface/components/EmptyState/index.js @@ -5,12 +5,21 @@ export default function EmptyState(props) { const { title, description, action, icon: Icon, isLoading } = props; if (isLoading) return null; return ( - - {Icon && } + + {Icon && } {title} {description && {description}} {action && ( - )} diff --git a/pages/interface/components/Header/index.js b/pages/interface/components/Header/index.js index 90bc82920..4ae0eb83d 100644 --- a/pages/interface/components/Header/index.js +++ b/pages/interface/components/Header/index.js @@ -172,7 +172,7 @@ export default function HeaderComponent() { Novo conteúdo - + diff --git a/pages/interface/components/Link/index.js b/pages/interface/components/Link/index.js index 5b2d83fcc..830e01da3 100644 --- a/pages/interface/components/Link/index.js +++ b/pages/interface/components/Link/index.js @@ -1,15 +1,16 @@ import NextLink from 'next/link'; import { useRouter } from 'next/router'; +import { forwardRef } from 'react'; import { NavList, PrimerHeader, PrimerLink } from '@/TabNewsUI'; -export function Link({ href, children, ...props }) { +export const Link = forwardRef(function Link({ href, children, ...props }, ref) { return ( - + {children} ); -} +}); export function HeaderLink({ href, children, ...props }) { return ( diff --git a/pages/interface/components/TabNewsUI/index.js b/pages/interface/components/TabNewsUI/index.js index 402b29200..dc236b645 100644 --- a/pages/interface/components/TabNewsUI/index.js +++ b/pages/interface/components/TabNewsUI/index.js @@ -20,6 +20,7 @@ export { default as TabCoinButtons } from '@/TabCoinButtons'; export { default as TabCoinCount } from '@/TabCoinCount'; export { default as ThemeProvider } from '@/ThemeProvider'; export { default as ThemeSelector, ThemeSwitcher } from '@/ThemeSelector'; +export { default as UserHeader } from '@/UserHeader'; export { ActionList, ActionMenu, @@ -29,6 +30,7 @@ export { BranchName, Button, Checkbox, + CounterLabel, Flash, FormControl, Heading, @@ -44,6 +46,7 @@ export { SSRProvider, SegmentedControl, Spinner, + TabNav, Text, TextInput, Tooltip, diff --git a/pages/interface/components/UserHeader/index.js b/pages/interface/components/UserHeader/index.js new file mode 100644 index 000000000..d28913b8b --- /dev/null +++ b/pages/interface/components/UserHeader/index.js @@ -0,0 +1,37 @@ +import { useRouter } from 'next/router'; + +import { Box, CounterLabel, Heading, Link, TabNav } from '@/TabNewsUI'; + +export default function UserHeader({ username, children, rootContentCount, childContentCount }) { + const router = useRouter(); + + return ( + <> + + + {username} + + {children} + + + + + Perfil + + + + Publicações {!!rootContentCount && {rootContentCount}} + + + Comentários {!!childContentCount && {childContentCount}} + + + + ); +}