diff --git a/packages/shared/src/features/profile/components/ProfileWidgets/ProfileViewsWidget.tsx b/packages/shared/src/features/profile/components/ProfileWidgets/ProfileViewsWidget.tsx
new file mode 100644
index 0000000000..e7f466f7de
--- /dev/null
+++ b/packages/shared/src/features/profile/components/ProfileWidgets/ProfileViewsWidget.tsx
@@ -0,0 +1,170 @@
+import type { ReactElement } from 'react';
+import React, { useMemo } from 'react';
+import { useQuery } from '@tanstack/react-query';
+import {
+ startOfWeek,
+ startOfMonth,
+ isAfter,
+ parseISO,
+ isEqual,
+} from 'date-fns';
+import { ActivityContainer } from '../../../../components/profile/ActivitySection';
+import {
+ Typography,
+ TypographyColor,
+ TypographyTag,
+ TypographyType,
+} from '../../../../components/typography/Typography';
+import { gqlClient } from '../../../../graphql/common';
+import type {
+ UserProfileAnalytics,
+ UserProfileAnalyticsHistory,
+} from '../../../../graphql/users';
+import {
+ USER_PROFILE_ANALYTICS_HISTORY_QUERY,
+ USER_PROFILE_ANALYTICS_QUERY,
+} from '../../../../graphql/users';
+import { generateQueryKey, RequestKey } from '../../../../lib/query';
+import { largeNumberFormat } from '../../../../lib';
+import { SummaryCard } from './BadgesAndAwardsComponents';
+import { ElementPlaceholder } from '../../../../components/ElementPlaceholder';
+
+interface ProfileViewsWidgetProps {
+ userId: string;
+}
+
+interface HistoryQueryResult {
+ userProfileAnalyticsHistory: {
+ edges: Array<{
+ node: UserProfileAnalyticsHistory;
+ }>;
+ };
+}
+
+interface AnalyticsQueryResult {
+ userProfileAnalytics: UserProfileAnalytics | null;
+}
+
+const ProfileViewsWidgetSkeleton = (): ReactElement => {
+ return (
+
+
+ Profile Activity
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const ProfileViewsWidget = ({
+ userId,
+}: ProfileViewsWidgetProps): ReactElement => {
+ const { data: historyData, isPending: isHistoryPending } =
+ useQuery({
+ queryKey: generateQueryKey(RequestKey.ProfileAnalyticsHistory, {
+ id: userId,
+ }),
+ queryFn: () =>
+ gqlClient.request(USER_PROFILE_ANALYTICS_HISTORY_QUERY, {
+ userId,
+ first: 31,
+ }),
+ enabled: !!userId,
+ refetchOnWindowFocus: false,
+ });
+
+ const { data: analyticsData, isPending: isAnalyticsPending } =
+ useQuery({
+ queryKey: generateQueryKey(RequestKey.ProfileAnalytics, { id: userId }),
+ queryFn: () =>
+ gqlClient.request(USER_PROFILE_ANALYTICS_QUERY, {
+ userId,
+ }),
+ enabled: !!userId,
+ refetchOnWindowFocus: false,
+ });
+
+ const { thisWeek, thisMonth } = useMemo(() => {
+ if (!historyData?.userProfileAnalyticsHistory?.edges) {
+ return { thisWeek: 0, thisMonth: 0 };
+ }
+
+ const now = new Date();
+ const weekStart = startOfWeek(now, { weekStartsOn: 1 });
+ const monthStart = startOfMonth(now);
+
+ let weekTotal = 0;
+ let monthTotal = 0;
+
+ historyData.userProfileAnalyticsHistory.edges.forEach(({ node }) => {
+ const entryDate = parseISO(node.date);
+
+ if (isAfter(entryDate, monthStart) || isEqual(entryDate, monthStart)) {
+ monthTotal += node.uniqueVisitors;
+ }
+
+ if (isAfter(entryDate, weekStart) || isEqual(entryDate, weekStart)) {
+ weekTotal += node.uniqueVisitors;
+ }
+ });
+
+ return { thisWeek: weekTotal, thisMonth: monthTotal };
+ }, [historyData]);
+
+ const total = analyticsData?.userProfileAnalytics?.uniqueVisitors ?? 0;
+
+ if (isHistoryPending || isAnalyticsPending) {
+ return ;
+ }
+
+ return (
+
+
+ Profile Activity
+
+
+
+ );
+};
diff --git a/packages/shared/src/features/profile/components/ProfileWidgets/ProfileWidgets.tsx b/packages/shared/src/features/profile/components/ProfileWidgets/ProfileWidgets.tsx
index 9dbd757961..7a448b37cf 100644
--- a/packages/shared/src/features/profile/components/ProfileWidgets/ProfileWidgets.tsx
+++ b/packages/shared/src/features/profile/components/ProfileWidgets/ProfileWidgets.tsx
@@ -10,9 +10,11 @@ import type { ProfileReadingData, ProfileV2 } from '../../../../graphql/users';
import { USER_READING_HISTORY_QUERY } from '../../../../graphql/users';
import { generateQueryKey, RequestKey } from '../../../../lib/query';
import { gqlClient } from '../../../../graphql/common';
+import { canViewUserProfileAnalytics } from '../../../../lib/user';
import { ReadingOverview } from './ReadingOverview';
import { ProfileCompletion } from './ProfileCompletion';
import { Share } from './Share';
+import { ProfileViewsWidget } from './ProfileViewsWidget';
const BadgesAndAwards = dynamic(() =>
import('./BadgesAndAwards').then((mod) => mod.BadgesAndAwards),
@@ -63,6 +65,10 @@ export function ProfileWidgets({
{isSameUser && (
)}
+ {canViewUserProfileAnalytics({
+ user: loggedUser,
+ profileUserId: user.id,
+ }) && }
=> {
const res = await gqlClient.request(USER_STREAK_QUERY);
return res.userStreak;
};
+export const USER_PROFILE_ANALYTICS_QUERY = gql`
+ query UserProfileAnalytics($userId: ID!) {
+ userProfileAnalytics(userId: $userId) {
+ id
+ uniqueVisitors
+ updatedAt
+ }
+ }
+`;
+
+export const USER_PROFILE_ANALYTICS_HISTORY_QUERY = gql`
+ query UserProfileAnalyticsHistory($userId: ID!, $first: Int) {
+ userProfileAnalyticsHistory(userId: $userId, first: $first) {
+ edges {
+ node {
+ id
+ date
+ uniqueVisitors
+ }
+ }
+ }
+ }
+`;
+
export interface UserStreakRecoverData {
canRecover: boolean;
cost: number;
diff --git a/packages/shared/src/lib/query.ts b/packages/shared/src/lib/query.ts
index 2b84978312..6416c9f355 100644
--- a/packages/shared/src/lib/query.ts
+++ b/packages/shared/src/lib/query.ts
@@ -212,6 +212,8 @@ export enum RequestKey {
NotificationSettings = 'notification_settings',
PostAnalytics = 'post_analytics',
PostAnalyticsHistory = 'post_analytics_history',
+ ProfileAnalytics = 'profile_analytics',
+ ProfileAnalyticsHistory = 'profile_analytics_history',
CheckLocation = 'check_location',
GenerateBrief = 'generate_brief',
Opportunity = 'opportunity',
diff --git a/packages/shared/src/lib/user.ts b/packages/shared/src/lib/user.ts
index 8ffd6c6173..f19655abd1 100644
--- a/packages/shared/src/lib/user.ts
+++ b/packages/shared/src/lib/user.ts
@@ -306,6 +306,20 @@ export const canViewPostAnalytics = ({
return !!user?.id && user.id === post?.author?.id;
};
+export const canViewUserProfileAnalytics = ({
+ user,
+ profileUserId,
+}: {
+ user?: Pick;
+ profileUserId?: string;
+}): boolean => {
+ if (user?.isTeamMember) {
+ return true;
+ }
+
+ return !!user?.id && user.id === profileUserId;
+};
+
export const userProfileQueryOptions = ({ id }) => {
return {
queryKey: generateQueryKey(
diff --git a/packages/webapp/components/layouts/ProfileLayout/index.tsx b/packages/webapp/components/layouts/ProfileLayout/index.tsx
index 301490010c..fc72734834 100644
--- a/packages/webapp/components/layouts/ProfileLayout/index.tsx
+++ b/packages/webapp/components/layouts/ProfileLayout/index.tsx
@@ -20,8 +20,7 @@ import type { NextSeoProps } from 'next-seo';
import { useProfile } from '@dailydotdev/shared/src/hooks/profile/useProfile';
import CustomAuthBanner from '@dailydotdev/shared/src/components/auth/CustomAuthBanner';
import { useLogContext } from '@dailydotdev/shared/src/contexts/LogContext';
-import { LogEvent, TargetType } from '@dailydotdev/shared/src/lib/log';
-import { usePostReferrerContext } from '@dailydotdev/shared/src/contexts/PostReferrerContext';
+import { LogEvent } from '@dailydotdev/shared/src/lib/log';
import { getLayout as getFooterNavBarLayout } from '../FooterNavBarLayout';
import { getLayout as getMainLayout } from '../MainLayout';
import { getTemplatedTitle } from '../utils';
@@ -90,7 +89,6 @@ export default function ProfileLayout({
const { user } = useProfile(initialUser);
const [trackedView, setTrackedView] = useState(false);
const { logEvent } = useLogContext();
- const { referrerPost } = usePostReferrerContext();
// Auto-collapse sidebar on small screens
useProfileSidebarCollapse();
@@ -103,16 +101,9 @@ export default function ProfileLayout({
logEvent({
event_name: LogEvent.ProfileView,
target_id: user.id,
- ...(!!referrerPost && {
- extra: JSON.stringify({
- referrer_target_id: referrerPost.id,
- referrer_target_type: TargetType.Post,
- author: user?.id && referrerPost.author?.id === user.id ? 1 : 0,
- }),
- }),
});
setTrackedView(true);
- }, [user, trackedView, logEvent, referrerPost]);
+ }, [user, trackedView, logEvent]);
if (!isFallback && !user) {
return ;