Skip to content

Commit 9d8c66f

Browse files
committed
refactor: consolidate and cleanup utils
1 parent 1b908c4 commit 9d8c66f

File tree

13 files changed

+162
-305
lines changed

13 files changed

+162
-305
lines changed

apps/api/src/query/builders/performance.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const PerformanceBuilders: Record<string, SimpleQueryConfig> = {
88
slow_pages: {
99
table: Analytics.events,
1010
fields: [
11-
"trimRight(path, '/') as name",
11+
"decodeURLComponent(CASE WHEN trimRight(path(path), '/') = '' THEN '/' ELSE trimRight(path(path), '/') END) as name",
1212
'COUNT(DISTINCT anonymous_id) as visitors',
1313
'AVG(CASE WHEN load_time > 0 THEN load_time ELSE NULL END) as avg_load_time',
1414
'quantile(0.50)(CASE WHEN load_time > 0 THEN load_time ELSE NULL END) as p50_load_time',
@@ -24,7 +24,9 @@ export const PerformanceBuilders: Record<string, SimpleQueryConfig> = {
2424
'COUNT(*) as pageviews',
2525
],
2626
where: ["event_name = 'screen_view'", "path != ''", 'load_time > 0'],
27-
groupBy: ["trimRight(path, '/')"],
27+
groupBy: [
28+
"decodeURLComponent(CASE WHEN trimRight(path(path), '/') = '' THEN '/' ELSE trimRight(path(path), '/') END)",
29+
],
2830
orderBy: 'p95_load_time DESC',
2931
limit: 100,
3032
timeField: 'time',
@@ -166,7 +168,7 @@ export const PerformanceBuilders: Record<string, SimpleQueryConfig> = {
166168
web_vitals_by_page: {
167169
table: Analytics.web_vitals,
168170
fields: [
169-
"trimRight(path, '/') as name",
171+
"decodeURLComponent(CASE WHEN trimRight(path(path), '/') = '' THEN '/' ELSE trimRight(path(path), '/') END) as name",
170172
'COUNT(DISTINCT anonymous_id) as visitors',
171173
'AVG(CASE WHEN fcp > 0 THEN fcp ELSE NULL END) as avg_fcp',
172174
'quantile(0.50)(CASE WHEN fcp > 0 THEN fcp ELSE NULL END) as p50_fcp',
@@ -195,7 +197,9 @@ export const PerformanceBuilders: Record<string, SimpleQueryConfig> = {
195197
'COUNT(*) as measurements',
196198
],
197199
where: ["path != ''"],
198-
groupBy: ["trimRight(path, '/')"],
200+
groupBy: [
201+
"decodeURLComponent(CASE WHEN trimRight(path(path), '/') = '' THEN '/' ELSE trimRight(path(path), '/') END)",
202+
],
199203
orderBy: 'p95_lcp DESC',
200204
limit: 100,
201205
timeField: 'timestamp',

apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
type ReferrerSourceCellData,
2828
} from '@/components/atomic/ReferrerSourceCell';
2929
import { MetricsChart } from '@/components/charts/metrics-chart';
30+
import { BrowserIcon, OSIcon } from '@/components/icon';
3031
import { useBatchDynamicQuery } from '@/hooks/use-dynamic-query';
3132
import { useTableTabs } from '@/lib/table-tabs';
3233
import { getUserTimezone } from '@/lib/timezone';
@@ -39,12 +40,7 @@ import {
3940
formatDateByGranularity,
4041
getColorVariant,
4142
} from '../utils/analytics-helpers';
42-
import {
43-
getBrowserIcon,
44-
getOSIcon,
45-
PercentageBadge,
46-
TechnologyIcon,
47-
} from '../utils/technology-helpers';
43+
import { PercentageBadge } from '../utils/technology-helpers';
4844
import type { FullTabProps, MetricPoint } from '../utils/types';
4945
import { MetricToggles } from '../utils/ui-components';
5046

@@ -450,11 +446,10 @@ export function WebsiteOverviewTab({
450446

451447
const createTechnologyCell = (type: 'browser' | 'os') => (info: CellInfo) => {
452448
const entry = info.row.original as TechnologyData;
453-
const icon =
454-
type === 'browser' ? getBrowserIcon(entry.name) : getOSIcon(entry.name);
449+
const IconComponent = type === 'browser' ? BrowserIcon : OSIcon;
455450
return (
456451
<div className="flex items-center gap-3">
457-
<TechnologyIcon entry={{ ...entry, icon, category: type }} size="md" />
452+
<IconComponent name={entry.name} size="md" />
458453
<span className="font-medium">{entry.name}</span>
459454
</div>
460455
);

apps/dashboard/app/(main)/websites/[id]/_components/utils/technology-helpers.tsx

Lines changed: 34 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ import {
77
Tablet,
88
Tv,
99
} from 'lucide-react';
10+
import Image from 'next/image';
1011
import type React from 'react';
12+
import { BrowserIcon, OSIcon } from '@/components/icon';
13+
14+
// Regex patterns for browser name processing
15+
const MOBILE_PREFIX_REGEX = /^Mobile\s+/;
16+
const MOBILE_SUFFIX_REGEX = /\s+Mobile$/;
1117

1218
// Types
1319
export interface DeviceTypeEntry {
@@ -76,62 +82,6 @@ export const getDeviceTypeIcon = (
7682
return <HelpCircle className={`${className} text-muted-foreground`} />;
7783
};
7884

79-
// Enhanced browser icon mapping
80-
export const getBrowserIcon = (browser: string): string => {
81-
const browserLower = browser.toLowerCase();
82-
83-
const iconMap: Record<string, string> = {
84-
chrome: '/browsers/Chrome.svg',
85-
firefox: '/browsers/Firefox.svg',
86-
safari: '/browsers/Safari.svg',
87-
edge: '/browsers/Edge.svg',
88-
opera: '/browsers/Opera.svg',
89-
ie: '/browsers/IE.svg',
90-
'internet explorer': '/browsers/IE.svg',
91-
samsung: '/browsers/SamsungInternet.svg',
92-
yandex: '/browsers/Yandex.svg',
93-
ucbrowser: '/browsers/UCBrowser.svg',
94-
qq: '/browsers/QQ.webp',
95-
baidu: '/browsers/Baidu.svg',
96-
duckduckgo: '/browsers/DuckDuckGo.svg',
97-
brave: '/browsers/Brave.svg',
98-
vivaldi: '/browsers/Vivaldi.svg',
99-
};
100-
101-
for (const [key, path] of Object.entries(iconMap)) {
102-
if (browserLower.includes(key)) {
103-
return path;
104-
}
105-
}
106-
107-
return '/browsers/Chrome.svg';
108-
};
109-
110-
// Enhanced OS icon mapping
111-
export const getOSIcon = (os: string): string => {
112-
const osLower = os.toLowerCase();
113-
114-
const iconMap: Record<string, string> = {
115-
windows: '/operating-systems/Windows.svg',
116-
mac: '/operating-systems/macOS.svg',
117-
darwin: '/operating-systems/macOS.svg',
118-
android: '/operating-systems/Android.svg',
119-
linux: '/operating-systems/Ubuntu.svg',
120-
ubuntu: '/operating-systems/Ubuntu.svg',
121-
chrome: '/operating-systems/Chrome.svg',
122-
harmony: '/operating-systems/HarmonyOS.svg',
123-
ios: '/operating-systems/Apple.svg',
124-
};
125-
126-
for (const [key, path] of Object.entries(iconMap)) {
127-
if (osLower.includes(key)) {
128-
return path;
129-
}
130-
}
131-
132-
return '/operating-systems/Ubuntu.svg';
133-
};
134-
13585
export const processDeviceData = (
13686
deviceTypes: DeviceTypeEntry[]
13787
): TechnologyTableEntry[] => {
@@ -163,7 +113,6 @@ export const processDeviceData = (
163113
}));
164114
};
165115

166-
// Process browser data with percentages and enhanced icons
167116
export const processBrowserData = (
168117
browserVersions: BrowserVersionEntry[]
169118
): TechnologyTableEntry[] => {
@@ -172,8 +121,8 @@ export const processBrowserData = (
172121
for (const item of browserVersions) {
173122
let browserName = item.browser || 'Unknown';
174123
browserName = browserName
175-
.replace(/^Mobile\s+/, '')
176-
.replace(/\s+Mobile$/, '');
124+
.replace(MOBILE_PREFIX_REGEX, '')
125+
.replace(MOBILE_SUFFIX_REGEX, '');
177126
browserGroups[browserName] =
178127
(browserGroups[browserName] || 0) + (item.visitors || 0);
179128
}
@@ -191,12 +140,11 @@ export const processBrowserData = (
191140
visitors,
192141
percentage:
193142
totalVisitors > 0 ? Math.round((visitors / totalVisitors) * 100) : 0,
194-
icon: getBrowserIcon(name),
143+
iconComponent: <BrowserIcon name={name} size="md" />,
195144
category: 'browser',
196145
}));
197146
};
198147

199-
// Enhanced icon component for tables
200148
export const TechnologyIcon = ({
201149
entry,
202150
size = 'md',
@@ -208,38 +156,36 @@ export const TechnologyIcon = ({
208156
return <>{entry.iconComponent}</>;
209157
}
210158

211-
if (entry.icon) {
212-
const sizeClasses = {
213-
sm: 'h-3 w-3',
214-
md: 'h-4 w-4',
215-
lg: 'h-5 w-5',
216-
};
217-
218-
return (
219-
<img
220-
alt={entry.name}
221-
className={`${sizeClasses[size]} object-contain`}
222-
onError={(e) => {
223-
(e.target as HTMLImageElement).style.display = 'none';
224-
}}
225-
src={`${entry.icon}`}
226-
/>
227-
);
159+
// Use unified icon components for better consistency
160+
if (entry.category === 'browser') {
161+
return <BrowserIcon name={entry.name} size={size} />;
228162
}
229163

230-
// For OS entries without icon, use getOSIcon
231164
if (entry.category === 'os') {
232-
const sizeClasses = {
233-
sm: 'h-3 w-3',
234-
md: 'h-4 w-4',
235-
lg: 'h-5 w-5',
165+
return <OSIcon name={entry.name} size={size} />;
166+
}
167+
168+
// Fallback for other categories or when no category is specified
169+
if (entry.icon) {
170+
const sizeMap = {
171+
sm: 12,
172+
md: 16,
173+
lg: 20,
236174
};
175+
const iconSize = sizeMap[size];
176+
237177
return (
238-
<img
239-
alt={entry.name}
240-
className={`${sizeClasses[size]} object-contain`}
241-
src={getOSIcon(entry.name)}
242-
/>
178+
<div
179+
className="relative flex-shrink-0"
180+
style={{ width: iconSize, height: iconSize }}
181+
>
182+
<Image
183+
alt={entry.name}
184+
className="object-contain"
185+
fill
186+
src={entry.icon}
187+
/>
188+
</div>
243189
);
244190
}
245191

apps/dashboard/app/(main)/websites/[id]/profiles/_components/profile-row.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,8 @@ type ProfileData = {
5757
}>;
5858
};
5959

60-
import {
61-
formatDuration,
62-
getBrowserIconComponent,
63-
getCountryFlag,
64-
getDeviceIcon,
65-
getOSIconComponent,
66-
} from './profile-utils';
60+
import { BrowserIcon, CountryFlag, OSIcon } from '@/components/icon';
61+
import { formatDuration, getDeviceIcon } from '@/lib/utils';
6762

6863
interface ProfileRowProps {
6964
profile: ProfileData;
@@ -152,10 +147,10 @@ export function ProfileRow({
152147
</div>
153148
</div>
154149
<div className="flex flex-shrink-0 items-center gap-2">
155-
{getCountryFlag(profile.country)}
150+
<CountryFlag country={profile.country} size="md" />
156151
{getDeviceIcon(profile.device)}
157-
{getBrowserIconComponent(profile.browser)}
158-
{getOSIconComponent(profile.os)}
152+
<BrowserIcon name={profile.browser} size="md" />
153+
<OSIcon name={profile.os} size="md" />
159154
</div>
160155

161156
{/* Profile Info */}

apps/dashboard/app/(main)/websites/[id]/profiles/_components/profile-utils.tsx

Lines changed: 0 additions & 85 deletions
This file was deleted.

apps/dashboard/app/(main)/websites/[id]/profiles/_components/profiles-list.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { useDateFilters } from '@/hooks/use-date-filters';
99
import { useProfilesData } from '@/hooks/use-dynamic-query';
1010
import { dynamicQueryFiltersAtom } from '@/stores/jotai/filterAtoms';
1111

12-
// Type adapter for the new profile data structure
1312
type ProfileData = {
1413
visitor_id: string;
1514
first_visit: string;

0 commit comments

Comments
 (0)