PluginProbe ʕ •ᴥ•ʔ
Presto Player / trunk
Presto Player vtrunk
4.3.1 4.3.0 4.2.4 4.2.3 4.2.2 4.2.0 4.2.1 trunk 1.10.0 1.10.1 1.10.2 1.11.0 1.12.0 1.13.0 1.14.0 1.14.1 1.5.10 1.5.11 1.5.12 1.5.13 1.5.14 1.5.15 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 1.6.13 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.3-beta1 2.3.0 2.3.1 2.3.2 2.3.3 3.0.0 3.0.0-beta1 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.1.0 3.1.1 3.1.2 3.1.3 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4
presto-player / src / admin / dashboard / hooks / useUserDetail.js
presto-player / src / admin / dashboard / hooks Last commit date
test 3 weeks ago useCompleteOnboarding.js 3 weeks ago useDateRangePicker.js 1 month ago useEmail.ts 1 month ago useEngagementChartData.js 1 month ago useLicenseSettings.js 1 month ago useLink.js 1 month ago useMediaDetail.js 1 month ago useMediaLibrary.js 1 month ago useMediaList.ts 1 month ago usePerformanceSettings.js 1 month ago useRegisterActivePage.js 1 month ago useSettingOption.js 1 month ago useSimpleSettingsPage.js 1 month ago useTopPerforming.js 1 month ago useTopVideosPaginated.js 1 month ago useUpgradeCTA.js 1 month ago useUserDetail.js 1 month ago
useUserDetail.js
196 lines
1 import { useState, useEffect, useCallback, useMemo, useRef } from "@wordpress/element";
2 import apiFetch from "@wordpress/api-fetch";
3 import { addQueryArgs } from "@wordpress/url";
4 import {
5 getLastNDays,
6 humanSeconds,
7 toAnalyticsDate,
8 DEFAULT_ANALYTICS_DAYS,
9 } from "../utils";
10 import useTopVideosPaginated from "./useTopVideosPaginated";
11
12 const { __ } = wp.i18n;
13
14 const TOP_MEDIA_PER_PAGE = 10;
15
16 /**
17 * Hook to fetch analytics data for a single user.
18 *
19 * Owns user metadata + stats. Delegates the paginated Top Media table
20 * to `useTopVideosPaginated` (the same hook the Analytics page uses,
21 * with `userId` set so the server-side filter applies).
22 *
23 * Fetches:
24 * - User metadata (name, email, avatar) via WP REST API
25 * - Total / average / total watch time stats
26 * - Current page of top media (paginated; default last-30-days range
27 * when no explicit range is set)
28 */
29 const useUserDetail = (userId) => {
30 const [user, setUser] = useState(null);
31 const [stats, setStats] = useState({
32 totalViews: 0,
33 avgWatchTime: "0s",
34 totalWatchTime: "0s",
35 });
36 const [dateRange, setDateRange] = useState(null);
37 const [isLoading, setIsLoading] = useState(true);
38 const [error, setError] = useState(null);
39
40 const statsAbortRef = useRef(null);
41
42 // The user detail page mirrors the Analytics page's default scope:
43 // last 7 days when no explicit range is set, matching the picker default.
44 const dateParams = useMemo(() => {
45 const range =
46 dateRange?.from && dateRange?.to
47 ? dateRange
48 : getLastNDays(DEFAULT_ANALYTICS_DAYS);
49 return {
50 start: toAnalyticsDate(range.from),
51 end: toAnalyticsDate(range.to),
52 };
53 }, [dateRange]);
54
55 const {
56 topMedia,
57 page: mediaPage,
58 setPage: setMediaPage,
59 pagination: mediaPagination,
60 perPage: mediaPerPage,
61 } = useTopVideosPaginated({
62 userId,
63 dateParams,
64 perPage: TOP_MEDIA_PER_PAGE,
65 enabled: !!userId,
66 });
67
68 // Stats + user metadata fetch. Page changes don't trigger this — only
69 // userId or date changes do.
70 useEffect(() => {
71 if (!userId) return undefined;
72
73 statsAbortRef.current?.abort();
74 statsAbortRef.current = new AbortController();
75 const { signal } = statsAbortRef.current;
76
77 (async () => {
78 try {
79 setIsLoading(true);
80 setError(null);
81
82 const userResponse = await apiFetch({
83 path: `/wp/v2/users/${userId}?context=edit`,
84 signal,
85 });
86
87 setUser({
88 id: userResponse.id,
89 name: userResponse.name || `User #${userId}`,
90 email: userResponse.email || "",
91 avatarUrl:
92 userResponse.avatar_urls?.["96"] ||
93 userResponse.avatar_urls?.["48"] ||
94 null,
95 profileUrl: `${window.location.origin}/wp-admin/user-edit.php?user_id=${userResponse.id}`,
96 });
97
98 try {
99 const [totalViewsRes, avgWatchTimeRes, totalWatchTimeRes] =
100 await Promise.all([
101 apiFetch({
102 path: addQueryArgs(
103 `/presto-player/v1/analytics/user/${userId}/total-views`,
104 dateParams
105 ),
106 signal,
107 }),
108 apiFetch({
109 path: addQueryArgs(
110 `/presto-player/v1/analytics/user/${userId}/average-watchtime`,
111 dateParams
112 ),
113 signal,
114 }),
115 apiFetch({
116 path: addQueryArgs(
117 `/presto-player/v1/analytics/user/${userId}/total-watchtime`,
118 dateParams
119 ),
120 signal,
121 }),
122 ]);
123
124 const viewCount = parseInt(totalViewsRes?.view) || 0;
125 setStats({
126 totalViews: viewCount,
127 avgWatchTime: viewCount
128 ? humanSeconds(parseFloat(avgWatchTimeRes?.view) || 0)
129 : "0s",
130 totalWatchTime: viewCount
131 ? humanSeconds(parseFloat(totalWatchTimeRes?.view) || 0)
132 : "0s",
133 });
134 } catch (analyticsErr) {
135 if (analyticsErr.name !== "AbortError") {
136 console.warn(
137 "Analytics data unavailable, using defaults:",
138 analyticsErr
139 );
140 setStats({
141 totalViews: 0,
142 avgWatchTime: "0s",
143 totalWatchTime: "0s",
144 });
145 }
146 }
147 } catch (err) {
148 if (err.name !== "AbortError") {
149 if (
150 err.code === "rest_user_invalid_id" ||
151 err.code === "rest_no_route"
152 ) {
153 setError(__("User not found", "presto-player"));
154 } else {
155 setError(
156 err.message ||
157 __("Failed to load user details", "presto-player")
158 );
159 }
160 console.error("Error fetching user detail:", err);
161 }
162 } finally {
163 setIsLoading(false);
164 }
165 })();
166
167 return () => statsAbortRef.current?.abort();
168 }, [userId, dateParams]);
169
170 // Public refetch hook for the date-range picker. The shared media hook
171 // resets its own page on dateParams change; we just push the new range
172 // into local state.
173 const refetch = useCallback((startDate, endDate) => {
174 if (startDate && endDate) {
175 setDateRange({ from: startDate, to: endDate });
176 } else {
177 setDateRange(null);
178 }
179 }, []);
180
181 return {
182 user,
183 stats,
184 topMedia,
185 mediaPage,
186 setMediaPage,
187 mediaPagination,
188 mediaPerPage,
189 isLoading,
190 error,
191 refetch,
192 };
193 };
194
195 export default useUserDetail;
196