Moved code into src folder. Added build for component library.

This commit is contained in:
Mike Cao 2023-08-21 02:06:09 -07:00
parent 7a7233ead4
commit ede658771e
490 changed files with 749 additions and 442 deletions

View file

@ -0,0 +1,145 @@
import { useState } from 'react';
import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics';
import { endOfYear, isSameDay } from 'date-fns';
import DatePickerForm from 'components/metrics/DatePickerForm';
import useLocale from 'components/hooks/useLocale';
import { formatDate } from 'lib/date';
import Icons from 'components/icons';
import useMessages from 'components/hooks/useMessages';
export function DateFilter({
value,
startDate,
endDate,
className,
onChange,
showAllTime = false,
alignment = 'end',
}) {
const { formatMessage, labels } = useMessages();
const [showPicker, setShowPicker] = useState(false);
const options = [
{ label: formatMessage(labels.today), value: '1day' },
{
label: formatMessage(labels.lastHours, { x: 24 }),
value: '24hour',
},
{
label: formatMessage(labels.yesterday),
value: '-1day',
},
{
label: formatMessage(labels.thisWeek),
value: '1week',
divider: true,
},
{
label: formatMessage(labels.lastDays, { x: 7 }),
value: '7day',
},
{
label: formatMessage(labels.thisMonth),
value: '1month',
divider: true,
},
{
label: formatMessage(labels.lastDays, { x: 30 }),
value: '30day',
},
{
label: formatMessage(labels.lastDays, { x: 90 }),
value: '90day',
},
{ label: formatMessage(labels.thisYear), value: '1year' },
showAllTime && {
label: formatMessage(labels.allTime),
value: 'all',
divider: true,
},
{
label: formatMessage(labels.customRange),
value: 'custom',
divider: true,
},
].filter(n => n);
const renderValue = value => {
return value.startsWith('range') ? (
<CustomRange startDate={startDate} endDate={endDate} onClick={() => handleChange('custom')} />
) : (
options.find(e => e.value === value).label
);
};
const handleChange = value => {
if (value === 'custom') {
setShowPicker(true);
return;
}
onChange(value);
};
const handlePickerChange = value => {
setShowPicker(false);
onChange(value);
};
const handleClose = () => setShowPicker(false);
return (
<>
<Dropdown
className={className}
items={options}
renderValue={renderValue}
value={value}
alignment={alignment}
placeholder={formatMessage(labels.selectDate)}
onChange={handleChange}
>
{({ label, value, divider }) => (
<Item key={value} divider={divider}>
{label}
</Item>
)}
</Dropdown>
{showPicker && (
<Modal onClose={handleClose}>
<DatePickerForm
startDate={startDate}
endDate={endDate}
minDate={new Date(2000, 0, 1)}
maxDate={endOfYear(new Date())}
onChange={handlePickerChange}
onClose={() => setShowPicker(false)}
/>
</Modal>
)}
</>
);
}
const CustomRange = ({ startDate, endDate, onClick }) => {
const { locale } = useLocale();
function handleClick(e) {
e.stopPropagation();
onClick();
}
return (
<Flexbox gap={10} alignItems="center" wrap="nowrap">
<Icon className="mr-2" onClick={handleClick}>
<Icons.Calendar />
</Icon>
<Text>
{formatDate(startDate, 'd LLL y', locale)}
{!isSameDay(startDate, endDate) && `${formatDate(endDate, 'd LLL y', locale)}`}
</Text>
</Flexbox>
);
};
export default DateFilter;

View file

@ -0,0 +1,53 @@
import { Icon, Button, PopupTrigger, Popup, Text } from 'react-basics';
import classNames from 'classnames';
import { languages } from 'lib/lang';
import useLocale from 'components/hooks/useLocale';
import Icons from 'components/icons';
import styles from './LanguageButton.module.css';
export function LanguageButton() {
const { locale, saveLocale, dir } = useLocale();
const items = Object.keys(languages).map(key => ({ ...languages[key], value: key }));
function handleSelect(value, close, e) {
e.stopPropagation();
saveLocale(value);
close();
}
return (
<PopupTrigger>
<Button variant="quiet">
<Icon>
<Icons.Globe />
</Icon>
</Button>
<Popup position="bottom" alignment={dir === 'rtl' ? 'start' : 'end'}>
{close => {
return (
<div className={styles.menu}>
{items.map(({ value, label }) => {
return (
<div
key={value}
className={classNames(styles.item, { [styles.selected]: value === locale })}
onClick={handleSelect.bind(null, value, close)}
>
<Text>{label}</Text>
{value === locale && (
<Icon className={styles.icon}>
<Icons.Check />
</Icon>
)}
</div>
);
})}
</div>
);
}}
</Popup>
</PopupTrigger>
);
}
export default LanguageButton;

View file

