[socialmon] ui: tweaks
This commit is contained in:
parent
63091c0951
commit
f503a4cdcd
|
|
@ -1,11 +1,10 @@
|
|||
import type { Metadata } from 'next';
|
||||
|
||||
import { redirectToLoginPageIfNotLoggedIn } from '~/components/auth/redirectToLoginPageIfNotLoggedIn';
|
||||
import ProjectsPage from '~/components/project/ProjectsPage';
|
||||
|
||||
import { getUser } from '~/app/lib/auth';
|
||||
|
||||
import ProjectsPage from './ProjectsPage';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
description: 'Social moderator',
|
||||
title: 'SocialMon | Projects',
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import type { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { getUser } from '~/app/lib/auth';
|
||||
import ProjectCreatePage from '~/components/project/ProjectCreatePage';
|
||||
|
||||
import ProjectCreatePage from './ProjectCreatePage';
|
||||
import { getUser } from '~/app/lib/auth';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
description: 'Social moderator',
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ export default function RootLayout({ children }: Props) {
|
|||
<html lang="en">
|
||||
<body className={clsx('antialiased')}>
|
||||
<GlobalProviders>
|
||||
<MantineProvider>
|
||||
<MantineProvider
|
||||
theme={{ defaultRadius: 'md', primaryColor: 'orange' }}>
|
||||
<CustomToaster />
|
||||
<div className="bg-white">{children}</div>
|
||||
</MantineProvider>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default async function Layout({ children, params }: Props) {
|
|||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<ProjectsNavbar user={user} />
|
||||
<Container className={clsx('flex-1', 'p-4', 'flex')}>
|
||||
<Container className={clsx('flex-1', 'px-4', 'flex')}>
|
||||
{children}
|
||||
</Container>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { notFound } from 'next/navigation';
|
||||
|
||||
import prisma from '~/server/prisma';
|
||||
import PostDetailPage from '~/components/posts/PostDetailPage';
|
||||
|
||||
import PostDetailPage from './PostDetailPage';
|
||||
import prisma from '~/server/prisma';
|
||||
|
||||
type Props = Readonly<{
|
||||
params: {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,11 @@ export default function ActivityLogList() {
|
|||
|
||||
{hasNextPage && (
|
||||
<div className="flex w-full justify-center py-6">
|
||||
<Button disabled={isFetchingNextPage} onClick={() => fetchNextPage()}>
|
||||
<Button
|
||||
disabled={isFetchingNextPage}
|
||||
size="sm"
|
||||
variant="default"
|
||||
onClick={() => fetchNextPage()}>
|
||||
{isFetchingNextPage ? 'Loading more...' : 'See more'}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -71,14 +71,14 @@ export default function PostDetailPage({ post }: Props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>
|
||||
<Button
|
||||
component={Link}
|
||||
href={`/projects/${projectSlug}`}
|
||||
leftSection={<RiArrowLeftLine />}
|
||||
size="xs"
|
||||
variant="outline">
|
||||
variant="light">
|
||||
Back
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -97,7 +97,7 @@ export default function PostDetail({
|
|||
return (
|
||||
<div>
|
||||
<Flex direction="column" gap={2} justify="space-between" mb="xs" mt="md">
|
||||
<Title order={3}>{post.title}</Title>
|
||||
<Title order={2}>{post.title}</Title>
|
||||
<PostMetadata post={post} showViewPost={true} />
|
||||
</Flex>
|
||||
<Text size="sm">
|
||||
|
|
@ -108,7 +108,6 @@ export default function PostDetail({
|
|||
className="prose"
|
||||
/>
|
||||
</Text>
|
||||
|
||||
{!post.reply && (
|
||||
<>
|
||||
<Divider my="md" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Box, Title } from '@mantine/core';
|
||||
import { Anchor, Box, Title } from '@mantine/core';
|
||||
import clsx from 'clsx';
|
||||
import Link from 'next/link';
|
||||
|
||||
|
|
@ -34,17 +34,21 @@ export default function PostItem({
|
|||
<Box
|
||||
className={clsx(
|
||||
'w-full',
|
||||
'p-2',
|
||||
'px-1',
|
||||
'py-4',
|
||||
'rounded',
|
||||
'hover:bg-slate-100',
|
||||
'transition-all duration-200',
|
||||
'cursor-pointer',
|
||||
'flex flex-col gap-2',
|
||||
'text-left',
|
||||
)}
|
||||
component={Link}
|
||||
href={`/projects/${projectSlug}/posts/${post.id}`}>
|
||||
<Title order={5}>{post.title}</Title>
|
||||
)}>
|
||||
<Anchor
|
||||
component={Link}
|
||||
href={`/projects/${projectSlug}/posts/${post.id}`}
|
||||
underline="hover">
|
||||
<Title order={3} size="h4">
|
||||
{post.title}
|
||||
</Title>
|
||||
</Anchor>
|
||||
<PostMetadata
|
||||
post={post as PostExtended}
|
||||
showMarkedAsIrrelevant={showMarkedAsIrrelevant}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { Button, Tabs, Text, Tooltip } from '@mantine/core';
|
||||
import { Box, Button, Tabs, Text, Tooltip } from '@mantine/core';
|
||||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
|
@ -52,21 +52,30 @@ export default function PostList() {
|
|||
const posts = data?.pages.flatMap((page) => page.posts);
|
||||
|
||||
return (
|
||||
<div className={clsx('flex flex-col gap-2', 'h-full w-full')}>
|
||||
<div className={clsx('flex flex-col', 'w-full')}>
|
||||
<div
|
||||
className="sticky w-full bg-white"
|
||||
className="sticky w-full bg-white pt-4"
|
||||
style={{ top: `${NAVBAR_HEIGHT}px` }}>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
variant="outline"
|
||||
onChange={(value) => setActiveTab(value as PostTab)}>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value="unreplied">Unreplied</Tabs.Tab>
|
||||
<Tabs.Tab value="replied">Replied</Tabs.Tab>
|
||||
<Tabs.Tab value="irrelevant">Irrelevant</Tabs.Tab>
|
||||
<Tabs.Tab value="all">All</Tabs.Tab>
|
||||
<Tabs.Tab fw={500} value="unreplied">
|
||||
Unreplied
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab fw={500} value="replied">
|
||||
Replied
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab fw={500} value="irrelevant">
|
||||
Irrelevant
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab fw={500} value="all">
|
||||
All
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
</Tabs>
|
||||
<div className="absolute right-1 top-0.5 flex items-center gap-2 md:right-4">
|
||||
<div className="absolute right-0 top-3 flex items-center gap-2">
|
||||
{projectData?.postsLastFetchedAt && (
|
||||
<div className="hidden md:block">
|
||||
<Tooltip
|
||||
|
|
@ -74,7 +83,7 @@ export default function PostList() {
|
|||
new Date(projectData.postsLastFetchedAt),
|
||||
)}
|
||||
withArrow={true}>
|
||||
<Text size="sm">
|
||||
<Text c="dimmed" size="sm">
|
||||
Fetched{' '}
|
||||
<RelativeTimestamp
|
||||
timestamp={new Date(projectData.postsLastFetchedAt)}
|
||||
|
|
@ -86,16 +95,13 @@ export default function PostList() {
|
|||
<FetchPostButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Text hidden={!isLoading} size="md">
|
||||
Loading...
|
||||
</Text>
|
||||
|
||||
{!isLoading && posts?.length === 0 && (
|
||||
<Text size="md">No post found</Text>
|
||||
)}
|
||||
|
||||
<div className={clsx('divide-y')}>
|
||||
<Box py={4}>
|
||||
{posts?.map((post) => (
|
||||
<PostItem
|
||||
key={post.id}
|
||||
|
|
@ -104,17 +110,17 @@ export default function PostList() {
|
|||
showRepliedBadge={activeTab === 'all'}
|
||||
/>
|
||||
))}
|
||||
|
||||
{hasNextPage && (
|
||||
<div className="flex w-full justify-center py-6">
|
||||
<Button
|
||||
disabled={isFetchingNextPage}
|
||||
variant="default"
|
||||
onClick={() => fetchNextPage()}>
|
||||
{isFetchingNextPage ? 'Loading more...' : 'See more'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { Badge, Button, Pill, Text, Tooltip } from '@mantine/core';
|
||||
import { Anchor, Badge, Button, Pill, Text, Tooltip } from '@mantine/core';
|
||||
import Link from 'next/link';
|
||||
import { RiArrowRightUpLine, RiCheckLine } from 'react-icons/ri';
|
||||
|
||||
import RelativeTimestamp from '~/components/common/datetime/RelativeTimestamp';
|
||||
|
||||
import type { PostExtended } from '~/types';
|
||||
|
||||
import { redditPermalinkToUrl } from '../utils';
|
||||
|
|
@ -16,7 +14,7 @@ type Props = Readonly<{
|
|||
showViewPost?: boolean;
|
||||
}>;
|
||||
|
||||
function PostMetadata({
|
||||
export default function PostMetadata({
|
||||
post,
|
||||
showMarkedAsIrrelevant,
|
||||
showRepliedBadge,
|
||||
|
|
@ -27,32 +25,43 @@ function PostMetadata({
|
|||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<PostStats post={post} />
|
||||
|
||||
<div className="h-1 w-1 rounded-full bg-slate-600" />
|
||||
|
||||
<Tooltip label="Post fetched at" withArrow={true}>
|
||||
<Text size="sm">
|
||||
<RelativeTimestamp timestamp={new Date(post.createdAt)} />
|
||||
<Text c="dimmed" size="sm">
|
||||
{new Intl.DateTimeFormat(undefined, {
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
hour12: true,
|
||||
minute: '2-digit',
|
||||
month: 'long',
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
}).format(post.createdAt)}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
|
||||
<div className="h-1 w-1 rounded-full bg-slate-600" />
|
||||
|
||||
<Text size="sm">{post.subreddit}</Text>
|
||||
<Text size="sm">
|
||||
<Anchor
|
||||
className="z-1"
|
||||
href={`https://reddit.com/${post.subreddit}`}
|
||||
target="_blank"
|
||||
underline="hover">
|
||||
{post.subreddit}
|
||||
</Anchor>
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{showViewPost && (
|
||||
<Button
|
||||
color="orange"
|
||||
component={Link}
|
||||
href={redditPermalinkToUrl(post.permalink)}
|
||||
rightSection={<RiArrowRightUpLine />}
|
||||
target="_blank"
|
||||
variant="subtle">
|
||||
View Post
|
||||
View on Reddit
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{post.keywords.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{post.keywords.map((keyword) => (
|
||||
|
|
@ -62,7 +71,6 @@ function PostMetadata({
|
|||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{post.reply && showRepliedBadge && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge color="violet" leftSection={<RiCheckLine />} size="xs">
|
||||
|
|
@ -73,12 +81,10 @@ function PostMetadata({
|
|||
{post.relevancy === 'IRRELEVANT' && showMarkedAsIrrelevant && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge color="violet" leftSection={<RiCheckLine />} size="xs">
|
||||
Marked as Irrelevant
|
||||
Marked as irrelevant
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PostMetadata;
|
||||
|
|
|
|||
|
|
@ -32,12 +32,11 @@ export default function PostResponse({
|
|||
rightSection={<RiArrowRightUpLine />}
|
||||
target="_blank"
|
||||
variant="subtle">
|
||||
View Reply
|
||||
View reply
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Fallback to showing the stored relied if fetching reply failed */}
|
||||
{/* Fallback to showing the stored reply if fetching reply failed */}
|
||||
{!isFetchingComments && !comments?.data?.children?.length ? (
|
||||
<PostCommentsList
|
||||
comments={{
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export default function PostRelevancyActionButton({
|
|||
disabled={markPostRelevancyMutation.isLoading}
|
||||
loading={markPostRelevancyMutation.isLoading}
|
||||
size="xs"
|
||||
variant="light"
|
||||
onClick={onMarkPostRelevancy}>
|
||||
{relevancy === PostRelevancy.IRRELEVANT
|
||||
? 'Mark as relevant'
|
||||
|
|
|
|||
|
|
@ -83,7 +83,6 @@ export default function PostComment({ className, comment, level }: Props) {
|
|||
<RelativeTimestamp timestamp={new Date(created_utc * 1000)} />
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Text size="sm">
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export default function ProjectForm({
|
|||
required={true}
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block font-semibold">
|
||||
Keyword/Subreddit Groups
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ export default function ProjectsProductsToAdvertiseInput() {
|
|||
{form.errors.productsToAdvertise}
|
||||
</Text>
|
||||
)}
|
||||
<Button onClick={addProduct}>Add Product</Button>
|
||||
<Button onClick={addProduct}>Add product</Button>
|
||||
</Fieldset>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,31 +56,29 @@ export default function ProjectsPage({ isAdminRole }: Props) {
|
|||
data?.map((project) => (
|
||||
<Card
|
||||
key={project.id}
|
||||
className="flex flex-col gap-6"
|
||||
padding="sm"
|
||||
shadow="sm"
|
||||
className="flex flex-col gap-2"
|
||||
padding="lg"
|
||||
radius="lg"
|
||||
withBorder={true}>
|
||||
<Text fw={500} size="lg">
|
||||
{project.name}
|
||||
</Text>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text>
|
||||
{project.productsToAdvertise?.length || 0} products to
|
||||
advertise
|
||||
<Text size="sm">
|
||||
{project.productsToAdvertise?.length || 0} product(s)
|
||||
</Text>
|
||||
{/* Aggregate keyword and subreddit counts from subredditKeywords */}
|
||||
{project.subredditKeywords &&
|
||||
project.subredditKeywords.length > 0 ? (
|
||||
<>
|
||||
<Text>
|
||||
<Text size="sm">
|
||||
{project.subredditKeywords.reduce(
|
||||
(acc, group) => acc + (group.keywords?.length || 0),
|
||||
0,
|
||||
)}{' '}
|
||||
keywords (grouped)
|
||||
</Text>
|
||||
<Text>
|
||||
<Text size="sm">
|
||||
{
|
||||
Array.from(
|
||||
new Set(
|
||||
|
|
@ -95,8 +93,12 @@ export default function ProjectsPage({ isAdminRole }: Props) {
|
|||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text>{project.keywords.length} keywords (legacy)</Text>
|
||||
<Text>{project.subreddits.length} subreddits (legacy)</Text>
|
||||
<Text size="sm">
|
||||
{project.keywords.length} keywords (legacy)
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
{project.subreddits.length} subreddits (legacy)
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -104,7 +106,6 @@ export default function ProjectsPage({ isAdminRole }: Props) {
|
|||
className="absolute inset-0"
|
||||
href={`/projects/${project.slug}`}
|
||||
/>
|
||||
|
||||
<Menu position="bottom-end" shadow="sm">
|
||||
<Menu.Target>
|
||||
<div
|
||||
|
|
@ -115,7 +116,6 @@ export default function ProjectsPage({ isAdminRole }: Props) {
|
|||
</ActionIcon>
|
||||
</div>
|
||||
</Menu.Target>
|
||||
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item
|
||||
leftSection={<RiEyeLine />}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { Text } from '@mantine/core';
|
||||
import clsx from 'clsx';
|
||||
import Link from 'next/link';
|
||||
import type { ReactNode } from 'react';
|
||||
|
|
@ -25,13 +26,12 @@ export default function Navbar({ navItems, navUser }: Props) {
|
|||
className={clsx('px-4', 'flex items-center justify-between gap-2')}>
|
||||
<div className="flex items-center gap-6">
|
||||
<Link href="/">
|
||||
<span className="text-xl font-bold tracking-tighter md:text-3xl">
|
||||
SocialMon
|
||||
</span>
|
||||
<Text fw={600} lts="1px" size="md" tt="uppercase">
|
||||
Socialmon
|
||||
</Text>
|
||||
</Link>
|
||||
{navItems}
|
||||
</div>
|
||||
|
||||
{navUser}
|
||||
</Container>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ export default function UserCard({ user }: Props) {
|
|||
className="relative flex flex-col gap-2"
|
||||
mb="md"
|
||||
padding="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
withBorder={true}>
|
||||
<Menu position="bottom-end" shadow="sm" withinPortal={true}>
|
||||
|
|
|
|||
Loading…
Reference in New Issue