Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/popup/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const PopupApp: React.FC = () => {
activePage.tab === tab &&
'bg-neutral-300 text-neutral-800 dark:bg-neutral-900 dark:text-neutral-200',
activePage.tab !== tab &&
'hover:bg-neutral-100 text-neutral-400 dark:hover:bg-neutral-900 dark:text-neutral-400',
'hover:bg-neutral-100 text-[#737373] dark:hover:bg-neutral-900 dark:text-neutral-400',
tab === Pages.Preferences && 'max-w-[75px]',
)}
key={tab}
Expand All @@ -83,9 +83,9 @@ export const PopupApp: React.FC = () => {
return (
<PopupContextProvider>
<div className="flex flex-col p-2 pt-4 dark:bg-neutral-900">
<div className="text-orange-500 p-2 border-none bg-slate-200 dark:bg-slate-800 tab-body-shadow dark:dark-tab-body-shadow">
<div className="text-orange-500 p-2 border-none bg-slate-200 dark:bg-slate-800 tab-body-shadow dark:dark-tab-body-shadow flex">
<img src="./icons/icon-16.png"></img>
Codealike
<span className='font-semibold px-2'>Codealike</span>
</div>
<Panel className="flex gap-2 p-2 font-semibold">{tabs}</Panel>
<Panel className="p-2 border-none bg-slate-200 dark:bg-slate-800 tab-body-shadow dark:dark-tab-body-shadow">
Expand Down
2 changes: 2 additions & 0 deletions src/popup/components/ActivityDatePicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const ActivityDatePicker: React.FC<ActivityDatePickerProps> = ({
<Button
buttonType={ButtonType.Secondary}
onClick={() => onDateChangeButtonClick(-1)}
className='px-4'
>
<Icon className="m-0 flex" type={IconType.LeftArrow} />
</Button>
Expand All @@ -50,6 +51,7 @@ export const ActivityDatePicker: React.FC<ActivityDatePickerProps> = ({
<Button
buttonType={ButtonType.Secondary}
onClick={() => onDateChangeButtonClick(1)}
className='px-4'
>
<Icon className="m-0 flex" type={IconType.RightArrow} />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const DailyActivityTab: React.FC<DailyActivityTabProps> = ({
title="Activity Timeline"
activityTimeline={activityTimeline}
filteredHostname={filteredHostname}
description="Your web activity timeline."
/>
</div>
<WebsiteActivityTable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as React from 'react';

import { TimeStore } from '../../hooks/useTimeStore';
import { get30DaysPriorDate, getIsoDate } from '../../../shared/utils/dates-helper';

import { TimeUsagePanel } from '../DailyTimeUsage/DailyTimeUsage';
import { WebsiteActivityTable } from '../WebsiteActivityTable/WebsiteActivityTable';
import { ActivityPageMonthlyActivityTabProps } from './types';
import { getTotalMonthlyActivity } from '../../selectors/get-total-monthly-activity';
import { MonthlyWebsiteActivityChart } from '../MonthlyWebsiteActivityChart/MonthlyWebsiteActivityChart';


export const ActivityPageMonthlyActivityTab: React.FC<ActivityPageMonthlyActivityTabProps> =
({ store, sundayDate }) => {
const [pickedDomain, setPickedDomain] = React.useState<null | string>(null);
const scrollToRef = React.useRef<HTMLDivElement | null>(null);

const handleDomainRowClick = React.useCallback((domain: string) => {
setPickedDomain(domain);
scrollToRef.current?.scrollIntoView({ behavior: 'smooth' });
}, []);

const allMonthlyActivity = React.useMemo(
() =>
get30DaysPriorDate(sundayDate).reduce((acc, date) => {
const isoDate = getIsoDate(date);
acc[isoDate] = store[isoDate] || {};

return acc;
}, {} as TimeStore),
[store, sundayDate]
);

const filteredWebsiteMonthActivity = React.useMemo(() => {
if (pickedDomain === null) {
return allMonthlyActivity;
}

return Object.entries(allMonthlyActivity).reduce(
(acc, [date, dateWebsitesUsage]) => {
acc[date] = {
[pickedDomain]: dateWebsitesUsage[pickedDomain] || 0,
};

return acc;
},
{} as typeof allMonthlyActivity
);
}, [allMonthlyActivity, pickedDomain]);

const totalWebsiteMonthlyActivity = React.useMemo(
() =>
Object.values(allMonthlyActivity).reduce((acc, dailyUsage) => {
Object.entries(dailyUsage).forEach(([key, value]) => {
acc[key] ??= 0;
acc[key] += value;
});

return acc;
}, {} as Record<string, number>),
[allMonthlyActivity]
);

const averageMonthlyActivity = React.useMemo(() => {
const averageMonthly =
getTotalMonthlyActivity(filteredWebsiteMonthActivity, sundayDate) / 7;
return averageMonthly;
}, [filteredWebsiteMonthActivity, sundayDate]);

const presentedPickedDomain = pickedDomain ?? 'All Websites';

return (
<div>
<TimeUsagePanel
title="Average Daily Activity"
time={averageMonthlyActivity}
/>
<div ref={scrollToRef}>
<MonthlyWebsiteActivityChart
store={filteredWebsiteMonthActivity}
sundayDate={sundayDate}
presentChartTitle={() =>
`Activity on ${presentedPickedDomain} per day`
}
/>
</div>
<WebsiteActivityTable
websiteTimeMap={totalWebsiteMonthlyActivity}
title={'Websites This Month'}
onDomainRowClicked={handleDomainRowClick}
/>
</div>
);
};
6 changes: 6 additions & 0 deletions src/popup/components/ActivityPageMonthlyActivityTab/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { TimeStore } from '../../hooks/useTimeStore';

export interface ActivityPageMonthlyActivityTabProps {
store: TimeStore;
sundayDate: Date;
}
2 changes: 2 additions & 0 deletions src/popup/components/GeneralTimeline/GeneralTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ const GeneralTimelineFC: React.FC<GeneralTimelineProps> = ({
activityTimeline,
title,
emptyHoursMarginCount = 2,
description,
}) => {
return (
<Panel>
<PanelHeader>
<Icon type={IconType.TimePast} />
{title}
{filteredHostname ? ` On ${filteredHostname}` : ''}
<span className='text-xs'> ({description})</span>
</PanelHeader>
<PanelBody
className={twMerge(
Expand Down
1 change: 1 addition & 0 deletions src/popup/components/GeneralTimeline/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export interface GeneralTimelineProps {
emptyHoursMarginCount?: number;
filteredHostname?: string | null;
activityTimeline: TimelineRecord[];
description: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { debounce } from 'throttle-debounce';
import { getIsoDate } from '../../../shared/utils/dates-helper';

import { GithubCalendarProps } from './types';
import { getAppTheme } from '../../hooks/useTheme';

const INACTIVE_DAY_COLOR = '#cccccc';
const LOW_ACTIVITY_DAY_COLOR = '#839dde';
const MEDIUM_ACTIVITY_DAY_COLOR = '#4b76e3';
const HIGH_ACTIVITY_DAY_COLOR = '#103ba6';
const COLORS = [
let INACTIVE_DAY_COLOR = '#444444';
let LOW_ACTIVITY_DAY_COLOR = '#114A74';
let MEDIUM_ACTIVITY_DAY_COLOR = '#0073C1';
let HIGH_ACTIVITY_DAY_COLOR = '#5BC0FB';
let COLORS = [
INACTIVE_DAY_COLOR,
LOW_ACTIVITY_DAY_COLOR,
MEDIUM_ACTIVITY_DAY_COLOR,
Expand Down Expand Up @@ -56,6 +57,36 @@ export const GithubCalendarWrapper: React.FC<GithubCalendarProps> = ({
}) => {
const calendarRef = React.useRef<HTMLDivElement>(null);

const theme = getAppTheme();

if (theme === 'light') {
INACTIVE_DAY_COLOR = '#F0F0F0';
LOW_ACTIVITY_DAY_COLOR = '#FFE0B2';
MEDIUM_ACTIVITY_DAY_COLOR = '#FFB74D';
HIGH_ACTIVITY_DAY_COLOR = '#FFA744';

COLORS = [
INACTIVE_DAY_COLOR,
LOW_ACTIVITY_DAY_COLOR,
MEDIUM_ACTIVITY_DAY_COLOR,
HIGH_ACTIVITY_DAY_COLOR,
]
}

if (theme === 'dark') {
INACTIVE_DAY_COLOR = '#2C2C2C';
LOW_ACTIVITY_DAY_COLOR = '#5C3B1A';
MEDIUM_ACTIVITY_DAY_COLOR = '#AB6C33';
HIGH_ACTIVITY_DAY_COLOR = '#FFA744';

COLORS = [
INACTIVE_DAY_COLOR,
LOW_ACTIVITY_DAY_COLOR,
MEDIUM_ACTIVITY_DAY_COLOR,
HIGH_ACTIVITY_DAY_COLOR,
]
}

React.useEffect(() => {
if (!calendarRef.current) {
return;
Expand All @@ -82,6 +113,14 @@ export const GithubCalendarWrapper: React.FC<GithubCalendarProps> = ({
<div className="calendar" ref={calendarRef} onClick={handleDateClick}>
{/* @ts-expect-error -- expected, this element does have props */}
<Calendar values={activity} panelColors={COLORS} />
<div className="flex items-center justify-end gap-1">
<span className="text-gray-400">Less</span>
<div className="w-2.5 h-2.5 rounded-sm" style={{ backgroundColor: INACTIVE_DAY_COLOR }} />
<div className="w-2.5 h-2.5 rounded-sm" style={{ backgroundColor: LOW_ACTIVITY_DAY_COLOR }} />
<div className="w-2.5 h-2.5 rounded-sm" style={{ backgroundColor: MEDIUM_ACTIVITY_DAY_COLOR }} />
<div className="w-2.5 h-2.5 rounded-sm" style={{ backgroundColor: HIGH_ACTIVITY_DAY_COLOR }} />
<span className="text-gray-400">More</span>
</div>
<ReactTooltip
id={REACT_TOOLTIP_ID}
delayShow={REACT_TOOLTIP_SHOW_DELAY_MS}
Expand Down
85 changes: 53 additions & 32 deletions src/popup/components/IgnoredDomainsSetting/IgnoredDomainSetting.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as React from 'react';
import { twMerge } from 'tailwind-merge';

import { Button, ButtonType } from '../../../blocks/Button';
import { Icon, IconType } from '../../../blocks/Icon';
import { Input } from '../../../blocks/Input';
import { TextArea } from '../../../blocks/Input';
import { PanelBody } from '../../../blocks/Panel';
import { assertDomainIsValid } from '../../../shared/utils/domains';
import { usePopupContext } from '../../hooks/PopupContext';
Expand All @@ -14,27 +13,46 @@ export const IgnoredDomainSetting: React.FC = () => {
settings.ignoredHosts
);
const [domainToIgnore, setDomainToIgnore] = React.useState<string>('');
const [isDomainsListExpanded, setDomainsListExpanded] =
React.useState<boolean>(false);
const [state, setState] = React.useState<{
status: boolean,
statusText: string,
}>({
status: false,
statusText: '',
});

const handleAddIgnoredDomain = React.useCallback(() => {
try {
assertDomainIsValid(domainToIgnore);
setIgnoredDomains((prev) => {
const newIgnoredHostList = Array.from(
new Set([...prev, domainToIgnore])
);

updateSettings({
ignoredHosts: newIgnoredHostList,
const ignoredHostsList = domainToIgnore.split(',')
for (const host of ignoredHostsList) {
assertDomainIsValid(host.trim());
setIgnoredDomains((prev) => {
const newIgnoredHostList = Array.from(
new Set([...prev, host.trim()])
);

updateSettings({
ignoredHosts: newIgnoredHostList,
});

return newIgnoredHostList;
});
}

return newIgnoredHostList;
});

setState((prev) => ({
...prev,
status: false,
statusText: ''
}));
setDomainToIgnore('');
} catch (_) {
//
} catch (error) {
const errorMessage = (error as Error)?.message;

setState((prev) => ({
...prev,
status: true,
statusText: errorMessage
}));
}
}, [domainToIgnore, updateSettings]);

Expand All @@ -54,27 +72,32 @@ export const IgnoredDomainSetting: React.FC = () => {
);

const handleDomainToIgnoreChange = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setDomainToIgnore(e.target.value);
},
[]
);

const handleToggleDomainsListExpanded = React.useCallback(() => {
setDomainsListExpanded((prev) => !prev);
}, [setDomainsListExpanded]);
const handleKeyDown = ((event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if(event.key === 'Enter') {
event.preventDefault();
handleAddIgnoredDomain()
}
})

const { status, statusText } = state;
return (
<div className="p-2">
<PanelBody className="flex flex-col gap-2">
<p>You can hide unwanted websites to keep dashboards clean.</p>
<div className="flex justify-between items-end gap-2">
<label className="flex flex-col gap-1 w-full">
Domain
<Input
placeholder="e.g. google.com"
<TextArea
placeholder="e.g. google.com, bing.com (comma separated)"
value={domainToIgnore}
onChange={handleDomainToIgnoreChange}
onKeyDown={handleKeyDown}
/>
</label>
<Button
Expand All @@ -85,23 +108,21 @@ export const IgnoredDomainSetting: React.FC = () => {
Add
</Button>
</div>
<div className="flex justify-between items-end gap-2">
{status
&& (<p className="text-red-600">{statusText}</p>)
}
</div>
<div className="flex flex-col gap-2">
<a
href="#"
className="text-blue-500"
onClick={handleToggleDomainsListExpanded}
>
View all blacklisted domains
</a>
<div className={twMerge('hidden', isDomainsListExpanded && 'block')}>
<div className="block max-h-[125px] overflow-auto scroll-auto">
{!ignoredDomains.length && (
<p className="text-gray-500">No blacklisted domains</p>
)}
{ignoredDomains.map((domain) => (
<div key={domain} className="flex items-center gap-2">
<Icon
type={IconType.Close}
className="hover:text-neutral-400 cursor-pointer"
className="hover:text-[#ff1a1a] cursor-pointer text-red-600"
onClick={() => handleRemoveIgnoredDomain(domain)}
/>
<span>{domain}</span>
Expand Down
2 changes: 1 addition & 1 deletion src/popup/components/LimitsSetting/LimitsSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export const LimitsSetting: React.FC = () => {
>
<Icon
type={IconType.Close}
className="hover:text-neutral-400 cursor-pointer"
className="hover:text-[#ff1a1a] cursor-pointer text-red-600"
onClick={() => handleLimitRemove(domain)}
/>
<span className="flex-1">{domain}</span>
Expand Down
Loading