@ -0,0 +1,34 @@
.menu {
display: flex;
flex-flow: row wrap;
min-width: 640px;
padding: 10px;
background: var(--base50);
z-index: var(--z-index-popup);
border-radius: 5px;
border: 1px solid var(--border-color);
margin-left: 10px;
}
.item {
display: flex;
align-items: center;
justify-content: space-between;
min-width: calc(100% / 3);
border-radius: 5px;
padding: 5px 10px;
}
.item:hover {
background: var(--base75);
cursor: pointer;
}
.selected {
font-weight: 700;
background: var(--blue100);
}
.icon {
color: var(--primary400);
}

View file

@ -0,0 +1,20 @@
import { Button, Icon, Icons, TooltipPopup } from 'react-basics';
import Link from 'next/link';
import useMessages from 'components/hooks/useMessages';
export function LogoutButton({ tooltipPosition = 'top' }) {
const { formatMessage, labels } = useMessages();
return (
<Link href="/src/pages/logout">
<TooltipPopup label={formatMessage(labels.logout)} position={tooltipPosition}>
<Button variant="quiet">
<Icon>
<Icons.Logout />
</Icon>
</Button>
</TooltipPopup>
</Link>
);
}
export default LogoutButton;

View file

@ -0,0 +1,71 @@
import { useRef } from 'react';
import {
Text,
Icon,
CalendarMonthSelect,
CalendarYearSelect,
Button,
PopupTrigger,
Popup,
} from 'react-basics';
import { startOfMonth, endOfMonth } from 'date-fns';
import Icons from 'components/icons';
import { useLocale } from 'components/hooks';
import { formatDate } from 'lib/date';
import { getDateLocale } from 'lib/lang';
import styles from './MonthSelect.module.css';
export function MonthSelect({ date = new Date(), onChange }) {
const { locale } = useLocale();
const month = formatDate(date, 'MMMM', locale);
const year = date.getFullYear();
const ref = useRef();
const handleChange = (close, date) => {
onChange(`range:${startOfMonth(date).getTime()}:${endOfMonth(date).getTime()}`);
close();
};
return (
<>
<div ref={ref} className={styles.container}>
<PopupTrigger>
<Button className={styles.input} variant="quiet">
<Text>{month}</Text>
<Icon size="sm">
<Icons.ChevronDown />
</Icon>
</Button>
<Popup className={styles.popup} alignment="start">
{close => (
<CalendarMonthSelect
date={date}
locale={getDateLocale(locale)}
onSelect={handleChange.bind(null, close)}
/>
)}
</Popup>
</PopupTrigger>
<PopupTrigger>
<Button className={styles.input} variant="quiet">
<Text>{year}</Text>
<Icon size="sm">
<Icons.ChevronDown />
</Icon>
</Button>
<Popup className={styles.popup} alignment="start">
{close => (
<CalendarYearSelect
date={date}
locale={getDateLocale(locale)}
onSelect={handleChange.bind(null, close)}
/>
)}
</Popup>
</PopupTrigger>
</div>
</>
);
}
export default MonthSelect;

View file

@ -0,0 +1,22 @@
.container {
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--base400);
border-radius: var(--border-radius);
}
.input {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
}
.popup {
border: 1px solid var(--base400);
background: var(--base50);
border-radius: var(--border-radius);
padding: 20px;
margin-top: 5px;
}

View file

@ -0,0 +1,61 @@
import { Icon, Button, PopupTrigger, Popup, Menu, Item, Text } from 'react-basics';
import { useRouter } from 'next/router';
import Icons from 'components/icons';
import useMessages from 'components/hooks/useMessages';
import useUser from 'components/hooks/useUser';
import useConfig from 'components/hooks/useConfig';
import styles from './ProfileButton.module.css';
import useLocale from 'components/hooks/useLocale';
export function ProfileButton() {
const { formatMessage, labels } = useMessages();
const { user } = useUser();
const { cloudMode } = useConfig();
const router = useRouter();
const { dir } = useLocale();
const handleSelect = key => {
if (key === 'profile') {
router.push('/settings/profile');
}
if (key === 'logout') {
router.push('/logout');
}
};
return (
<PopupTrigger>
<Button variant="quiet">
<Icon>
<Icons.Profile />
</Icon>
<Icon size="sm">
<Icons.ChevronDown />
</Icon>
</Button>
<Popup position="bottom" alignment={dir === 'rtl' ? 'start' : 'end'}>
<Menu variant="popup" onSelect={handleSelect} className={styles.menu}>
<Item key="user" className={styles.item}>
<Text>{user.username}</Text>
</Item>
<Item key="profile" className={styles.item} divider={true}>
<Icon>
<Icons.User />
</Icon>
<Text>{formatMessage(labels.profile)}</Text>
</Item>
{!cloudMode && (
<Item key="logout" className={styles.item}>
<Icon>
<Icons.Logout />
</Icon>
<Text>{formatMessage(labels.logout)}</Text>
</Item>
)}
</Menu>
</Popup>
</PopupTrigger>
);
}
export default ProfileButton;

View file

@ -0,0 +1,10 @@
.menu {
width: 200px;
z-index: var(--z-index-popup);
}
.item {
display: flex;
gap: 12px;
background: var(--base50);
}

