Skip to content

Commit c7042d4

Browse files
authored
feat: entry points (#4811)
1 parent cfdbb05 commit c7042d4

File tree

7 files changed

+149
-48
lines changed

7 files changed

+149
-48
lines changed

packages/shared/src/components/analytics/ImpressionsChart.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ import {
1919
postAnalyticsHistoryLimit,
2020
postAnalyticsHistoryQuery,
2121
} from '../../graphql/posts';
22+
import { canViewPostAnalytics } from '../../lib/user';
23+
import { useAuthContext } from '../../contexts/AuthContext';
2224

2325
export type ImpressionsChartProps = {
24-
post: Pick<Post, 'id'> | undefined;
26+
post: Pick<Post, 'id' | 'author'> | undefined;
2527
};
2628

2729
type ImpressionNode = {
@@ -38,8 +40,11 @@ const tickProp: TickProp = {
3840
export const ImpressionsChart = ({
3941
post,
4042
}: ImpressionsChartProps): ReactElement => {
43+
const { user } = useAuthContext();
44+
4145
const { data: postAnalyticsHistory } = useQuery({
4246
...postAnalyticsHistoryQuery({ id: post?.id }),
47+
enabled: canViewPostAnalytics({ post, user }),
4348
select: useCallback((data): ImpressionNode[] => {
4449
if (!data) {
4550
return [];

packages/shared/src/components/post/PostUpvotesCommentsCount.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import { Image } from '../image/Image';
77
import { useLazyModal } from '../../hooks/useLazyModal';
88
import { LazyModal } from '../modals/common/types';
99
import { useHasAccessToCores } from '../../hooks/useCoresFeature';
10+
import { canViewPostAnalytics } from '../../lib/user';
11+
import { useAuthContext } from '../../contexts/AuthContext';
12+
import Link from '../utilities/Link';
13+
import { Button, ButtonSize } from '../buttons/Button';
14+
import { AnalyticsIcon } from '../icons';
15+
import { webappUrl } from '../../lib/constants';
1016

1117
interface PostUpvotesCommentsCountProps {
1218
post: Post;
@@ -18,6 +24,7 @@ export function PostUpvotesCommentsCount({
1824
onUpvotesClick,
1925
}: PostUpvotesCommentsCountProps): ReactElement {
2026
const { openModal } = useLazyModal();
27+
const { user } = useAuthContext();
2128
const upvotes = post.numUpvotes || 0;
2229
const comments = post.numComments || 0;
2330
const awards = post.numAwards || 0;
@@ -70,6 +77,18 @@ export function PostUpvotesCommentsCount({
7077
</span>
7178
</ClickableText>
7279
)}
80+
{canViewPostAnalytics({ user, post }) && (
81+
<Link href={`${webappUrl}posts/${post.id}/analytics`} passHref>
82+
<Button
83+
tag="a"
84+
size={ButtonSize.XSmall}
85+
className="font-normal text-text-link"
86+
icon={<AnalyticsIcon />}
87+
>
88+
Post analytics
89+
</Button>
90+
</Link>
91+
)}
7392
</div>
7493
);
7594
}

packages/shared/src/components/post/analytics/PostShortInfo.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Typography, TypographyType } from '../../typography/Typography';
1414
import { ButtonSize, ButtonVariant } from '../../buttons/common';
1515
import { Button } from '../../buttons/Button';
1616
import { webappUrl } from '../../../lib/constants';
17+
import { BoostPostButton } from '../../../features/boost/BoostPostButton';
1718

1819
interface PostShortInfoProps {
1920
post: Post;
@@ -61,6 +62,7 @@ export function PostShortInfo({
6162

6263
const postTitle = title || sharedPost?.title;
6364
const postImage = sharedPost?.image || image;
65+
const isBoosting = !!post.flags?.campaignId;
6466

6567
return (
6668
<div className={classNames('flex items-center gap-2', className)}>
@@ -86,6 +88,16 @@ export function PostShortInfo({
8688
</Typography>
8789
)}
8890
<div className="flex items-center gap-2">
91+
{isBoosting && (
92+
<BoostPostButton
93+
post={post}
94+
buttonProps={{
95+
className: 'typo-footnote',
96+
size: ButtonSize.XSmall,
97+
}}
98+
isActive
99+
/>
100+
)}
89101
{createdAt && (
90102
<DateFormat
91103
prefix="Published "

packages/shared/src/features/boost/BoostPostButton.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ReactElement } from 'react';
22
import React from 'react';
3+
import classNames from 'classnames';
34
import type { Post } from '../../graphql/posts';
45
import type { ButtonProps } from '../../components/buttons/Button';
56
import { Button, ButtonColor } from '../../components/buttons/Button';
@@ -11,23 +12,31 @@ import { useLazyModal } from '../../hooks/useLazyModal';
1112
export function BoostPostButton({
1213
post,
1314
buttonProps = {},
15+
isActive = false,
1416
}: {
1517
post: Post;
1618
buttonProps?: ButtonProps<'button'>;
19+
isActive?: boolean;
1720
}): ReactElement {
1821
const { openModal } = useLazyModal();
1922

2023
return (
2124
<Button
2225
variant={ButtonVariant.Primary}
2326
{...buttonProps}
24-
icon={<BoostIcon secondary />}
27+
className={classNames(
28+
buttonProps?.className,
29+
isActive &&
30+
'border-none bg-action-comment-float font-normal text-surface-focus',
31+
)}
32+
icon={!isActive && <BoostIcon secondary />}
2533
color={ButtonColor.BlueCheese}
34+
disabled={isActive}
2635
onClick={() => {
2736
openModal({ type: LazyModal.BoostPost, props: { post } });
2837
}}
2938
>
30-
Boost
39+
{isActive ? 'Boosting' : 'Boost'}
3140
</Button>
3241
);
3342
}

packages/shared/src/features/posts/PostOptionButton.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
UpvoteIcon,
3131
TrendingIcon,
3232
SettingsIcon,
33+
AnalyticsIcon,
3334
} from '../../components/icons';
3435
import {
3536
Button,
@@ -95,7 +96,7 @@ import { useSourceActionsFollow } from '../../hooks/source/useSourceActionsFollo
9596
import { useIsSpecialUser } from '../../hooks/auth/useIsSpecialUser';
9697
import { isFollowingContent } from '../../hooks/contentPreference/types';
9798
import { MenuIcon } from '../../components/MenuIcon';
98-
import { Roles } from '../../lib/user';
99+
import { canViewPostAnalytics, Roles } from '../../lib/user';
99100
import type { PromptOptions } from '../../hooks/usePrompt';
100101
import { usePrompt } from '../../hooks/usePrompt';
101102
import { BoostIcon } from '../../components/icons/Boost';
@@ -448,14 +449,13 @@ const PostOptionButtonContent = ({
448449
...logOpts,
449450
}),
450451
},
451-
// TODO post-analytics enable when available for public
452-
// canBoost && {
453-
// icon: <MenuIcon Icon={AnalyticsIcon} />,
454-
// label: 'Post analytics',
455-
// anchorProps: {
456-
// href: `${webappUrl}posts/${post.slug}/analytics`,
457-
// },
458-
// },
452+
canViewPostAnalytics({ user, post }) && {
453+
icon: <MenuIcon Icon={AnalyticsIcon} />,
454+
label: 'Post analytics',
455+
anchorProps: {
456+
href: `${webappUrl}posts/${post.id}/analytics`,
457+
},
458+
},
459459
!isBriefPost && {
460460
icon: <MenuIcon Icon={EyeIcon} />,
461461
label: 'Hide',

packages/shared/src/lib/user.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { ContentPreference } from '../graphql/contentPreference';
99
import type { TopReader } from '../components/badges/TopReaderBadge';
1010
import type { SubscriptionProvider, SubscriptionStatus } from './plus';
1111
import type { FeaturedAward, UserTransactionPublic } from '../graphql/njord';
12+
import type { Post } from '../graphql/posts';
1213

1314
export enum Roles {
1415
Moderator = 'moderator',
@@ -265,3 +266,13 @@ export const isSpecialUser = ({
265266
export const getFirstName = (name: string): string => {
266267
return name?.split?.(' ')?.[0] ?? '';
267268
};
269+
270+
export const canViewPostAnalytics = ({
271+
user,
272+
post,
273+
}: {
274+
user?: Pick<LoggedUser, 'id'>;
275+
post?: Pick<Post, 'author'>;
276+
}): boolean => {
277+
return !!user?.id && user.id === post?.author?.id;
278+
};

packages/webapp/pages/posts/[id]/analytics/index.tsx

Lines changed: 81 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ReactElement } from 'react';
2-
import React from 'react';
2+
import React, { useEffect } from 'react';
33
import type { GetServerSideProps } from 'next';
44
import type { NextSeoProps } from 'next-seo';
55
import type { ClientError } from 'graphql-request';
@@ -48,6 +48,7 @@ import {
4848
postAnalyticsQueryOptions,
4949
} from '@dailydotdev/shared/src/graphql/posts';
5050
import type { PublicProfile } from '@dailydotdev/shared/src/lib/user';
51+
import { canViewPostAnalytics } from '@dailydotdev/shared/src/lib/user';
5152
import { ApiError, gqlClient } from '@dailydotdev/shared/src/graphql/common';
5253
import { webappUrl } from '@dailydotdev/shared/src/lib/constants';
5354
import { StaleTime } from '@dailydotdev/shared/src/lib/query';
@@ -64,6 +65,8 @@ import { TimeFormatType } from '@dailydotdev/shared/src/lib/dateFormat';
6465
import { useQuery } from '@tanstack/react-query';
6566
import dynamic from 'next/dynamic';
6667
import { useRouter } from 'next/router';
68+
import { useShowBoostButton } from '@dailydotdev/shared/src/features/boost/useShowBoostButton';
69+
import { useAuthContext } from '@dailydotdev/shared/src/contexts/AuthContext';
6770
import { getSeoDescription } from '../../../../components/PostSEOSchema';
6871
import type { Props } from '../index';
6972
import { seoTitle } from '../index';
@@ -101,7 +104,9 @@ export const getServerSideProps: GetServerSideProps<
101104
const post = initialData.post as Post;
102105
const seo: NextSeoProps = {
103106
canonical: post?.slug ? `${webappUrl}posts/${post.slug}` : undefined,
104-
title: getTemplatedTitle(seoTitle(post)),
107+
title: getTemplatedTitle(
108+
[seoTitle(post), 'Analytics'].filter(Boolean).join(' | '),
109+
),
105110
description: getSeoDescription(post),
106111
openGraph: {
107112
images: [
@@ -162,6 +167,7 @@ const PostAnalyticsPage = ({
162167
initialData,
163168
}: PostAnalyticsPageProps): ReactElement => {
164169
const router = useRouter();
170+
const { user, isAuthReady } = useAuthContext();
165171

166172
const { post } = usePostById({
167173
id,
@@ -171,9 +177,13 @@ const PostAnalyticsPage = ({
171177
},
172178
});
173179

174-
const { data: postAnalytics } = useQuery(
175-
postAnalyticsQueryOptions({ id: post?.id }),
176-
);
180+
const { data: postAnalytics } = useQuery({
181+
...postAnalyticsQueryOptions({ id: post?.id }),
182+
enabled: canViewPostAnalytics({ post, user }),
183+
});
184+
185+
const canBoost = useShowBoostButton({ post });
186+
const isBoosting = !!post?.flags?.campaignId;
177187

178188
const profileActivityList: AnalyticsNumberList = [
179189
{
@@ -243,6 +253,28 @@ const PostAnalyticsPage = ({
243253
},
244254
];
245255

256+
const postLink = `${webappUrl}posts/${
257+
router?.query?.id === post.slug ? post.slug : post.id
258+
}`;
259+
260+
useEffect(() => {
261+
if (!isAuthReady) {
262+
return;
263+
}
264+
265+
if (!post) {
266+
return;
267+
}
268+
269+
if (user?.isTeamMember) {
270+
return;
271+
}
272+
273+
if (!canViewPostAnalytics({ user, post })) {
274+
router.replace(postLink);
275+
}
276+
}, [isAuthReady, post, postLink, router, user]);
277+
246278
return (
247279
<div className="mx-auto w-full max-w-[48rem]">
248280
<LayoutHeader
@@ -253,11 +285,7 @@ const PostAnalyticsPage = ({
253285
size={ButtonSize.Medium}
254286
icon={<ArrowIcon className="-rotate-90" />}
255287
onClick={() => {
256-
router.push(
257-
`${webappUrl}posts/${
258-
router?.query?.id === post.slug ? post.slug : post.id
259-
}`,
260-
);
288+
router.push(postLink);
261289
}}
262290
/>
263291
<Typography
@@ -268,7 +296,14 @@ const PostAnalyticsPage = ({
268296
>
269297
Analytics
270298
</Typography>
271-
<BoostPostButton post={post} buttonProps={{ size: ButtonSize.Small }} />
299+
<BoostPostButton
300+
post={post}
301+
buttonProps={{
302+
className: isBoosting && 'typo-footnote',
303+
size: isBoosting ? ButtonSize.XSmall : ButtonSize.Small,
304+
}}
305+
isActive={isBoosting}
306+
/>
272307
</LayoutHeader>
273308
<ResponsivePageContainer className="!mx-0 !w-full !max-w-full gap-6">
274309
<SectionContainer>
@@ -317,36 +352,46 @@ const PostAnalyticsPage = ({
317352
<div className="size-2 rounded-full bg-brand-default" />{' '}
318353
<Typography type={TypographyType.Footnote}>Organic</Typography>
319354
</div>
320-
<div className="flex items-center gap-1">
321-
<div className="size-2 rounded-full bg-accent-blueCheese-default" />{' '}
322-
<Typography type={TypographyType.Footnote}>Boosted</Typography>
323-
</div>
355+
{/* TODO post-analytics enable boosting data */}
356+
{false && (
357+
<div className="flex items-center gap-1">
358+
<div className="size-2 rounded-full bg-accent-blueCheese-default" />{' '}
359+
<Typography type={TypographyType.Footnote}>
360+
Boosted
361+
</Typography>
362+
</div>
363+
)}
324364
</div>
325365
</div>
326366
<ImpressionsChart post={post} />
327367
</SectionContainer>
328368
<Divider className={dividerClassName} />
329-
<SectionContainer>
330-
<SectionHeader>Boost your post</SectionHeader>
331-
<Typography
332-
type={TypographyType.Callout}
333-
color={TypographyColor.Tertiary}
334-
>
335-
Give your content the spotlight it deserves. Our auto-targeting
336-
engine gets your post in front of developers most likely to care.
337-
</Typography>
338-
<Typography
339-
type={TypographyType.Footnote}
340-
color={TypographyColor.Boost}
341-
>
342-
Reach up to 100k more developers now
343-
</Typography>
344-
<BoostPostButton
345-
post={post}
346-
buttonProps={{ size: ButtonSize.Small, className: 'mr-auto' }}
347-
/>
348-
</SectionContainer>
349-
<Divider className={dividerClassName} />
369+
{canBoost && (
370+
<>
371+
<SectionContainer>
372+
<SectionHeader>Boost your post</SectionHeader>
373+
<Typography
374+
type={TypographyType.Callout}
375+
color={TypographyColor.Tertiary}
376+
>
377+
Give your content the spotlight it deserves. Our auto-targeting
378+
engine gets your post in front of developers most likely to
379+
care.
380+
</Typography>
381+
<Typography
382+
type={TypographyType.Footnote}
383+
color={TypographyColor.Boost}
384+
>
385+
Reach up to 100k more developers now
386+
</Typography>
387+
<BoostPostButton
388+
post={post}
389+
buttonProps={{ size: ButtonSize.Small, className: 'mr-auto' }}
390+
/>
391+
</SectionContainer>
392+
<Divider className={dividerClassName} />
393+
</>
394+
)}
350395
{/* TODO post-analytics enable boosting data */}
351396
{false && (
352397
<SectionContainer>

0 commit comments

Comments
 (0)