View file

@ -0,0 +1,28 @@
import { LoadingButton, Icon, TooltipPopup } from 'react-basics';
import { setWebsiteDateRange } from 'store/websites';
import useDateRange from 'components/hooks/useDateRange';
import Icons from 'components/icons';
import useMessages from 'components/hooks/useMessages';
export function RefreshButton({ websiteId, isLoading }) {
const { formatMessage, labels } = useMessages();
const [dateRange] = useDateRange(websiteId);
function handleClick() {
if (!isLoading && dateRange) {
setWebsiteDateRange(websiteId, dateRange);
}
}
return (
<TooltipPopup label={formatMessage(labels.refresh)}>
<LoadingButton loading={isLoading} onClick={handleClick}>
<Icon>
<Icons.Refresh />
</Icon>
</LoadingButton>
</TooltipPopup>
);
}
export default RefreshButton;

View file

@ -0,0 +1,37 @@
import { Button, Icon, PopupTrigger, Popup, Form, FormRow } from 'react-basics';
import TimezoneSetting from 'components/pages/settings/profile/TimezoneSetting';
import DateRangeSetting from 'components/pages/settings/profile/DateRangeSetting';
import Icons from 'components/icons';
import useMessages from 'components/hooks/useMessages';
import styles from './SettingsButton.module.css';
export function SettingsButton() {
const { formatMessage, labels } = useMessages();
return (
<PopupTrigger>
<Button variant="quiet">
<Icon>
<Icons.Gear />
</Icon>
</Button>
<Popup
className={styles.popup}
position="bottom"
alignment="end"
onClick={e => e.stopPropagation()}
>
<Form>
<FormRow label={formatMessage(labels.timezone)}>
<TimezoneSetting />
</FormRow>
<FormRow label={formatMessage(labels.defaultDateRange)}>
<DateRangeSetting />
</FormRow>
</Form>
</Popup>
</PopupTrigger>
);
}
export default SettingsButton;

View file

@ -0,0 +1,11 @@
.popup {
background: var(--base50);
border: 1px solid var(--base500);
border-radius: 4px;
display: flex;
flex-direction: column;
position: absolute;
top: 100%;
right: 0;
padding: 20px;
}

View file

@ -0,0 +1,38 @@
import { useTransition, animated } from 'react-spring';
import { Button, Icon } from 'react-basics';
import useTheme from 'components/hooks/useTheme';
import Icons from 'components/icons';
import styles from './ThemeButton.module.css';
export function ThemeButton() {
const { theme, saveTheme } = useTheme();
const transitions = useTransition(theme, {
initial: { opacity: 1 },
from: {
opacity: 0,
transform: `translateY(${theme === 'light' ? '20px' : '-20px'}) scale(0.5)`,
},
enter: { opacity: 1, transform: 'translateY(0px) scale(1.0)' },
leave: {
opacity: 0,
transform: `translateY(${theme === 'light' ? '-20px' : '20px'}) scale(0.5)`,
},
});
function handleClick() {
saveTheme(theme === 'light' ? 'dark' : 'light');
}
return (
<Button variant="quiet" className={styles.button} onClick={handleClick}>
{transitions((style, item) => (
<animated.div key={item} style={style}>
<Icon className={styles.icon}>{item === 'light' ? <Icons.Sun /> : <Icons.Moon />}</Icon>
</animated.div>
))}
</Button>
);
}
export default ThemeButton;

View file

@ -0,0 +1,14 @@
.button {
width: 50px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.button > div {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
}

View file

@ -0,0 +1,25 @@
import useDateRange from 'components/hooks/useDateRange';
import DateFilter from './DateFilter';
import styles from './WebsiteDateFilter.module.css';
export function WebsiteDateFilter({ websiteId }) {
const [dateRange, setDateRange] = useDateRange(websiteId);
const { value, startDate, endDate } = dateRange;
const handleChange = async value => {
setDateRange(value);
};
return (
<DateFilter
className={styles.dropdown}
value={value}
startDate={startDate}
endDate={endDate}
onChange={handleChange}
showAllTime={true}
/>
);
}
export default WebsiteDateFilter;

View file

@ -0,0 +1,3 @@
.dropdown {
min-width: 200px;
}

View file

@ -0,0 +1,28 @@
import { Dropdown, Item } from 'react-basics';
import useApi from 'components/hooks/useApi';
import useMessages from 'components/hooks/useMessages';
export function WebsiteSelect({ websiteId, onSelect }) {
const { formatMessage, labels } = useMessages();
const { get, useQuery } = useApi();
const { data } = useQuery(['websites:me'], () => get('/me/websites'));
const renderValue = value => {
return data?.data?.find(({ id }) => id === value)?.name;
};
return (
<Dropdown
items={data?.data}
value={websiteId}
renderValue={renderValue}
onChange={onSelect}
alignment="end"
placeholder={formatMessage(labels.selectWebsite)}
>
{({ id, name }) => <Item key={id}>{name}</Item>}
</Dropdown>
);
}
export default WebsiteSelect;