mirror of
https://github.com/kremalicious/umami.git
synced 2024-12-19 15:53:39 +01:00
commit
8df3c21ada
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,6 +16,7 @@
|
||||
# production
|
||||
/build
|
||||
/public/umami.js
|
||||
/lang-compiled
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
1
assets/globe.svg
Normal file
1
assets/globe.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm179.3 160h-67.2c-6.7-36.5-17.5-68.8-31.2-94.7 42.9 19 77.7 52.7 98.4 94.7zM248 56c18.6 0 48.6 41.2 63.2 112H184.8C199.4 97.2 229.4 56 248 56zM48 256c0-13.7 1.4-27.1 4-40h77.7c-1 13.1-1.7 26.3-1.7 40s.7 26.9 1.7 40H52c-2.6-12.9-4-26.3-4-40zm20.7 88h67.2c6.7 36.5 17.5 68.8 31.2 94.7-42.9-19-77.7-52.7-98.4-94.7zm67.2-176H68.7c20.7-42 55.5-75.7 98.4-94.7-13.7 25.9-24.5 58.2-31.2 94.7zM248 456c-18.6 0-48.6-41.2-63.2-112h126.5c-14.7 70.8-44.7 112-63.3 112zm70.1-160H177.9c-1.1-12.8-1.9-26-1.9-40s.8-27.2 1.9-40h140.3c1.1 12.8 1.9 26 1.9 40s-.9 27.2-2 40zm10.8 142.7c13.7-25.9 24.4-58.2 31.2-94.7h67.2c-20.7 42-55.5 75.7-98.4 94.7zM366.3 296c1-13.1 1.7-26.3 1.7-40s-.7-26.9-1.7-40H444c2.6 12.9 4 26.3 4 40s-1.4 27.1-4 40h-77.7z"/></svg>
|
After Width: | Height: | Size: 874 B |
@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import WebsiteChart from 'components/metrics/WebsiteChart';
|
||||
import WorldMap from 'components/common/WorldMap';
|
||||
@ -32,7 +33,9 @@ export default function WebsiteDetails({ websiteId }) {
|
||||
size="xsmall"
|
||||
onClick={() => setExpand(null)}
|
||||
>
|
||||
<div>Back</div>
|
||||
<div>
|
||||
<FormattedMessage id="button.back" defaultMessage="Back" />
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
|
||||
@ -40,13 +43,41 @@ export default function WebsiteDetails({ websiteId }) {
|
||||
{
|
||||
render: BackButton,
|
||||
},
|
||||
{ label: 'Pages', value: 'url', component: PagesTable },
|
||||
{ label: 'Referrers', value: 'referrer', component: ReferrersTable },
|
||||
{ label: 'Browsers', value: 'browser', component: BrowsersTable },
|
||||
{ label: 'Operating system', value: 'os', component: OSTable },
|
||||
{ label: 'Devices', value: 'device', component: DevicesTable },
|
||||
{ label: 'Countries', value: 'country', component: CountriesTable },
|
||||
{ label: 'Events', value: 'event', component: EventsTable },
|
||||
{
|
||||
label: <FormattedMessage id="metrics.pages" defaultMessage="Pages" />,
|
||||
value: 'url',
|
||||
component: PagesTable,
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="metrics.browsers" defaultMessage="Browsers" />,
|
||||
value: 'referrer',
|
||||
component: ReferrersTable,
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="metrics.referrers" defaultMessage="Referrers" />,
|
||||
value: 'browser',
|
||||
component: BrowsersTable,
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="metrics.operating-system" defaultMessage="Operating system" />,
|
||||
value: 'os',
|
||||
component: OSTable,
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="metrics.devices" defaultMessage="Devices" />,
|
||||
value: 'device',
|
||||
component: DevicesTable,
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="metrics.countries" defaultMessage="Countries" />,
|
||||
value: 'country',
|
||||
component: CountriesTable,
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="metrics.events" defaultMessage="Events" />,
|
||||
value: 'event',
|
||||
component: EventsTable,
|
||||
},
|
||||
];
|
||||
|
||||
const tableProps = {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useRouter } from 'next/router';
|
||||
import WebsiteChart from 'components/metrics/WebsiteChart';
|
||||
import Page from 'components/layout/Page';
|
||||
@ -24,9 +25,21 @@ export default function WebsiteList() {
|
||||
</div>
|
||||
))}
|
||||
{data.length === 0 && (
|
||||
<EmptyPlaceholder msg={"You don't have any websites configured."}>
|
||||
<EmptyPlaceholder
|
||||
msg={
|
||||
<FormattedMessage
|
||||
id="placeholder.message.no-websites-configured"
|
||||
defaultMessage="You don't have any websites configured."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button icon={<Arrow />} size="medium" onClick={() => router.push('/settings')}>
|
||||
<div>Go to settings</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="placeholder.message.go-to-settings"
|
||||
defaultMessage="Go to settings"
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
</EmptyPlaceholder>
|
||||
)}
|
||||
|
@ -13,17 +13,20 @@ export default function ButtonGroup({
|
||||
}) {
|
||||
return (
|
||||
<div className={classNames(styles.group, className)}>
|
||||
{items.map(item => (
|
||||
<Button
|
||||
key={item}
|
||||
className={classNames(styles.button, { [styles.selected]: selectedItem === item })}
|
||||
size={size}
|
||||
icon={icon}
|
||||
onClick={() => onClick(item)}
|
||||
>
|
||||
{item}
|
||||
</Button>
|
||||
))}
|
||||
{items.map(item => {
|
||||
const { label, value } = item;
|
||||
return (
|
||||
<Button
|
||||
key={value}
|
||||
className={classNames(styles.button, { [styles.selected]: selectedItem === value })}
|
||||
size={size}
|
||||
icon={icon}
|
||||
onClick={() => onClick(value)}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import Button from './Button';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const defaultText = 'Copy to clipboard';
|
||||
const defaultText = (
|
||||
<FormattedMessage id="button.copy-to-clipboard" defaultMessage="Copy to clipboard" />
|
||||
);
|
||||
|
||||
export default function CopyButton({ element, ...props }) {
|
||||
const [text, setText] = useState(defaultText);
|
||||
@ -10,7 +13,7 @@ export default function CopyButton({ element, ...props }) {
|
||||
if (element?.current) {
|
||||
element.current.select();
|
||||
document.execCommand('copy');
|
||||
setText('Copied!');
|
||||
setText(<FormattedMessage id="message.copied" defaultMessage="Copied!" />);
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,40 @@
|
||||
import React from 'react';
|
||||
import { getDateRange } from 'lib/date';
|
||||
import DropDown from './DropDown';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const filterOptions = [
|
||||
{ label: 'Last 24 hours', value: '24hour' },
|
||||
{ label: 'Last 7 days', value: '7day' },
|
||||
{ label: 'Last 30 days', value: '30day' },
|
||||
{ label: 'Last 90 days', value: '90day' },
|
||||
{ label: 'Today', value: '1day' },
|
||||
{ label: 'This week', value: '1week' },
|
||||
{ label: 'This month', value: '1month' },
|
||||
{ label: 'This year', value: '1year' },
|
||||
{
|
||||
label: (
|
||||
<FormattedMessage id="label.last-hours" defaultMessage="Last {x} hours" values={{ x: 24 }} />
|
||||
),
|
||||
value: '24hour',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 7 }} />
|
||||
),
|
||||
value: '7day',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 30 }} />
|
||||
),
|
||||
value: '30day',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 90 }} />
|
||||
),
|
||||
value: '90day',
|
||||
},
|
||||
{ label: <FormattedMessage id="label.today" defaultMessage="Today" />, value: '1day' },
|
||||
{ label: <FormattedMessage id="label.this-week" defaultMessage="This week" />, value: '1week' },
|
||||
{
|
||||
label: <FormattedMessage id="label.this-month" defaultMessage="This month" />,
|
||||
value: '1month',
|
||||
},
|
||||
{ label: <FormattedMessage id="label.this-year" defaultMessage="This year" />, value: '1year' },
|
||||
];
|
||||
|
||||
export default function DateFilter({ value, onChange, className }) {
|
||||
|
45
components/common/LanguageButton.js
Normal file
45
components/common/LanguageButton.js
Normal file
@ -0,0 +1,45 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Globe from 'assets/globe.svg';
|
||||
import useDocumentClick from 'hooks/useDocumentClick';
|
||||
import { updateApp } from 'redux/actions/app';
|
||||
import Menu from './Menu';
|
||||
import Button from './Button';
|
||||
import { menuOptions } from 'lib/lang';
|
||||
import styles from './LanguageButton.module.css';
|
||||
|
||||
export default function LanguageButton({ menuPosition = 'bottom', menuAlign = 'left' }) {
|
||||
const dispatch = useDispatch();
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
const locale = useSelector(state => state.app.locale);
|
||||
const ref = useRef();
|
||||
const selectedLocale = menuOptions.find(e => e.value === locale)?.display;
|
||||
|
||||
function handleSelect(value) {
|
||||
dispatch(updateApp({ locale: value }));
|
||||
window.localStorage.setItem('locale', value);
|
||||
setShowMenu(false);
|
||||
}
|
||||
|
||||
useDocumentClick(e => {
|
||||
if (!ref.current.contains(e.target)) {
|
||||
setShowMenu(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={ref} className={styles.container}>
|
||||
<Button icon={<Globe />} onClick={() => setShowMenu(true)} size="small">
|
||||
<div className={locale}>{selectedLocale}</div>
|
||||
</Button>
|
||||
{showMenu && (
|
||||
<Menu
|
||||
options={menuOptions}
|
||||
onSelect={handleSelect}
|
||||
float={menuPosition}
|
||||
align={menuAlign}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
5
components/common/LanguageButton.module.css
Normal file
5
components/common/LanguageButton.module.css
Normal file
@ -0,0 +1,5 @@
|
||||
.container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
@ -26,7 +26,7 @@
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: var(--gray50);
|
||||
min-width: 200px;
|
||||
min-width: 400px;
|
||||
min-height: 100px;
|
||||
z-index: 1;
|
||||
border: 1px solid var(--gray300);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouter } from 'next/router';
|
||||
import Menu from './Menu';
|
||||
@ -17,14 +18,16 @@ export default function UserButton() {
|
||||
const menuOptions = [
|
||||
{
|
||||
label: (
|
||||
<>
|
||||
Logged in as <b>{user.username}</b>
|
||||
</>
|
||||
<FormattedMessage
|
||||
id="label.logged-in-as"
|
||||
defaultMessage="Logged in as {username}"
|
||||
values={{ username: <b>{user.username}</b> }}
|
||||
/>
|
||||
),
|
||||
value: 'username',
|
||||
className: styles.username,
|
||||
},
|
||||
{ label: 'Logout', value: 'logout' },
|
||||
{ label: <FormattedMessage id="label.logout" defaultMessage="Logout" />, value: 'logout' },
|
||||
];
|
||||
|
||||
function handleSelect(value) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Formik, Form, Field } from 'formik';
|
||||
import { post } from 'lib/web';
|
||||
import Button from 'components/common/Button';
|
||||
@ -18,10 +19,10 @@ const validate = ({ user_id, username, password }) => {
|
||||
const errors = {};
|
||||
|
||||
if (!username) {
|
||||
errors.username = 'Required';
|
||||
errors.username = <FormattedMessage id="label.required" defaultMessage="Required" />;
|
||||
}
|
||||
if (!user_id && !password) {
|
||||
errors.password = 'Required';
|
||||
errors.password = <FormattedMessage id="label.required" defaultMessage="Required" />;
|
||||
}
|
||||
|
||||
return errors;
|
||||
@ -36,7 +37,11 @@ export default function AccountEditForm({ values, onSave, onClose }) {
|
||||
if (typeof response !== 'string') {
|
||||
onSave();
|
||||
} else {
|
||||
setMessage(response || 'Something went wrong');
|
||||
setMessage(
|
||||
response || (
|
||||
<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -50,20 +55,26 @@ export default function AccountEditForm({ values, onSave, onClose }) {
|
||||
{() => (
|
||||
<Form>
|
||||
<FormRow>
|
||||
<label htmlFor="username">Username</label>
|
||||
<label htmlFor="username">
|
||||
<FormattedMessage id="label.username" defaultMessage="Username" />
|
||||
</label>
|
||||
<Field name="username" type="text" />
|
||||
<FormError name="username" />
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<label htmlFor="password">Password</label>
|
||||
<label htmlFor="password">
|
||||
<FormattedMessage id="label.password" defaultMessage="Password" />
|
||||
</label>
|
||||
<Field name="password" type="password" />
|
||||
<FormError name="password" />
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<Button type="submit" variant="action">
|
||||
Save
|
||||
<FormattedMessage id="button.save" defaultMessage="Save" />
|
||||
</Button>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage id="button.cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
</FormButtons>
|
||||
<FormMessage>{message}</FormMessage>
|
||||
</Form>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Formik, Form, Field } from 'formik';
|
||||
import { post } from 'lib/web';
|
||||
import Button from 'components/common/Button';
|
||||
@ -19,15 +20,17 @@ const validate = ({ current_password, new_password, confirm_password }) => {
|
||||
const errors = {};
|
||||
|
||||
if (!current_password) {
|
||||
errors.current_password = 'Required';
|
||||
errors.current_password = <FormattedMessage id="label.required" defaultMessage="Required" />;
|
||||
}
|
||||
if (!new_password) {
|
||||
errors.new_password = 'Required';
|
||||
errors.new_password = <FormattedMessage id="label.required" defaultMessage="Required" />;
|
||||
}
|
||||
if (!confirm_password) {
|
||||
errors.confirm_password = 'Required';
|
||||
errors.confirm_password = <FormattedMessage id="label.required" defaultMessage="Required" />;
|
||||
} else if (new_password !== confirm_password) {
|
||||
errors.confirm_password = `Passwords don't match`;
|
||||
errors.confirm_password = (
|
||||
<FormattedMessage id="label.passwords-dont-match" defaultMessage="Passwords don't match" />
|
||||
);
|
||||
}
|
||||
|
||||
return errors;
|
||||
@ -42,7 +45,11 @@ export default function ChangePasswordForm({ values, onSave, onClose }) {
|
||||
if (typeof response !== 'string') {
|
||||
onSave();
|
||||
} else {
|
||||
setMessage(response || 'Something went wrong');
|
||||
setMessage(
|
||||
response || (
|
||||
<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -56,25 +63,33 @@ export default function ChangePasswordForm({ values, onSave, onClose }) {
|
||||
{() => (
|
||||
<Form>
|
||||
<FormRow>
|
||||
<label htmlFor="current_password">Current password</label>
|
||||
<label htmlFor="current_password">
|
||||
<FormattedMessage id="label.current-password" defaultMessage="Current password" />
|
||||
</label>
|
||||
<Field name="current_password" type="password" />
|
||||
<FormError name="current_password" />
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<label htmlFor="new_password">New password</label>
|
||||
<label htmlFor="new_password">
|
||||
<FormattedMessage id="label.new-password" defaultMessage="New password" />
|
||||
</label>
|
||||
<Field name="new_password" type="password" />
|
||||
<FormError name="new_password" />
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<label htmlFor="confirm_password">Confirm password</label>
|
||||
<label htmlFor="confirm_password">
|
||||
<FormattedMessage id="label.confirm-password" defaultMessage="Confirm password" />
|
||||
</label>
|
||||
<Field name="confirm_password" type="password" />
|
||||
<FormError name="confirm_password" />
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<Button type="submit" variant="action">
|
||||
Save
|
||||
<FormattedMessage id="button.save" defaultMessage="Save" />
|
||||
</Button>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage id="button.cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
</FormButtons>
|
||||
<FormMessage>{message}</FormMessage>
|
||||
</Form>
|
||||
|
@ -8,12 +8,17 @@ import FormLayout, {
|
||||
FormMessage,
|
||||
FormRow,
|
||||
} from 'components/layout/FormLayout';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const validate = ({ confirmation }) => {
|
||||
const errors = {};
|
||||
|
||||
if (confirmation !== 'DELETE') {
|
||||
errors.confirmation = !confirmation ? 'Required' : 'Invalid';
|
||||
errors.confirmation = !confirmation ? (
|
||||
<FormattedMessage id="label.required" defaultMessage="Required" />
|
||||
) : (
|
||||
<FormattedMessage id="label.invalid" defaultMessage="Invalid" />
|
||||
);
|
||||
}
|
||||
|
||||
return errors;
|
||||
@ -28,7 +33,7 @@ export default function DeleteForm({ values, onSave, onClose }) {
|
||||
if (typeof response !== 'string') {
|
||||
onSave();
|
||||
} else {
|
||||
setMessage('Something went wrong');
|
||||
setMessage(<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />);
|
||||
}
|
||||
};
|
||||
|
||||
@ -42,11 +47,24 @@ export default function DeleteForm({ values, onSave, onClose }) {
|
||||
{() => (
|
||||
<Form>
|
||||
<div>
|
||||
Are your sure you want to delete <b>{values.name}</b>?
|
||||
<FormattedMessage
|
||||
id="message.confirm-delete"
|
||||
defaultMessage="Are your sure you want to delete {target}?"
|
||||
values={{ target: <b>{values.name}</b> }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="message.delete-warning"
|
||||
defaultMessage="All associated data will be deleted as well."
|
||||
/>
|
||||
</div>
|
||||
<div>All associated data will be deleted as well.</div>
|
||||
<p>
|
||||
Type <b>DELETE</b> in the box below to confirm.
|
||||
<FormattedMessage
|
||||
id="message.type-delete"
|
||||
defaultMessage="Type {delete} in the box below to confirm."
|
||||
values={{ delete: <b>DELETE</b> }}
|
||||
/>
|
||||
</p>
|
||||
<FormRow>
|
||||
<Field name="confirmation" type="text" />
|
||||
@ -54,9 +72,11 @@ export default function DeleteForm({ values, onSave, onClose }) {
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<Button type="submit" variant="danger">
|
||||
Delete
|
||||
<FormattedMessage id="button.delete" defaultMessage="Delete" />
|
||||
</Button>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage id="button.cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
</FormButtons>
|
||||
<FormMessage>{message}</FormMessage>
|
||||
</Form>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Formik, Form, Field } from 'formik';
|
||||
import Router from 'next/router';
|
||||
import { post } from 'lib/web';
|
||||
@ -17,10 +18,10 @@ const validate = ({ username, password }) => {
|
||||
const errors = {};
|
||||
|
||||
if (!username) {
|
||||
errors.username = 'Required';
|
||||
errors.username = <FormattedMessage id="label.required" defaultMessage="Required" />;
|
||||
}
|
||||
if (!password) {
|
||||
errors.password = 'Required';
|
||||
errors.password = <FormattedMessage id="label.required" defaultMessage="Required" />;
|
||||
}
|
||||
|
||||
return errors;
|
||||
@ -35,7 +36,16 @@ export default function LoginForm() {
|
||||
if (typeof response !== 'string') {
|
||||
await Router.push('/');
|
||||
} else {
|
||||
setMessage(response.startsWith('401') ? 'Incorrect username/password' : response);
|
||||
setMessage(
|
||||
response.startsWith('401') ? (
|
||||
<FormattedMessage
|
||||
id="message.incorrect-username-password"
|
||||
defaultMessage="Incorrect username/password."
|
||||
/>
|
||||
) : (
|
||||
response
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -54,18 +64,22 @@ export default function LoginForm() {
|
||||
<Icon icon={<Logo />} size="xlarge" className={styles.icon} />
|
||||
<h1 className="center">umami</h1>
|
||||
<FormRow>
|
||||
<label htmlFor="username">Username</label>
|
||||
<label htmlFor="username">
|
||||
<FormattedMessage id="label.username" defaultMessage="Username" />
|
||||
</label>
|
||||
<Field name="username" type="text" />
|
||||
<FormError name="username" />
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<label htmlFor="password">Password</label>
|
||||
<label htmlFor="password">
|
||||
<FormattedMessage id="label.password" defaultMessage="Password" />
|
||||
</label>
|
||||
<Field name="password" type="password" />
|
||||
<FormError name="password" />
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<Button type="submit" variant="action">
|
||||
Login
|
||||
<FormattedMessage id="button.login" defaultMessage="Login" />
|
||||
</Button>
|
||||
</FormButtons>
|
||||
<FormMessage>{message}</FormMessage>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Button from 'components/common/Button';
|
||||
import FormLayout, { FormButtons, FormRow } from 'components/layout/FormLayout';
|
||||
import CopyButton from '../common/CopyButton';
|
||||
import CopyButton from 'components/common/CopyButton';
|
||||
|
||||
export default function TrackingCodeForm({ values, onClose }) {
|
||||
const ref = useRef();
|
||||
@ -10,7 +11,11 @@ export default function TrackingCodeForm({ values, onClose }) {
|
||||
return (
|
||||
<FormLayout>
|
||||
<p>
|
||||
This is the publicly shared URL for <b>{values.name}</b>.
|
||||
<FormattedMessage
|
||||
id="message.share-url"
|
||||
defaultMessage="This is the publicly shared URL for {target}."
|
||||
values={{ target: <b>{values.name}</b> }}
|
||||
/>
|
||||
</p>
|
||||
<FormRow>
|
||||
<textarea
|
||||
@ -24,7 +29,9 @@ export default function TrackingCodeForm({ values, onClose }) {
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<CopyButton type="submit" variant="action" element={ref} />
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage id="button.cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
</FormButtons>
|
||||
</FormLayout>
|
||||
);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Button from 'components/common/Button';
|
||||
import FormLayout, { FormButtons, FormRow } from 'components/layout/FormLayout';
|
||||
import CopyButton from '../common/CopyButton';
|
||||
@ -9,8 +10,11 @@ export default function TrackingCodeForm({ values, onClose }) {
|
||||
return (
|
||||
<FormLayout>
|
||||
<p>
|
||||
To track stats for <b>{values.name}</b>, place the following code in the <head>
|
||||
section of your website.
|
||||
<FormattedMessage
|
||||
id="message.track-stats"
|
||||
defaultMessage="To track stats for {target}, place the following code in the {head} section of your website."
|
||||
values={{ head: '<head>', target: <b>{values.name}</b> }}
|
||||
/>
|
||||
</p>
|
||||
<FormRow>
|
||||
<textarea
|
||||
@ -24,7 +28,9 @@ export default function TrackingCodeForm({ values, onClose }) {
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<CopyButton type="submit" variant="action" element={ref} />
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage id="button.cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
</FormButtons>
|
||||
</FormLayout>
|
||||
);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Formik, Form, Field } from 'formik';
|
||||
import { post } from 'lib/web';
|
||||
import Button from 'components/common/Button';
|
||||
@ -21,12 +22,12 @@ const validate = ({ name, domain }) => {
|
||||
const errors = {};
|
||||
|
||||
if (!name) {
|
||||
errors.name = 'Required';
|
||||
errors.name = <FormattedMessage id="label.required" defaultMessage="Required" />;
|
||||
}
|
||||
if (!domain) {
|
||||
errors.domain = 'Required';
|
||||
errors.domain = <FormattedMessage id="label.required" defaultMessage="Required" />;
|
||||
} else if (!DOMAIN_REGEX.test(domain)) {
|
||||
errors.domain = 'Invalid domain';
|
||||
errors.domain = <FormattedMessage id="label.invalid-domain" defaultMessage="Invalid domain" />;
|
||||
}
|
||||
|
||||
return errors;
|
||||
@ -41,7 +42,7 @@ export default function WebsiteEditForm({ values, onSave, onClose }) {
|
||||
if (typeof response !== 'string') {
|
||||
onSave();
|
||||
} else {
|
||||
setMessage('Something went wrong');
|
||||
setMessage(<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />);
|
||||
}
|
||||
};
|
||||
|
||||
@ -55,26 +56,42 @@ export default function WebsiteEditForm({ values, onSave, onClose }) {
|
||||
{() => (
|
||||
<Form>
|
||||
<FormRow>
|
||||
<label htmlFor="name">Name</label>
|
||||
<label htmlFor="name">
|
||||
<FormattedMessage id="label.name" defaultMessage="Name" />
|
||||
</label>
|
||||
<Field name="name" type="text" />
|
||||
<FormError name="name" />
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<label htmlFor="domain">Domain</label>
|
||||
<label htmlFor="domain">
|
||||
<FormattedMessage id="label.domain" defaultMessage="Domain" />
|
||||
</label>
|
||||
<Field name="domain" type="text" />
|
||||
<FormError name="domain" />
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<label></label>
|
||||
<Field name="enable_share_url">
|
||||
{({ field }) => <Checkbox {...field} label="Enable share URL" />}
|
||||
{({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="label.enable-share-url"
|
||||
defaultMessage="Enable share URL"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<Button type="submit" variant="action">
|
||||
Save
|
||||
<FormattedMessage id="button.save" defaultMessage="Save" />
|
||||
</Button>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage id="button.cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
</FormButtons>
|
||||
<FormMessage>{message}</FormMessage>
|
||||
</Form>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import Button from 'components/common/Button';
|
||||
@ -9,10 +10,10 @@ export default function Footer() {
|
||||
return (
|
||||
<footer className="container">
|
||||
<div className={classNames(styles.footer, 'row justify-content-center')}>
|
||||
<div>powered by</div>
|
||||
<FormattedMessage id="footer.powered-by" defaultMessage="powered by" />
|
||||
<Link href="https://umami.is">
|
||||
<a>
|
||||
<Button icon={<Logo />} size="small">
|
||||
<Button className={styles.button} icon={<Logo />} size="small">
|
||||
<b>umami</b>
|
||||
</Button>
|
||||
</a>
|
||||
|
@ -1,14 +1,15 @@
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-small);
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.footer button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useSelector } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'components/common/Link';
|
||||
@ -6,6 +7,7 @@ import UserButton from '../common/UserButton';
|
||||
import Icon from '../common/Icon';
|
||||
import Logo from 'assets/logo.svg';
|
||||
import styles from './Header.module.css';
|
||||
import LanguageButton from '../common/LanguageButton';
|
||||
|
||||
export default function Header() {
|
||||
const user = useSelector(state => state.user);
|
||||
@ -19,15 +21,24 @@ export default function Header() {
|
||||
<Link href={user ? '/' : 'https://umami.is'}>umami</Link>
|
||||
</div>
|
||||
</div>
|
||||
{user && (
|
||||
<div className="col-12 col-md-6">
|
||||
<div className={styles.nav}>
|
||||
<Link href="/dashboard">Dashboard</Link>
|
||||
<Link href="/settings">Settings</Link>
|
||||
<UserButton />
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<div className={styles.nav}>
|
||||
{user ? (
|
||||
<>
|
||||
<Link href="/dashboard">
|
||||
<FormattedMessage id="header.nav.dashboard" defaultMessage="Dashboard" />
|
||||
</Link>
|
||||
<Link href="/settings">
|
||||
<FormattedMessage id="header.nav.settings" defaultMessage="Settings" />
|
||||
</Link>
|
||||
<LanguageButton menuAlign="right" />
|
||||
<UserButton />
|
||||
</>
|
||||
) : (
|
||||
<LanguageButton menuAlign="right" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
@ -13,6 +13,10 @@ export default function Layout({ title, children, header = true, footer = true }
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</Head>
|
||||
{header && <Header />}
|
||||
<main className="container">{children}</main>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useSpring, animated } from 'react-spring';
|
||||
import classNames from 'classnames';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import styles from './ActiveUsers.module.css';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default function ActiveUsers({ websiteId, className }) {
|
||||
const { data } = useFetch(`/api/website/${websiteId}/active`, {}, { interval: 60000 });
|
||||
@ -10,11 +10,6 @@ export default function ActiveUsers({ websiteId, className }) {
|
||||
return data?.[0]?.x || 0;
|
||||
}, [data]);
|
||||
|
||||
const props = useSpring({
|
||||
x: count,
|
||||
from: { x: 0 },
|
||||
});
|
||||
|
||||
if (count === 0) {
|
||||
return null;
|
||||
}
|
||||
@ -23,10 +18,13 @@ export default function ActiveUsers({ websiteId, className }) {
|
||||
<div className={classNames(styles.container, className)}>
|
||||
<div className={styles.dot} />
|
||||
<div className={styles.text}>
|
||||
<animated.div className={styles.value}>
|
||||
{props.x.interpolate(x => x.toFixed(0))}
|
||||
</animated.div>
|
||||
<div>{`current visitor${count !== 1 ? 's' : ''}`}</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="active-users.message"
|
||||
defaultMessage="{x} current {x, plural, one {visitor} other {visitors}}"
|
||||
values={{ x: count }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -3,8 +3,9 @@ import ReactTooltip from 'react-tooltip';
|
||||
import classNames from 'classnames';
|
||||
import ChartJS from 'chart.js';
|
||||
import styles from './BarChart.module.css';
|
||||
import { format } from 'date-fns';
|
||||
import { formatLongNumber } from 'lib/format';
|
||||
import { dateFormat } from 'lib/lang';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
export default function BarChart({
|
||||
chartId,
|
||||
@ -21,6 +22,7 @@ export default function BarChart({
|
||||
const canvas = useRef();
|
||||
const chart = useRef();
|
||||
const [tooltip, setTooltip] = useState({});
|
||||
const locale = useSelector(state => state.app.locale);
|
||||
|
||||
function renderXLabel(label, index, values) {
|
||||
const d = new Date(values[index].value);
|
||||
@ -28,23 +30,23 @@ export default function BarChart({
|
||||
|
||||
switch (unit) {
|
||||
case 'hour':
|
||||
return format(d, 'ha');
|
||||
return dateFormat(d, 'ha', locale);
|
||||
case 'day':
|
||||
if (records > 31) {
|
||||
if (w <= 500) {
|
||||
return index % 10 === 0 ? format(d, 'M/d') : '';
|
||||
return index % 10 === 0 ? dateFormat(d, 'M/d', locale) : '';
|
||||
}
|
||||
return index % 5 === 0 ? format(d, 'M/d') : '';
|
||||
return index % 5 === 0 ? dateFormat(d, 'M/d', locale) : '';
|
||||
}
|
||||
if (w <= 500) {
|
||||
return index % 2 === 0 ? format(d, 'MMM d') : '';
|
||||
return index % 2 === 0 ? dateFormat(d, 'MMM d', locale) : '';
|
||||
}
|
||||
return format(d, 'EEE M/d');
|
||||
return dateFormat(d, 'EEE M/d', locale);
|
||||
case 'month':
|
||||
if (w <= 660) {
|
||||
return format(d, 'MMM');
|
||||
return dateFormat(d, 'MMM', locale);
|
||||
}
|
||||
return format(d, 'MMMM');
|
||||
return dateFormat(d, 'MMMM', locale);
|
||||
default:
|
||||
return label;
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { browserFilter } from 'lib/filters';
|
||||
|
||||
export default function BrowsersTable({ websiteId, limit, onExpand }) {
|
||||
return (
|
||||
<MetricsTable
|
||||
title="Browsers"
|
||||
title={<FormattedMessage id="metrics.browsers" defaultMessage="Browsers" />}
|
||||
type="browser"
|
||||
metric="Visitors"
|
||||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
websiteId={websiteId}
|
||||
limit={limit}
|
||||
dataFilter={browserFilter}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { countryFilter, percentFilter } from 'lib/filters';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default function CountriesTable({ websiteId, limit, onDataLoad = () => {}, onExpand }) {
|
||||
return (
|
||||
<MetricsTable
|
||||
title="Countries"
|
||||
title={<FormattedMessage id="metrics.countries" defaultMessage="Countries" />}
|
||||
type="country"
|
||||
metric="Visitors"
|
||||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
websiteId={websiteId}
|
||||
limit={limit}
|
||||
dataFilter={countryFilter}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { deviceFilter } from 'lib/filters';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default function DevicesTable({ websiteId, limit, onExpand }) {
|
||||
return (
|
||||
<MetricsTable
|
||||
title="Devices"
|
||||
title={<FormattedMessage id="metrics.devices" defaultMessage="Devices" />}
|
||||
type="device"
|
||||
metric="Visitors"
|
||||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
websiteId={websiteId}
|
||||
limit={limit}
|
||||
dataFilter={deviceFilter}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import styles from './EventsTable.module.css';
|
||||
|
||||
export default function EventsTable({ websiteId, limit, onExpand, onDataLoad }) {
|
||||
return (
|
||||
<MetricsTable
|
||||
title="Events"
|
||||
title={<FormattedMessage id="metrics.events" defaultMessage="Events" />}
|
||||
type="event"
|
||||
metric="Actions"
|
||||
metric={<FormattedMessage id="metrics.actions" defaultMessage="Actions" />}
|
||||
websiteId={websiteId}
|
||||
limit={limit}
|
||||
renderLabel={({ x }) => <Label value={x} />}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import MetricCard from './MetricCard';
|
||||
import Loading from 'components/common/Loading';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { useDateRange } from 'hooks/useDateRange';
|
||||
import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format';
|
||||
import MetricCard from './MetricCard';
|
||||
import styles from './MetricsBar.module.css';
|
||||
import { useDateRange } from '../../hooks/useDateRange';
|
||||
|
||||
export default function MetricsBar({ websiteId, className }) {
|
||||
const dateRange = useDateRange(websiteId);
|
||||
@ -36,15 +37,28 @@ export default function MetricsBar({ websiteId, className }) {
|
||||
<Loading />
|
||||
) : (
|
||||
<>
|
||||
<MetricCard label="Views" value={pageviews} format={formatFunc} />
|
||||
<MetricCard label="Visitors" value={uniques} format={formatFunc} />
|
||||
<MetricCard
|
||||
label="Bounce rate"
|
||||
label={<FormattedMessage id="metrics.views" defaultMessage="Views" />}
|
||||
value={pageviews}
|
||||
format={formatFunc}
|
||||
/>
|
||||
<MetricCard
|
||||
label={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
value={uniques}
|
||||
format={formatFunc}
|
||||
/>
|
||||
<MetricCard
|
||||
label={<FormattedMessage id="metrics.bounce-rate" defaultMessage="Bounce rate" />}
|
||||
value={pageviews ? (bounces / pageviews) * 100 : 0}
|
||||
format={n => Number(n).toFixed(0) + '%'}
|
||||
/>
|
||||
<MetricCard
|
||||
label="Average visit time"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="metrics.average-visit-time"
|
||||
defaultMessage="Average visit time"
|
||||
/>
|
||||
}
|
||||
value={totaltime && pageviews ? totaltime / (pageviews - bounces) : 0}
|
||||
format={n => formatShortTime(n, ['m', 's'], ' ')}
|
||||
/>
|
||||
|
@ -10,6 +10,7 @@ import { percentFilter } from 'lib/filters';
|
||||
import { formatNumber, formatLongNumber } from 'lib/format';
|
||||
import { useDateRange } from 'hooks/useDateRange';
|
||||
import styles from './MetricsTable.module.css';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default function MetricsTable({
|
||||
websiteId,
|
||||
@ -97,7 +98,9 @@ export default function MetricsTable({
|
||||
<div className={styles.footer}>
|
||||
{limit && data.length > limit && (
|
||||
<Button icon={<Arrow />} size="xsmall" onClick={() => onExpand(type)}>
|
||||
<div>More</div>
|
||||
<div>
|
||||
<FormattedMessage id="button.more" defaultMessage="More" />
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { osFilter } from 'lib/filters';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default function OSTable({ websiteId, limit, onExpand }) {
|
||||
return (
|
||||
<MetricsTable
|
||||
title="Operating System"
|
||||
title={<FormattedMessage id="metrics.operating-system" defaultMessage="Operating system" />}
|
||||
type="os"
|
||||
metric="Visitors"
|
||||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
websiteId={websiteId}
|
||||
limit={limit}
|
||||
dataFilter={osFilter}
|
||||
|
@ -1,34 +1,39 @@
|
||||
import React, { useState } from 'react';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ButtonGroup from 'components/common/ButtonGroup';
|
||||
import { urlFilter } from 'lib/filters';
|
||||
import ButtonGroup from '../common/ButtonGroup';
|
||||
import { FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
|
||||
import MetricsTable from './MetricsTable';
|
||||
|
||||
export default function PagesTable({ websiteId, websiteDomain, limit, onExpand }) {
|
||||
const [filter, setFilter] = useState('Combined');
|
||||
const [filter, setFilter] = useState(FILTER_COMBINED);
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: <FormattedMessage id="metrics.filter.combined" defaultMessage="Combined" />,
|
||||
value: FILTER_COMBINED,
|
||||
},
|
||||
{ label: <FormattedMessage id="metrics.filter.raw" defaultMessage="Raw" />, value: FILTER_RAW },
|
||||
];
|
||||
|
||||
return (
|
||||
<MetricsTable
|
||||
title="Pages"
|
||||
title={<FormattedMessage id="metrics.pages" defaultMessage="Pages" />}
|
||||
type="url"
|
||||
metric="Views"
|
||||
headerComponent={limit ? null : <FilterButtons selected={filter} onClick={setFilter} />}
|
||||
metric={<FormattedMessage id="metrics.views" defaultMessage="Views" />}
|
||||
headerComponent={
|
||||
limit ? null : <FilterButtons buttons={buttons} selected={filter} onClick={setFilter} />
|
||||
}
|
||||
websiteId={websiteId}
|
||||
limit={limit}
|
||||
dataFilter={urlFilter}
|
||||
filterOptions={{ domain: websiteDomain, raw: filter === 'Raw' }}
|
||||
filterOptions={{ domain: websiteDomain, raw: filter === FILTER_RAW }}
|
||||
renderLabel={({ x }) => decodeURI(x)}
|
||||
onExpand={onExpand}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const FilterButtons = ({ selected, onClick }) => {
|
||||
return (
|
||||
<ButtonGroup
|
||||
size="xsmall"
|
||||
items={['Combined', 'Raw']}
|
||||
selectedItem={selected}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
const FilterButtons = ({ buttons, selected, onClick }) => {
|
||||
return <ButtonGroup size="xsmall" items={buttons} selectedItem={selected} onClick={onClick} />;
|
||||
};
|
||||
|
@ -1,8 +1,11 @@
|
||||
import React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import CheckVisible from 'components/helpers/CheckVisible';
|
||||
import BarChart from './BarChart';
|
||||
|
||||
export default function PageviewsChart({ websiteId, data, unit, records, className }) {
|
||||
const intl = useIntl();
|
||||
|
||||
const handleUpdate = chart => {
|
||||
const {
|
||||
data: { datasets },
|
||||
@ -26,7 +29,10 @@ export default function PageviewsChart({ websiteId, data, unit, records, classNa
|
||||
chartId={websiteId}
|
||||
datasets={[
|
||||
{
|
||||
label: 'unique visitors',
|
||||
label: intl.formatMessage({
|
||||
id: 'metrics.unique-visitors',
|
||||
defaultMessage: 'Unique visitors',
|
||||
}),
|
||||
data: data.uniques,
|
||||
lineTension: 0,
|
||||
backgroundColor: 'rgb(38, 128, 235, 0.4)',
|
||||
@ -34,7 +40,10 @@ export default function PageviewsChart({ websiteId, data, unit, records, classNa
|
||||
borderWidth: 1,
|
||||
},
|
||||
{
|
||||
label: 'page views',
|
||||
label: intl.formatMessage({
|
||||
id: 'metrics.page-views',
|
||||
defaultMessage: 'Page views',
|
||||
}),
|
||||
data: data.pageviews,
|
||||
lineTension: 0,
|
||||
backgroundColor: 'rgb(38, 128, 235, 0.2)',
|
||||
|
@ -3,18 +3,18 @@ import ButtonGroup from 'components/common/ButtonGroup';
|
||||
import { getDateRange } from 'lib/date';
|
||||
import styles from './QuickButtons.module.css';
|
||||
|
||||
const options = {
|
||||
'24h': '24hour',
|
||||
'7d': '7day',
|
||||
'30d': '30day',
|
||||
};
|
||||
const options = [
|
||||
{ label: '24h', value: '24hour' },
|
||||
{ label: '7d', value: '7day' },
|
||||
{ label: '30d', value: '30day' },
|
||||
];
|
||||
|
||||
export default function QuickButtons({ value, onChange }) {
|
||||
const selectedItem = Object.keys(options).find(key => options[key] === value);
|
||||
const selectedItem = options.find(item => item.value === value)?.value;
|
||||
|
||||
function handleClick(selected) {
|
||||
if (options[selected] !== value) {
|
||||
onChange(getDateRange(options[selected]));
|
||||
if (selected !== value) {
|
||||
onChange(getDateRange(selected));
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ export default function QuickButtons({ value, onChange }) {
|
||||
<ButtonGroup
|
||||
size="xsmall"
|
||||
className={styles.buttons}
|
||||
items={Object.keys(options)}
|
||||
items={options}
|
||||
selectedItem={selectedItem}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
|
@ -1,10 +1,24 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { refFilter } from 'lib/filters';
|
||||
import ButtonGroup from 'components/common/ButtonGroup';
|
||||
import { FILTER_DOMAIN_ONLY, FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
|
||||
|
||||
export default function ReferrersTable({ websiteId, websiteDomain, limit, onExpand = () => {} }) {
|
||||
const [filter, setFilter] = useState('Combined');
|
||||
const [filter, setFilter] = useState(FILTER_COMBINED);
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: <FormattedMessage id="metrics.filter.domain-only" defaultMessage="Domain only" />,
|
||||
value: FILTER_DOMAIN_ONLY,
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="metrics.filter.combined" defaultMessage="Combined" />,
|
||||
value: FILTER_COMBINED,
|
||||
},
|
||||
{ label: <FormattedMessage id="metrics.filter.raw" defaultMessage="Raw" />, value: FILTER_RAW },
|
||||
];
|
||||
|
||||
const renderLink = ({ x: url }) => {
|
||||
return url.startsWith('http') ? (
|
||||
@ -18,18 +32,20 @@ export default function ReferrersTable({ websiteId, websiteDomain, limit, onExpa
|
||||
|
||||
return (
|
||||
<MetricsTable
|
||||
title="Referrers"
|
||||
title={<FormattedMessage id="metrics.referrers" defaultMessage="Referrers" />}
|
||||
type="referrer"
|
||||
metric="Views"
|
||||
headerComponent={limit ? null : <FilterButtons selected={filter} onClick={setFilter} />}
|
||||
metric={<FormattedMessage id="metrics.views" defaultMessage="Views" />}
|
||||
headerComponent={
|
||||
limit ? null : <FilterButtons buttons={buttons} selected={filter} onClick={setFilter} />
|
||||
}
|
||||
websiteId={websiteId}
|
||||
websiteDomain={websiteDomain}
|
||||
limit={limit}
|
||||
dataFilter={refFilter}
|
||||
filterOptions={{
|
||||
domain: websiteDomain,
|
||||
domainOnly: filter === 'Domain only',
|
||||
raw: filter === 'Raw',
|
||||
domainOnly: filter === FILTER_DOMAIN_ONLY,
|
||||
raw: filter === FILTER_RAW,
|
||||
}}
|
||||
onExpand={onExpand}
|
||||
renderLabel={renderLink}
|
||||
@ -37,13 +53,6 @@ export default function ReferrersTable({ websiteId, websiteDomain, limit, onExpa
|
||||
);
|
||||
}
|
||||
|
||||
const FilterButtons = ({ selected, onClick }) => {
|
||||
return (
|
||||
<ButtonGroup
|
||||
size="xsmall"
|
||||
items={['Domain only', 'Combined', 'Raw']}
|
||||
selectedItem={selected}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
const FilterButtons = ({ buttons, selected, onClick }) => {
|
||||
return <ButtonGroup size="xsmall" items={buttons} selectedItem={selected} onClick={onClick} />;
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useRouter } from 'next/router';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import Button from 'components/common/Button';
|
||||
@ -27,7 +28,9 @@ export default function WebsiteHeader({ websiteId, title, showLink = false }) {
|
||||
}
|
||||
size="small"
|
||||
>
|
||||
<div>View details</div>
|
||||
<div>
|
||||
<FormattedMessage id="button.view-details" defaultMessage="View details" />
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
</ButtonLayout>
|
||||
|
@ -15,6 +15,7 @@ import Trash from 'assets/trash.svg';
|
||||
import Check from 'assets/check.svg';
|
||||
import styles from './AccountSettings.module.css';
|
||||
import Toast from '../common/Toast';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default function AccountSettings() {
|
||||
const [addAccount, setAddAccount] = useState();
|
||||
@ -30,19 +31,27 @@ export default function AccountSettings() {
|
||||
row.username !== 'admin' ? (
|
||||
<ButtonLayout>
|
||||
<Button icon={<Pen />} size="small" onClick={() => setEditAccount(row)}>
|
||||
<div>Edit</div>
|
||||
<div>
|
||||
<FormattedMessage id="button.edit" defaultMessage="Edit" />
|
||||
</div>
|
||||
</Button>
|
||||
<Button icon={<Trash />} size="small" onClick={() => setDeleteAccount(row)}>
|
||||
<div>Delete</div>
|
||||
<div>
|
||||
<FormattedMessage id="button.delete" defaultMessage="Delete" />
|
||||
</div>
|
||||
</Button>
|
||||
</ButtonLayout>
|
||||
) : null;
|
||||
|
||||
const columns = [
|
||||
{ key: 'username', label: 'Username', className: 'col-6 col-md-4' },
|
||||
{
|
||||
key: 'username',
|
||||
label: <FormattedMessage id="label.username" defaultMessage="Username" />,
|
||||
className: 'col-6 col-md-4',
|
||||
},
|
||||
{
|
||||
key: 'is_admin',
|
||||
label: 'Administrator',
|
||||
label: <FormattedMessage id="label.adminsitrator" defaultMessage="Administrator" />,
|
||||
className: 'col-6 col-md-4',
|
||||
render: Checkmark,
|
||||
},
|
||||
@ -54,7 +63,7 @@ export default function AccountSettings() {
|
||||
|
||||
function handleSave() {
|
||||
setSaved(state => state + 1);
|
||||
setMessage('Saved successfully.');
|
||||
setMessage(<FormattedMessage id="message.save-success" defaultMessage="Saved successfully." />);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
@ -73,12 +82,14 @@ export default function AccountSettings() {
|
||||
<PageHeader>
|
||||
<div>Accounts</div>
|
||||
<Button icon={<Plus />} size="small" onClick={() => setAddAccount(true)}>
|
||||
<div>Add account</div>
|
||||
<div>
|
||||
<FormattedMessage id="button.add-account" defaultMessage="Add account" />
|
||||
</div>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Table columns={columns} rows={data} />
|
||||
{editAccount && (
|
||||
<Modal title="Edit account">
|
||||
<Modal title={<FormattedMessage id="title.edit-account" defaultMessage="Edit account" />}>
|
||||
<AccountEditForm
|
||||
values={{ ...editAccount, password: '' }}
|
||||
onSave={handleSave}
|
||||
@ -87,12 +98,14 @@ export default function AccountSettings() {
|
||||
</Modal>
|
||||
)}
|
||||
{addAccount && (
|
||||
<Modal title="Add account">
|
||||
<Modal title={<FormattedMessage id="title.add-account" defaultMessage="Add account" />}>
|
||||
<AccountEditForm onSave={handleSave} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
{deleteAccount && (
|
||||
<Modal title="Delete account">
|
||||
<Modal
|
||||
title={<FormattedMessage id="title.delete-account" defaultMessage="Delete account" />}
|
||||
>
|
||||
<DeleteForm
|
||||
values={{ type: 'account', id: deleteAccount.user_id, name: deleteAccount.username }}
|
||||
onSave={handleSave}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import Button from 'components/common/Button';
|
||||
import ChangePasswordForm from '../forms/ChangePasswordForm';
|
||||
import Modal from 'components/common/Modal';
|
||||
import Toast from 'components/common/Toast';
|
||||
import ChangePasswordForm from 'components/forms/ChangePasswordForm';
|
||||
import Dots from 'assets/ellipsis-h.svg';
|
||||
import Toast from '../common/Toast';
|
||||
|
||||
export default function ProfileSettings() {
|
||||
const user = useSelector(state => state.user);
|
||||
@ -15,19 +16,25 @@ export default function ProfileSettings() {
|
||||
|
||||
function handleSave() {
|
||||
setChangePassword(false);
|
||||
setMessage('Saved successfully.');
|
||||
setMessage(<FormattedMessage id="message.save-success" defaultMessage="Saved successfully." />);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader>
|
||||
<div>Profile</div>
|
||||
<div>
|
||||
<FormattedMessage id="settings.profile" defaultMessage="Profile" />
|
||||
</div>
|
||||
<Button icon={<Dots />} size="small" onClick={() => setChangePassword(true)}>
|
||||
<div>Change password</div>
|
||||
<div>
|
||||
<FormattedMessage id="button.change-password" defaultMessage="Change password" />
|
||||
</div>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<dl>
|
||||
<dt>Username</dt>
|
||||
<dt>
|
||||
<FormattedMessage id="label.username" defaultMessage="Username" />
|
||||
</dt>
|
||||
<dd>{user.username}</dd>
|
||||
</dl>
|
||||
{changePassword && (
|
||||
|
@ -5,23 +5,35 @@ import WebsiteSettings from './WebsiteSettings';
|
||||
import AccountSettings from './AccountSettings';
|
||||
import ProfileSettings from './ProfileSettings';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const WEBSITES = 1;
|
||||
const ACCOUNTS = 2;
|
||||
const PROFILE = 3;
|
||||
|
||||
export default function Settings() {
|
||||
const user = useSelector(state => state.user);
|
||||
const [option, setOption] = useState(1);
|
||||
const [option, setOption] = useState(WEBSITES);
|
||||
|
||||
const menuOptions = [
|
||||
{ label: 'Websites', value: 1 },
|
||||
{ label: 'Accounts', value: 2, hidden: !user.is_admin },
|
||||
{ label: 'Profile', value: 3 },
|
||||
{
|
||||
label: <FormattedMessage id="settings.websites" defaultMessage="Websites" />,
|
||||
value: WEBSITES,
|
||||
},
|
||||
{
|
||||
label: <FormattedMessage id="settings.accounts" defaultMessage="Accounts" />,
|
||||
value: ACCOUNTS,
|
||||
hidden: !user.is_admin,
|
||||
},
|
||||
{ label: <FormattedMessage id="settings.profile" defaultMessage="Profile" />, value: PROFILE },
|
||||
];
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<MenuLayout menu={menuOptions} selectedOption={option} onMenuSelect={setOption}>
|
||||
{option === 1 && <WebsiteSettings />}
|
||||
{option === 2 && <AccountSettings />}
|
||||
{option === 3 && <ProfileSettings />}
|
||||
{option === WEBSITES && <WebsiteSettings />}
|
||||
{option === ACCOUNTS && <AccountSettings />}
|
||||
{option === PROFILE && <ProfileSettings />}
|
||||
</MenuLayout>
|
||||
</Page>
|
||||
);
|
||||
|
@ -1,23 +1,24 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import Table from 'components/common/Table';
|
||||
import Button from 'components/common/Button';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import Modal from 'components/common/Modal';
|
||||
import WebsiteEditForm from '../forms/WebsiteEditForm';
|
||||
import DeleteForm from '../forms/DeleteForm';
|
||||
import TrackingCodeForm from '../forms/TrackingCodeForm';
|
||||
import ShareUrlForm from '../forms/ShareUrlForm';
|
||||
import WebsiteEditForm from 'components/forms/WebsiteEditForm';
|
||||
import DeleteForm from 'components/forms/DeleteForm';
|
||||
import TrackingCodeForm from 'components/forms/TrackingCodeForm';
|
||||
import ShareUrlForm from 'components/forms/ShareUrlForm';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
import ButtonLayout from 'components/layout/ButtonLayout';
|
||||
import Toast from 'components/common/Toast';
|
||||
import Pen from 'assets/pen.svg';
|
||||
import Trash from 'assets/trash.svg';
|
||||
import Plus from 'assets/plus.svg';
|
||||
import Code from 'assets/code.svg';
|
||||
import Link from 'assets/link.svg';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import styles from './WebsiteSettings.module.css';
|
||||
import useFetch from '../../hooks/useFetch';
|
||||
import Toast from '../common/Toast';
|
||||
|
||||
export default function WebsiteSettings() {
|
||||
const [editWebsite, setEditWebsite] = useState();
|
||||
@ -35,7 +36,7 @@ export default function WebsiteSettings() {
|
||||
<Button
|
||||
icon={<Link />}
|
||||
size="small"
|
||||
tooltip="Share URL"
|
||||
tooltip={<FormattedMessage id="tooltip.get-share-url" defaultMessage="Get share URL" />}
|
||||
tooltipId={`button-share-${row.website_id}`}
|
||||
onClick={() => setShowUrl(row)}
|
||||
/>
|
||||
@ -43,22 +44,36 @@ export default function WebsiteSettings() {
|
||||
<Button
|
||||
icon={<Code />}
|
||||
size="small"
|
||||
tooltip="Get tracking code"
|
||||
tooltip={
|
||||
<FormattedMessage id="tooltip.get-tracking-code" defaultMessage="Get tracking code" />
|
||||
}
|
||||
tooltipId={`button-code-${row.website_id}`}
|
||||
onClick={() => setShowCode(row)}
|
||||
/>
|
||||
<Button icon={<Pen />} size="small" onClick={() => setEditWebsite(row)}>
|
||||
<div>Edit</div>
|
||||
<div>
|
||||
<FormattedMessage id="button.edit" defaultMessage="Edit" />
|
||||
</div>
|
||||
</Button>
|
||||
<Button icon={<Trash />} size="small" onClick={() => setDeleteWebsite(row)}>
|
||||
<div>Delete</div>
|
||||
<div>
|
||||
<FormattedMessage id="button.delete" defaultMessage="Delete" />
|
||||
</div>
|
||||
</Button>
|
||||
</ButtonLayout>
|
||||
);
|
||||
|
||||
const columns = [
|
||||
{ key: 'name', label: 'Name', className: 'col-6 col-md-4' },
|
||||
{ key: 'domain', label: 'Domain', className: 'col-6 col-md-4' },
|
||||
{
|
||||
key: 'name',
|
||||
label: <FormattedMessage id="label.name" defaultMessage="Name" />,
|
||||
className: 'col-6 col-md-4',
|
||||
},
|
||||
{
|
||||
key: 'domain',
|
||||
label: <FormattedMessage id="label.domain" defaultMessage="Domain" />,
|
||||
className: 'col-6 col-md-4',
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
className: classNames(styles.buttons, 'col-12 col-md-4 pt-2 pt-md-0'),
|
||||
@ -68,7 +83,7 @@ export default function WebsiteSettings() {
|
||||
|
||||
function handleSave() {
|
||||
setSaved(state => state + 1);
|
||||
setMessage('Saved successfully.');
|
||||
setMessage(<FormattedMessage id="message.save-success" defaultMessage="Saved successfully." />);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
@ -85,9 +100,18 @@ export default function WebsiteSettings() {
|
||||
}
|
||||
|
||||
const empty = (
|
||||
<EmptyPlaceholder msg={"You don't have any websites configured."}>
|
||||
<EmptyPlaceholder
|
||||
msg={
|
||||
<FormattedMessage
|
||||
id="placeholder.message.no-websites-configured"
|
||||
defaultMessage="You don't have any websites configured."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button icon={<Plus />} size="medium" onClick={() => setAddWebsite(true)}>
|
||||
<div>Add website</div>
|
||||
<div>
|
||||
<FormattedMessage id="button.add-website" defaultMessage="Add website" />
|
||||
</div>
|
||||
</Button>
|
||||
</EmptyPlaceholder>
|
||||
);
|
||||
@ -95,24 +119,30 @@ export default function WebsiteSettings() {
|
||||
return (
|
||||
<>
|
||||
<PageHeader>
|
||||
<div>Websites</div>
|
||||
<div>
|
||||
<FormattedMessage id="settings.websites" defaultMessage="Websites" />
|
||||
</div>
|
||||
<Button icon={<Plus />} size="small" onClick={() => setAddWebsite(true)}>
|
||||
<div>Add website</div>
|
||||
<div>
|
||||
<FormattedMessage id="button.add-website" defaultMessage="Add website" />
|
||||
</div>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Table columns={columns} rows={data} empty={empty} />
|
||||
{editWebsite && (
|
||||
<Modal title="Edit website">
|
||||
<Modal title={<FormattedMessage id="title.edit-website" defaultMessage="Edit website" />}>
|
||||
<WebsiteEditForm values={editWebsite} onSave={handleSave} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
{addWebsite && (
|
||||
<Modal title="Add website">
|
||||
<Modal title={<FormattedMessage id="title.add-website" defaultMessage="Add website" />}>
|
||||
<WebsiteEditForm onSave={handleSave} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
{deleteWebsite && (
|
||||
<Modal title="Delete website">
|
||||
<Modal
|
||||
title={<FormattedMessage id="title.delete-website" defaultMessage="Delete website" />}
|
||||
>
|
||||
<DeleteForm
|
||||
values={{ type: 'website', id: deleteWebsite.website_id, name: deleteWebsite.name }}
|
||||
onSave={handleSave}
|
||||
@ -121,12 +151,12 @@ export default function WebsiteSettings() {
|
||||
</Modal>
|
||||
)}
|
||||
{showCode && (
|
||||
<Modal title="Tracking code">
|
||||
<Modal title={<FormattedMessage id="title.tracking-code" defaultMessage="Tracking code" />}>
|
||||
<TrackingCodeForm values={showCode} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
{showUrl && (
|
||||
<Modal title="Share URL">
|
||||
<Modal title={<FormattedMessage id="title.share-url" defaultMessage="Share URL" />}>
|
||||
<ShareUrlForm values={showUrl} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
|
236
lang/en.json
Normal file
236
lang/en.json
Normal file
@ -0,0 +1,236 @@
|
||||
{
|
||||
"active-users.message": {
|
||||
"defaultMessage": "{x} current {x, plural, one {visitor} other {visitors}}"
|
||||
},
|
||||
"button.add-account": {
|
||||
"defaultMessage": "Add account"
|
||||
},
|
||||
"button.add-website": {
|
||||
"defaultMessage": "Add website"
|
||||
},
|
||||
"button.back": {
|
||||
"defaultMessage": "Back"
|
||||
},
|
||||
"button.cancel": {
|
||||
"defaultMessage": "Cancel"
|
||||
},
|
||||
"button.change-password": {
|
||||
"defaultMessage": "Change password"
|
||||
},
|
||||
"button.copy-to-clipboard": {
|
||||
"defaultMessage": "Copy to clipboard"
|
||||
},
|
||||
"button.delete": {
|
||||
"defaultMessage": "Delete"
|
||||
},
|
||||
"button.edit": {
|
||||
"defaultMessage": "Edit"
|
||||
},
|
||||
"button.login": {
|
||||
"defaultMessage": "Login"
|
||||
},
|
||||
"button.more": {
|
||||
"defaultMessage": "More"
|
||||
},
|
||||
"button.save": {
|
||||
"defaultMessage": "Save"
|
||||
},
|
||||
"button.view-details": {
|
||||
"defaultMessage": "View details"
|
||||
},
|
||||
"footer.powered-by": {
|
||||
"defaultMessage": "powered by"
|
||||
},
|
||||
"header.nav.dashboard": {
|
||||
"defaultMessage": "Dashboard"
|
||||
},
|
||||
"header.nav.settings": {
|
||||
"defaultMessage": "Settings"
|
||||
},
|
||||
"label.adminsitrator": {
|
||||
"defaultMessage": "Administrator"
|
||||
},
|
||||
"label.confirm-password": {
|
||||
"defaultMessage": "Confirm password"
|
||||
},
|
||||
"label.current-password": {
|
||||
"defaultMessage": "Current password"
|
||||
},
|
||||
"label.domain": {
|
||||
"defaultMessage": "Domain"
|
||||
},
|
||||
"label.enable-share-url": {
|
||||
"defaultMessage": "Enable share URL"
|
||||
},
|
||||
"label.invalid": {
|
||||
"defaultMessage": "Invalid"
|
||||
},
|
||||
"label.invalid-domain": {
|
||||
"defaultMessage": "Invalid domain"
|
||||
},
|
||||
"label.last-days": {
|
||||
"defaultMessage": "Last {x} days"
|
||||
},
|
||||
"label.last-hours": {
|
||||
"defaultMessage": "Last {x} hours"
|
||||
},
|
||||
"label.logged-in-as": {
|
||||
"defaultMessage": "Logged in as {username}"
|
||||
},
|
||||
"label.logout": {
|
||||
"defaultMessage": "Logout"
|
||||
},
|
||||
"label.name": {
|
||||
"defaultMessage": "Name"
|
||||
},
|
||||
"label.new-password": {
|
||||
"defaultMessage": "New password"
|
||||
},
|
||||
"label.password": {
|
||||
"defaultMessage": "Password"
|
||||
},
|
||||
"label.passwords-dont-match": {
|
||||
"defaultMessage": "Passwords don't match"
|
||||
},
|
||||
"label.required": {
|
||||
"defaultMessage": "Required"
|
||||
},
|
||||
"label.this-month": {
|
||||
"defaultMessage": "This month"
|
||||
},
|
||||
"label.this-week": {
|
||||
"defaultMessage": "This week"
|
||||
},
|
||||
"label.this-year": {
|
||||
"defaultMessage": "This year"
|
||||
},
|
||||
"label.today": {
|
||||
"defaultMessage": "Today"
|
||||
},
|
||||
"label.username": {
|
||||
"defaultMessage": "Username"
|
||||
},
|
||||
"message.confirm-delete": {
|
||||
"defaultMessage": "Are your sure you want to delete {target}?"
|
||||
},
|
||||
"message.copied": {
|
||||
"defaultMessage": "Copied!"
|
||||
},
|
||||
"message.delete-warning": {
|
||||
"defaultMessage": "All associated data will be deleted as well."
|
||||
},
|
||||
"message.failure": {
|
||||
"defaultMessage": "Something went wrong."
|
||||
},
|
||||
"message.incorrect-username-password": {
|
||||
"defaultMessage": "Incorrect username/password."
|
||||
},
|
||||
"message.save-success": {
|
||||
"defaultMessage": "Saved successfully."
|
||||
},
|
||||
"message.share-url": {
|
||||
"defaultMessage": "This is the publicly shared URL for {target}."
|
||||
},
|
||||
"message.track-stats": {
|
||||
"defaultMessage": "To track stats for {target}, place the following code in the {head} section of your website."
|
||||
},
|
||||
"message.type-delete": {
|
||||
"defaultMessage": "Type {delete} in the box below to confirm."
|
||||
},
|
||||
"metrics.actions": {
|
||||
"defaultMessage": "Actions"
|
||||
},
|
||||
"metrics.average-visit-time": {
|
||||
"defaultMessage": "Average visit time"
|
||||
},
|
||||
"metrics.bounce-rate": {
|
||||
"defaultMessage": "Bounce rate"
|
||||
},
|
||||
"metrics.browsers": {
|
||||
"defaultMessage": "Browsers"
|
||||
},
|
||||
"metrics.countries": {
|
||||
"defaultMessage": "Countries"
|
||||
},
|
||||
"metrics.devices": {
|
||||
"defaultMessage": "Devices"
|
||||
},
|
||||
"metrics.events": {
|
||||
"defaultMessage": "Events"
|
||||
},
|
||||
"metrics.filter.combined": {
|
||||
"defaultMessage": "Combined"
|
||||
},
|
||||
"metrics.filter.domain-only": {
|
||||
"defaultMessage": "Domain only"
|
||||
},
|
||||
"metrics.filter.raw": {
|
||||
"defaultMessage": "Raw"
|
||||
},
|
||||
"metrics.operating-system": {
|
||||
"defaultMessage": "Operating system"
|
||||
},
|
||||
"metrics.page-views": {
|
||||
"defaultMessage": "Page views"
|
||||
},
|
||||
"metrics.pages": {
|
||||
"defaultMessage": "Pages"
|
||||
},
|
||||
"metrics.referrers": {
|
||||
"defaultMessage": "Referrers"
|
||||
},
|
||||
"metrics.unique-visitors": {
|
||||
"defaultMessage": "Unique visitors"
|
||||
},
|
||||
"metrics.views": {
|
||||
"defaultMessage": "Views"
|
||||
},
|
||||
"metrics.visitors": {
|
||||
"defaultMessage": "Visitors"
|
||||
},
|
||||
"placeholder.message.go-to-settings": {
|
||||
"defaultMessage": "Go to settings"
|
||||
},
|
||||
"placeholder.message.no-websites-configured": {
|
||||
"defaultMessage": "You don't have any websites configured."
|
||||
},
|
||||
"settings.accounts": {
|
||||
"defaultMessage": "Accounts"
|
||||
},
|
||||
"settings.profile": {
|
||||
"defaultMessage": "Profile"
|
||||
},
|
||||
"settings.websites": {
|
||||
"defaultMessage": "Websites"
|
||||
},
|
||||
"title.add-account": {
|
||||
"defaultMessage": "Add account"
|
||||
},
|
||||
"title.add-website": {
|
||||
"defaultMessage": "Add website"
|
||||
},
|
||||
"title.delete-account": {
|
||||
"defaultMessage": "Delete account"
|
||||
},
|
||||
"title.delete-website": {
|
||||
"defaultMessage": "Delete website"
|
||||
},
|
||||
"title.edit-account": {
|
||||
"defaultMessage": "Edit account"
|
||||
},
|
||||
"title.edit-website": {
|
||||
"defaultMessage": "Edit website"
|
||||
},
|
||||
"title.share-url": {
|
||||
"defaultMessage": "Share URL"
|
||||
},
|
||||
"title.tracking-code": {
|
||||
"defaultMessage": "Tracking code"
|
||||
},
|
||||
"tooltip.get-share-url": {
|
||||
"defaultMessage": "Get share URL"
|
||||
},
|
||||
"tooltip.get-tracking-code": {
|
||||
"defaultMessage": "Get tracking code"
|
||||
}
|
||||
}
|
236
lang/zh-CN.json
Normal file
236
lang/zh-CN.json
Normal file
@ -0,0 +1,236 @@
|
||||
{
|
||||
"active-users.message": {
|
||||
"defaultMessage": "当前在线 {x} 人"
|
||||
},
|
||||
"button.add-account": {
|
||||
"defaultMessage": "添加账户"
|
||||
},
|
||||
"button.add-website": {
|
||||
"defaultMessage": "添加网站"
|
||||
},
|
||||
"button.back": {
|
||||
"defaultMessage": "返回"
|
||||
},
|
||||
"button.cancel": {
|
||||
"defaultMessage": "取消"
|
||||
},
|
||||
"button.change-password": {
|
||||
"defaultMessage": "更新密码"
|
||||
},
|
||||
"button.copy-to-clipboard": {
|
||||
"defaultMessage": "复制"
|
||||
},
|
||||
"button.delete": {
|
||||
"defaultMessage": "删除"
|
||||
},
|
||||
"button.edit": {
|
||||
"defaultMessage": "编辑"
|
||||
},
|
||||
"button.login": {
|
||||
"defaultMessage": "登录"
|
||||
},
|
||||
"button.more": {
|
||||
"defaultMessage": "更多"
|
||||
},
|
||||
"button.save": {
|
||||
"defaultMessage": "保存"
|
||||
},
|
||||
"button.view-details": {
|
||||
"defaultMessage": "查看更多"
|
||||
},
|
||||
"footer.powered-by": {
|
||||
"defaultMessage": "运行"
|
||||
},
|
||||
"header.nav.dashboard": {
|
||||
"defaultMessage": "仪表板"
|
||||
},
|
||||
"header.nav.settings": {
|
||||
"defaultMessage": "设置"
|
||||
},
|
||||
"label.adminsitrator": {
|
||||
"defaultMessage": "管理员"
|
||||
},
|
||||
"label.confirm-password": {
|
||||
"defaultMessage": "确认密码"
|
||||
},
|
||||
"label.current-password": {
|
||||
"defaultMessage": "目前密码"
|
||||
},
|
||||
"label.domain": {
|
||||
"defaultMessage": "域名"
|
||||
},
|
||||
"label.enable-share-url": {
|
||||
"defaultMessage": "激活共享链接"
|
||||
},
|
||||
"label.invalid": {
|
||||
"defaultMessage": "输入无效"
|
||||
},
|
||||
"label.invalid-domain": {
|
||||
"defaultMessage": "无效域名"
|
||||
},
|
||||
"label.last-days": {
|
||||
"defaultMessage": "最近 {x} 天"
|
||||
},
|
||||
"label.last-hours": {
|
||||
"defaultMessage": "最近 {x} 小时"
|
||||
},
|
||||
"label.logged-in-as": {
|
||||
"defaultMessage": "登录名: {username}"
|
||||
},
|
||||
"label.logout": {
|
||||
"defaultMessage": "退出"
|
||||
},
|
||||
"label.name": {
|
||||
"defaultMessage": "名字"
|
||||
},
|
||||
"label.new-password": {
|
||||
"defaultMessage": "新密码"
|
||||
},
|
||||
"label.password": {
|
||||
"defaultMessage": "密码"
|
||||
},
|
||||
"label.passwords-dont-match": {
|
||||
"defaultMessage": "密码不一致"
|
||||
},
|
||||
"label.required": {
|
||||
"defaultMessage": "必填"
|
||||
},
|
||||
"label.this-month": {
|
||||
"defaultMessage": "本月"
|
||||
},
|
||||
"label.this-week": {
|
||||
"defaultMessage": "本周"
|
||||
},
|
||||
"label.this-year": {
|
||||
"defaultMessage": "今年"
|
||||
},
|
||||
"label.today": {
|
||||
"defaultMessage": "今天"
|
||||
},
|
||||
"label.username": {
|
||||
"defaultMessage": "用户名"
|
||||
},
|
||||
"message.confirm-delete": {
|
||||
"defaultMessage": "你确定要删除{target}吗?"
|
||||
},
|
||||
"message.copied": {
|
||||
"defaultMessage": "复制成功!"
|
||||
},
|
||||
"message.delete-warning": {
|
||||
"defaultMessage": "所有相关数据将会被删除."
|
||||
},
|
||||
"message.failure": {
|
||||
"defaultMessage": "出现错误."
|
||||
},
|
||||
"message.incorrect-username-password": {
|
||||
"defaultMessage": "用户名密码不正确."
|
||||
},
|
||||
"message.save-success": {
|
||||
"defaultMessage": "成功保存."
|
||||
},
|
||||
"message.share-url": {
|
||||
"defaultMessage": "这是 {target} 的共享链接."
|
||||
},
|
||||
"message.track-stats": {
|
||||
"defaultMessage": "把以下代码放到你的网站的{head}部分来收集{target}的数据."
|
||||
},
|
||||
"message.type-delete": {
|
||||
"defaultMessage": "在下面空格输入{delete}确认"
|
||||
},
|
||||
"metrics.actions": {
|
||||
"defaultMessage": "用户行为"
|
||||
},
|
||||
"metrics.average-visit-time": {
|
||||
"defaultMessage": "平均访问时间"
|
||||
},
|
||||
"metrics.bounce-rate": {
|
||||
"defaultMessage": "跳出率"
|
||||
},
|
||||
"metrics.browsers": {
|
||||
"defaultMessage": "浏览器"
|
||||
},
|
||||
"metrics.countries": {
|
||||
"defaultMessage": "国家"
|
||||
},
|
||||
"metrics.devices": {
|
||||
"defaultMessage": "设备"
|
||||
},
|
||||
"metrics.events": {
|
||||
"defaultMessage": "行为类别"
|
||||
},
|
||||
"metrics.filter.combined": {
|
||||
"defaultMessage": "总和"
|
||||
},
|
||||
"metrics.filter.domain-only": {
|
||||
"defaultMessage": "只看域名"
|
||||
},
|
||||
"metrics.filter.raw": {
|
||||
"defaultMessage": "原始"
|
||||
},
|
||||
"metrics.operating-system": {
|
||||
"defaultMessage": "操作系统"
|
||||
},
|
||||
"metrics.page-views": {
|
||||
"defaultMessage": "页面流量"
|
||||
},
|
||||
"metrics.pages": {
|
||||
"defaultMessage": "网页"
|
||||
},
|
||||
"metrics.referrers": {
|
||||
"defaultMessage": "指入域名"
|
||||
},
|
||||
"metrics.unique-visitors": {
|
||||
"defaultMessage": "独立访客"
|
||||
},
|
||||
"metrics.views": {
|
||||
"defaultMessage": "页面流量"
|
||||
},
|
||||
"metrics.visitors": {
|
||||
"defaultMessage": "独立访客"
|
||||
},
|
||||
"placeholder.message.go-to-settings": {
|
||||
"defaultMessage": "去设置"
|
||||
},
|
||||
"placeholder.message.no-websites-configured": {
|
||||
"defaultMessage": "你还没有设置任何网站."
|
||||
},
|
||||
"settings.accounts": {
|
||||
"defaultMessage": "账户"
|
||||
},
|
||||
"settings.profile": {
|
||||
"defaultMessage": "个人资料"
|
||||
},
|
||||
"settings.websites": {
|
||||
"defaultMessage": "网站"
|
||||
},
|
||||
"title.add-account": {
|
||||
"defaultMessage": "添加账户"
|
||||
},
|
||||
"title.add-website": {
|
||||
"defaultMessage": "添加网站"
|
||||
},
|
||||
"title.delete-account": {
|
||||
"defaultMessage": "删除账户"
|
||||
},
|
||||
"title.delete-website": {
|
||||
"defaultMessage": "删除网站"
|
||||
},
|
||||
"title.edit-account": {
|
||||
"defaultMessage": "编辑账户"
|
||||
},
|
||||
"title.edit-website": {
|
||||
"defaultMessage": "编辑网站"
|
||||
},
|
||||
"title.share-url": {
|
||||
"defaultMessage": "共享链接"
|
||||
},
|
||||
"title.tracking-code": {
|
||||
"defaultMessage": "跟踪代码"
|
||||
},
|
||||
"tooltip.get-share-url": {
|
||||
"defaultMessage": "获得共享链接"
|
||||
},
|
||||
"tooltip.get-tracking-code": {
|
||||
"defaultMessage": "获得跟踪代码"
|
||||
}
|
||||
}
|
@ -19,6 +19,10 @@ export const POSTGRESQL_DATE_FORMATS = {
|
||||
year: 'YYYY-01-01',
|
||||
};
|
||||
|
||||
export const FILTER_DOMAIN_ONLY = 0;
|
||||
export const FILTER_COMBINED = 1;
|
||||
export const FILTER_RAW = 2;
|
||||
|
||||
export const DOMAIN_REGEX = /((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}/;
|
||||
|
||||
export const DESKTOP_SCREEN_WIDTH = 1920;
|
||||
|
23
lib/lang.js
Normal file
23
lib/lang.js
Normal file
@ -0,0 +1,23 @@
|
||||
import enMessages from 'lang-compiled/en.json';
|
||||
import zhCNMessages from 'lang-compiled/zh-CN.json';
|
||||
import { format } from 'date-fns';
|
||||
import { enUS, zhCN } from 'date-fns/locale';
|
||||
|
||||
export const messages = {
|
||||
en: enMessages,
|
||||
'zh-CN': zhCNMessages,
|
||||
};
|
||||
|
||||
export const dateLocales = {
|
||||
en: enUS,
|
||||
'zh-CN': zhCN,
|
||||
};
|
||||
|
||||
export const menuOptions = [
|
||||
{ label: 'English', value: 'en', display: 'EN' },
|
||||
{ label: '中文 (Chinese Simplified)', value: 'zh-CN', display: '中文' },
|
||||
];
|
||||
|
||||
export function dateFormat(date, str, locale) {
|
||||
return format(date, str, { locale: dateLocales[locale] || enUS });
|
||||
}
|
@ -4,10 +4,15 @@ import { subMinutes } from 'date-fns';
|
||||
import { MYSQL, POSTGRESQL, MYSQL_DATE_FORMATS, POSTGRESQL_DATE_FORMATS } from 'lib/constants';
|
||||
|
||||
export function getDatabase() {
|
||||
return (
|
||||
const type =
|
||||
process.env.DATABASE_TYPE ||
|
||||
(process.env.DATABASE_URL && process.env.DATABASE_URL.split(':')[0])
|
||||
);
|
||||
(process.env.DATABASE_URL && process.env.DATABASE_URL.split(':')[0]);
|
||||
|
||||
if (type === 'postgres') {
|
||||
return 'postgresql';
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
export function getDateQuery(db, field, unit, timezone) {
|
||||
|
11
package.json
11
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "umami",
|
||||
"version": "0.22.0",
|
||||
"version": "0.24.0",
|
||||
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
|
||||
"author": "Mike Cao <mike@mikecao.com>",
|
||||
"license": "MIT",
|
||||
@ -11,7 +11,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "npm-run-all build-tracker copy-db-schema build-db-client build-app",
|
||||
"build": "npm-run-all build-tracker compile-lang copy-db-schema build-db-client build-app",
|
||||
"start": "next start",
|
||||
"build-app": "next build",
|
||||
"build-tracker": "rollup -c rollup.tracker.config.js",
|
||||
@ -21,7 +21,9 @@
|
||||
"build-mysql-schema": "dotenv prisma introspect -- --schema=./prisma/schema.mysql.prisma",
|
||||
"build-mysql-client": "dotenv prisma generate -- --schema=./prisma/schema.mysql.prisma",
|
||||
"build-postgresql-schema": "dotenv prisma introspect -- --schema=./prisma/schema.postgresql.prisma",
|
||||
"build-postgresql-client": "dotenv prisma generate -- --schema=./prisma/schema.postgresql.prisma"
|
||||
"build-postgresql-client": "dotenv prisma generate -- --schema=./prisma/schema.postgresql.prisma",
|
||||
"extract-lang": "formatjs extract {pages,components}/**/*.js --out-file lang/en.json",
|
||||
"compile-lang": "formatjs compile-folder --ast lang lang-compiled"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.js": [
|
||||
@ -61,6 +63,7 @@
|
||||
"promise-polyfill": "^8.1.3",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-intl": "^5.8.0",
|
||||
"react-redux": "^7.2.1",
|
||||
"react-simple-maps": "^2.1.2",
|
||||
"react-spring": "^8.0.27",
|
||||
@ -75,6 +78,7 @@
|
||||
"uuid": "^8.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formatjs/cli": "^2.9.0",
|
||||
"@prisma/cli": "2.6.2",
|
||||
"@rollup/plugin-buble": "^0.21.3",
|
||||
"@rollup/plugin-node-resolve": "^9.0.0",
|
||||
@ -87,6 +91,7 @@
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-react": "^7.20.6",
|
||||
"eslint-plugin-react-hooks": "^4.1.0",
|
||||
"extract-react-intl-messages": "^4.1.1",
|
||||
"husky": "^4.2.5",
|
||||
"lint-staged": "^10.3.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
@ -1,16 +1,41 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import React, { useEffect } from 'react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { Provider, useDispatch, useSelector } from 'react-redux';
|
||||
import { useStore } from 'redux/store';
|
||||
import { updateApp } from 'redux/actions/app';
|
||||
import { messages } from 'lib/lang';
|
||||
import 'styles/variables.css';
|
||||
import 'styles/bootstrap-grid.css';
|
||||
import 'styles/index.css';
|
||||
|
||||
const Intl = ({ children }) => {
|
||||
const dispatch = useDispatch();
|
||||
const locale = useSelector(state => state.app.locale);
|
||||
|
||||
const Wrapper = ({ children }) => <span className={locale}>{children}</span>;
|
||||
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem('locale');
|
||||
if (saved) {
|
||||
dispatch(updateApp({ locale: saved }));
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages[locale]} textComponent={Wrapper}>
|
||||
{children}
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
const store = useStore();
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Component {...pageProps} />
|
||||
<Intl>
|
||||
<Component {...pageProps} />
|
||||
</Intl>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
16
redux/actions/app.js
Normal file
16
redux/actions/app.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const app = createSlice({
|
||||
name: 'app',
|
||||
initialState: { locale: 'en' },
|
||||
reducers: {
|
||||
updateApp(state, action) {
|
||||
state = action.payload;
|
||||
return state;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { updateApp } = app.actions;
|
||||
|
||||
export default app.reducer;
|
@ -1,6 +1,7 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import app from './actions/app';
|
||||
import user from './actions/user';
|
||||
import websites from './actions/websites';
|
||||
import queries from './actions/queries';
|
||||
|
||||
export default combineReducers({ user, websites, queries });
|
||||
export default combineReducers({ app, user, websites, queries });
|
||||
|
@ -2,14 +2,25 @@ require('dotenv').config();
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const databaseType =
|
||||
process.env.DATABASE_TYPE || (process.env.DATABASE_URL && process.env.DATABASE_URL.split(':')[0]);
|
||||
function getDatabase() {
|
||||
const type =
|
||||
process.env.DATABASE_TYPE ||
|
||||
(process.env.DATABASE_URL && process.env.DATABASE_URL.split(':')[0]);
|
||||
|
||||
if (type === 'postgres') {
|
||||
return 'postgresql';
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
const databaseType = getDatabase();
|
||||
|
||||
if (!databaseType || !['mysql', 'postgresql'].includes(databaseType)) {
|
||||
throw new Error('Missing or invalid database');
|
||||
}
|
||||
|
||||
console.log(`Database schema detected: ${databaseType}`);
|
||||
console.log(`Database type detected: ${databaseType}`);
|
||||
|
||||
const src = path.resolve(__dirname, `../prisma/schema.${databaseType}.prisma`);
|
||||
const dest = path.resolve(__dirname, '../prisma/schema.prisma');
|
||||
|
@ -13,6 +13,11 @@ body {
|
||||
background: var(--gray75);
|
||||
}
|
||||
|
||||
.zh-CN {
|
||||
font-family: 'Noto Sans SC', sans-serif !important;
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
|
441
yarn.lock
441
yarn.lock
@ -344,7 +344,7 @@
|
||||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/parser@^7.10.4", "@babel/parser@^7.11.5", "@babel/parser@^7.7.7":
|
||||
"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.5", "@babel/parser@^7.7.7":
|
||||
version "7.11.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037"
|
||||
integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==
|
||||
@ -1025,7 +1025,7 @@
|
||||
globals "^11.1.0"
|
||||
lodash "^4.17.19"
|
||||
|
||||
"@babel/types@7.11.5", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.4.4", "@babel/types@^7.7.4", "@babel/types@^7.9.5":
|
||||
"@babel/types@7.11.5", "@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.3.0", "@babel/types@^7.4.4", "@babel/types@^7.7.4", "@babel/types@^7.9.5":
|
||||
version "7.11.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d"
|
||||
integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==
|
||||
@ -1064,6 +1064,80 @@
|
||||
minimatch "^3.0.4"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@formatjs/cli@^2.9.0":
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-2.9.0.tgz#52cceffb133cd1307650af26e878785aa4370a0c"
|
||||
integrity sha512-HSpu0qrpPGaja+V7bHc61ZyzHfV+OzGVmB2DmamjZSaI0S5o7gs0skY40o4chIVNsYT/X9RD9G77pOIEHDWmUA==
|
||||
dependencies:
|
||||
"@formatjs/ts-transformer" "^2.9.0"
|
||||
"@types/json-stable-stringify" "^1.0.32"
|
||||
"@types/lodash" "^4.14.150"
|
||||
"@types/loud-rejection" "^2.0.0"
|
||||
"@types/node" "14"
|
||||
chalk "^4.0.0"
|
||||
commander "5.1.0"
|
||||
fast-glob "^3.2.4"
|
||||
fs-extra "^9.0.0"
|
||||
intl-messageformat-parser "^6.0.5"
|
||||
json-stable-stringify "^1.0.1"
|
||||
lodash "^4.17.15"
|
||||
loud-rejection "^2.2.0"
|
||||
typescript "^4.0"
|
||||
|
||||
"@formatjs/ecma402-abstract@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.0.tgz#5b03ba4931436070ad926d1b2e89bf07edc5ea5b"
|
||||
integrity sha512-jc1bZHhIE1YI0HnZIZcdlKpF4wle2pkgQpzXHDoyy4bUqzBSvDqktnF26hOkyA04KD4wqd61gkuTvRrHMmroAg==
|
||||
|
||||
"@formatjs/intl-displaynames@^3.3.6":
|
||||
version "3.3.6"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-3.3.6.tgz#2b5c938ea1cd38e859f2d716ea317feccbbd8896"
|
||||
integrity sha512-yrTDL3U0MR10vp17noLI2JuNiHq/Fp1P8/mW/t1gCMOpw38FY4bFTOV68FWxSZwzsy/yETqXHjPUTUbpLtEO/Q==
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract" "^1.2.0"
|
||||
|
||||
"@formatjs/intl-listformat@^4.2.5":
|
||||
version "4.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-4.2.5.tgz#2a39223c5fda3f865d56cea80d7459f5bd9828a8"
|
||||
integrity sha512-mcH/CdRH58ao3caZzIdAA32vZM5woxTszIieRjhY2qHxCorVzBPXFYCGTVCO9rtKVFlkMR/pyzaqH3Y1gNiRmw==
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract" "^1.2.0"
|
||||
|
||||
"@formatjs/intl-numberformat@^5.5.2":
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-numberformat/-/intl-numberformat-5.6.0.tgz#87bd1e56246fba2c7af58f73930cbe379dd0aef8"
|
||||
integrity sha512-MfYcqX1LE2N4P9eVtQXI/L6APlXgjexCj0b7GxJfK+icrwbA0XINSPGTt96kUxO5hf/tDu0MxJXnt9gwMKm/EA==
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract" "^1.2.0"
|
||||
|
||||
"@formatjs/intl-relativetimeformat@^7.2.5":
|
||||
version "7.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-7.2.5.tgz#3101a8262bd7fb329d7bd555135f67a36c5e58df"
|
||||
integrity sha512-KTf0zTP7YbrVAPPJMnZNYRrNvEwuNwqOVNcfz0cQwewjE2ImxPW+03zdRHkwDt92WbRv6T0EDRBpgC2Dxaip6Q==
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract" "^1.2.0"
|
||||
|
||||
"@formatjs/intl@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-1.3.0.tgz#843d79ced6908c2ca25abf65ccee52bc72da6b85"
|
||||
integrity sha512-wjzzA7CALsYDjDOdpmGGsMYUblp9LcPtxdjjdZyd8s4xQ5lZZUWrJxqzInkax89TWeGTprHGYh31qPpYbjsRRQ==
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract" "^1.2.0"
|
||||
"@formatjs/intl-displaynames" "^3.3.6"
|
||||
"@formatjs/intl-listformat" "^4.2.5"
|
||||
"@formatjs/intl-relativetimeformat" "^7.2.5"
|
||||
fast-memoize "^2.5.2"
|
||||
intl-messageformat "^9.3.6"
|
||||
intl-messageformat-parser "^6.0.5"
|
||||
|
||||
"@formatjs/ts-transformer@^2.6.0", "@formatjs/ts-transformer@^2.9.0":
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-2.9.0.tgz#582f8c54bc5888044f3e848163411dabca130aff"
|
||||
integrity sha512-H6skH+McG2OoUL3nc6Eas/5IunM8hk7uDU5Ak/qtNtvsehOH8g030LaFaMxw28BWq2vBipGAPlyyz/KTvO8fPw==
|
||||
dependencies:
|
||||
intl-messageformat-parser "^6.0.5"
|
||||
typescript "^4.0"
|
||||
|
||||
"@next/react-dev-overlay@9.5.3":
|
||||
version "9.5.3"
|
||||
resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-9.5.3.tgz#3275301f08045ecc709e3273031973a1f5e81427"
|
||||
@ -1289,6 +1363,39 @@
|
||||
"@svgr/plugin-svgo" "^5.4.0"
|
||||
loader-utils "^2.0.0"
|
||||
|
||||
"@types/babel__core@^7.1.7":
|
||||
version "7.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d"
|
||||
integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.1.0"
|
||||
"@babel/types" "^7.0.0"
|
||||
"@types/babel__generator" "*"
|
||||
"@types/babel__template" "*"
|
||||
"@types/babel__traverse" "*"
|
||||
|
||||
"@types/babel__generator@*":
|
||||
version "7.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04"
|
||||
integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==
|
||||
dependencies:
|
||||
"@babel/types" "^7.0.0"
|
||||
|
||||
"@types/babel__template@*":
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307"
|
||||
integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.1.0"
|
||||
"@babel/types" "^7.0.0"
|
||||
|
||||
"@types/babel__traverse@*":
|
||||
version "7.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.13.tgz#1874914be974a492e1b4cb00585cabb274e8ba18"
|
||||
integrity sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==
|
||||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/buble@^0.19.2":
|
||||
version "0.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/buble/-/buble-0.19.2.tgz#a4289d20b175b3c206aaad80caabdabe3ecdfdd1"
|
||||
@ -1311,17 +1418,49 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
||||
|
||||
"@types/fs-extra@^9.0.1":
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.1.tgz#91c8fc4c51f6d5dbe44c2ca9ab09310bd00c7918"
|
||||
integrity sha512-B42Sxuaz09MhC3DDeW5kubRcQ5by4iuVQ0cRRWM2lggLzAa/KVom0Aft/208NgMvNQQZ86s5rVcqDdn/SH0/mg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.1":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5":
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
|
||||
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
|
||||
|
||||
"@types/json-stable-stringify@^1.0.32":
|
||||
version "1.0.32"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz#121f6917c4389db3923640b2e68de5fa64dda88e"
|
||||
integrity sha512-q9Q6+eUEGwQkv4Sbst3J4PNgDOvpuVuKj79Hl/qnmBMEIPzB5QoFRUtjcgcg2xNUZyYUGXBk5wYIBKHt0A+Mxw==
|
||||
|
||||
"@types/lodash@^4.14.150":
|
||||
version "4.14.161"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18"
|
||||
integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA==
|
||||
|
||||
"@types/loud-rejection@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/loud-rejection/-/loud-rejection-2.0.0.tgz#271bb21c63f51776e1156604cda3b21a2d3f60f3"
|
||||
integrity sha512-oTHISsIybJGoh3b3Ay/10csbAd2k0su7G7DGrE1QWciC+IdydPm0WMw1+Gr9YMYjPiJ5poB3g5Ev73IlLoavLw==
|
||||
dependencies:
|
||||
loud-rejection "*"
|
||||
|
||||
"@types/minimist@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6"
|
||||
integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
|
||||
|
||||
"@types/node@*":
|
||||
"@types/node@*", "@types/node@14":
|
||||
version "14.6.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.4.tgz#a145cc0bb14ef9c4777361b7bbafa5cf8e3acb5a"
|
||||
integrity sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==
|
||||
@ -1336,11 +1475,24 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
|
||||
|
||||
"@types/q@^1.5.1":
|
||||
version "1.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
|
||||
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
|
||||
|
||||
"@types/react@*":
|
||||
version "16.9.49"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.49.tgz#09db021cf8089aba0cdb12a49f8021a69cce4872"
|
||||
integrity sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/resolve@1.17.1":
|
||||
version "1.17.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
|
||||
@ -1348,6 +1500,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/schema-utils@^2.4.0":
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/schema-utils/-/schema-utils-2.4.0.tgz#9983012045d541dcee053e685a27c9c87c840fcd"
|
||||
integrity sha512-454hrj5gz/FXcUE20ygfEiN4DxZ1sprUo0V1gqIqkNZ/CzoEzAZEll2uxMsuyz6BYjiQan4Aa65xbTemfzW9hQ==
|
||||
dependencies:
|
||||
schema-utils "*"
|
||||
|
||||
"@types/unist@^2.0.0", "@types/unist@^2.0.2":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
|
||||
@ -1755,6 +1914,11 @@ arr-union@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
|
||||
integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
|
||||
|
||||
array-find-index@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
|
||||
integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=
|
||||
|
||||
array-includes@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348"
|
||||
@ -1838,6 +2002,11 @@ async-each@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
|
||||
integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
|
||||
|
||||
at-least-node@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
||||
|
||||
atob@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
@ -1863,6 +2032,22 @@ babel-plugin-dynamic-import-node@^2.3.3:
|
||||
dependencies:
|
||||
object.assign "^4.1.0"
|
||||
|
||||
babel-plugin-react-intl@^7.0.0:
|
||||
version "7.9.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-react-intl/-/babel-plugin-react-intl-7.9.4.tgz#1fc9ab50470d41b934df50d8f436578ee1732cb0"
|
||||
integrity sha512-cMKrHEXrw43yT4M89Wbgq8A8N8lffSquj1Piwov/HVukR7jwOw8gf9btXNsQhT27ccyqEwy+M286JQYy0jby2g==
|
||||
dependencies:
|
||||
"@babel/core" "^7.9.0"
|
||||
"@babel/helper-plugin-utils" "^7.8.3"
|
||||
"@babel/types" "^7.9.5"
|
||||
"@formatjs/ts-transformer" "^2.6.0"
|
||||
"@types/babel__core" "^7.1.7"
|
||||
"@types/fs-extra" "^9.0.1"
|
||||
"@types/schema-utils" "^2.4.0"
|
||||
fs-extra "^9.0.0"
|
||||
intl-messageformat-parser "^5.3.7"
|
||||
schema-utils "^2.6.6"
|
||||
|
||||
babel-plugin-syntax-jsx@6.18.0:
|
||||
version "6.18.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
|
||||
@ -2501,6 +2686,11 @@ commander@2, commander@^2.20.0:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
|
||||
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
|
||||
|
||||
commander@^6.0.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-6.1.0.tgz#f8d722b78103141006b66f4c7ba1e97315ba75bc"
|
||||
@ -2867,6 +3057,18 @@ csso@^4.0.2:
|
||||
dependencies:
|
||||
css-tree "1.0.0-alpha.39"
|
||||
|
||||
csstype@^3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8"
|
||||
integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==
|
||||
|
||||
currently-unhandled@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
|
||||
integrity sha1-mI3zP+qxke95mmE2nddsF635V+o=
|
||||
dependencies:
|
||||
array-find-index "^1.0.1"
|
||||
|
||||
cyclist@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||
@ -3083,6 +3285,11 @@ detect-browser@^5.1.1:
|
||||
resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.1.1.tgz#a800db91d3fd60d0861669f5984f1be9ffbe009c"
|
||||
integrity sha512-5n2aWI57qC3kZaK4j2zYsG6L1LrxgLptGCNhMQgdKhVn6cSdcq43pp6xHPfTHG3TYM6myF4tIPWiZtfdVDgb9w==
|
||||
|
||||
detect-indent@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd"
|
||||
integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==
|
||||
|
||||
detect-libc@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
||||
@ -3707,6 +3914,27 @@ extglob@^2.0.4:
|
||||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
extract-react-intl-messages@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/extract-react-intl-messages/-/extract-react-intl-messages-4.1.1.tgz#cd01d99053bb053ecc8410ccdccb9ac56daae91c"
|
||||
integrity sha512-dPogci5X7HVtV7VbUxajH/1YgfNRaW2VtEiVidZ/31Tq8314uzOtzVMNo0IrAPD2E+H1wHoPiu/j565TZsyIZg==
|
||||
dependencies:
|
||||
"@babel/core" "^7.9.0"
|
||||
babel-plugin-react-intl "^7.0.0"
|
||||
flat "^5.0.0"
|
||||
glob "^7.1.6"
|
||||
js-yaml "^3.13.1"
|
||||
load-json-file "^6.2.0"
|
||||
lodash.merge "^4.6.2"
|
||||
lodash.mergewith "^4.6.2"
|
||||
lodash.pick "^4.4.0"
|
||||
meow "^6.1.0"
|
||||
mkdirp "^1.0.3"
|
||||
pify "^5.0.0"
|
||||
read-babelrc-up "^1.1.0"
|
||||
sort-keys "^4.0.0"
|
||||
write-json-file "^4.3.0"
|
||||
|
||||
fast-deep-equal@^3.1.1:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
@ -3739,6 +3967,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||
|
||||
fast-memoize@^2.5.2:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e"
|
||||
integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==
|
||||
|
||||
fastest-levenshtein@^1.0.12:
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
|
||||
@ -3841,6 +4074,11 @@ flat-cache@^2.0.1:
|
||||
rimraf "2.6.3"
|
||||
write "1.0.3"
|
||||
|
||||
flat@^5.0.0:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
|
||||
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
|
||||
|
||||
flatted@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
|
||||
@ -3905,6 +4143,16 @@ from2@^2.1.0:
|
||||
inherits "^2.0.1"
|
||||
readable-stream "^2.0.0"
|
||||
|
||||
fs-extra@^9.0.0:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc"
|
||||
integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==
|
||||
dependencies:
|
||||
at-least-node "^1.0.0"
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^1.0.0"
|
||||
|
||||
fs-minipass@^1.2.5:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
|
||||
@ -4095,7 +4343,7 @@ gonzales-pe@^4.3.0:
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.2:
|
||||
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
||||
@ -4201,7 +4449,7 @@ hmac-drbg@^1.0.0:
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hoist-non-react-statics@^3.3.0:
|
||||
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
@ -4409,6 +4657,28 @@ internal-slot@^1.0.2:
|
||||
has "^1.0.3"
|
||||
side-channel "^1.0.2"
|
||||
|
||||
intl-messageformat-parser@^5.3.7:
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-5.5.1.tgz#f09a692755813e6220081e3374df3fb1698bd0c6"
|
||||
integrity sha512-TvB3LqF2VtP6yI6HXlRT5TxX98HKha6hCcrg9dwlPwNaedVNuQA9KgBdtWKgiyakyCTYHQ+KJeFEstNKfZr64w==
|
||||
dependencies:
|
||||
"@formatjs/intl-numberformat" "^5.5.2"
|
||||
|
||||
intl-messageformat-parser@^6.0.5:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-6.0.5.tgz#098b052ac2714101b4da06fd45d68199d3abd131"
|
||||
integrity sha512-4aO/RTUtzWiV/naqif4ubwz8P7THOxhraN6XmQpgXj4mdGjtPNO2j3vKlEDgAvv4BEi12R/JCHfLf7SUyfPKog==
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract" "^1.2.0"
|
||||
|
||||
intl-messageformat@^9.3.6:
|
||||
version "9.3.6"
|
||||
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.3.6.tgz#6b15bca5ebbd81808cf703423c34fb789cf1da8e"
|
||||
integrity sha512-ZmaPVtB1i0Ao64sI+kCl+uAqlHGn1KyHHPYw2W/cd4q00ACDBpdeqeD3y4tQnMXMGZriwbSn90dJ+bvSkQr1dA==
|
||||
dependencies:
|
||||
fast-memoize "^2.5.2"
|
||||
intl-messageformat-parser "^6.0.5"
|
||||
|
||||
invariant@^2.2.2, invariant@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
@ -4779,6 +5049,13 @@ json-stable-stringify-without-jsonify@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
|
||||
|
||||
json-stable-stringify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
|
||||
integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=
|
||||
dependencies:
|
||||
jsonify "~0.0.0"
|
||||
|
||||
json5@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
|
||||
@ -4793,6 +5070,20 @@ json5@^2.1.0, json5@^2.1.2:
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
jsonfile@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179"
|
||||
integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==
|
||||
dependencies:
|
||||
universalify "^1.0.0"
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonify@~0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
|
||||
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
|
||||
|
||||
jsx-ast-utils@^2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e"
|
||||
@ -4925,6 +5216,16 @@ load-json-file@^4.0.0:
|
||||
pify "^3.0.0"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
load-json-file@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1"
|
||||
integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==
|
||||
dependencies:
|
||||
graceful-fs "^4.1.15"
|
||||
parse-json "^5.0.0"
|
||||
strip-bom "^4.0.0"
|
||||
type-fest "^0.6.0"
|
||||
|
||||
loader-runner@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
|
||||
@ -4982,11 +5283,21 @@ lodash._reinterpolate@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
|
||||
|
||||
lodash.merge@^4.6.0:
|
||||
lodash.merge@^4.6.0, lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash.mergewith@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
|
||||
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
|
||||
|
||||
lodash.pick@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
|
||||
integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=
|
||||
|
||||
lodash.sortby@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
@ -5054,6 +5365,14 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
loud-rejection@*, loud-rejection@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-2.2.0.tgz#4255eb6e9c74045b0edc021fa7397ab655a8517c"
|
||||
integrity sha512-S0FayMXku80toa5sZ6Ro4C+s+EtFDCsyJNG/AzFMfX3AxD5Si4dZsgzm/kKnbOxHl5Cv8jBlno8+3XYIh2pNjQ==
|
||||
dependencies:
|
||||
currently-unhandled "^0.4.1"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
lru-cache@6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||
@ -5090,7 +5409,7 @@ make-dir@^2.0.0:
|
||||
pify "^4.0.1"
|
||||
semver "^5.6.0"
|
||||
|
||||
make-dir@^3.0.2:
|
||||
make-dir@^3.0.0, make-dir@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
||||
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
|
||||
@ -5195,6 +5514,23 @@ memorystream@^0.3.1:
|
||||
resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
|
||||
integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI=
|
||||
|
||||
meow@^6.1.0:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/meow/-/meow-6.1.1.tgz#1ad64c4b76b2a24dfb2f635fddcadf320d251467"
|
||||
integrity sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==
|
||||
dependencies:
|
||||
"@types/minimist" "^1.2.0"
|
||||
camelcase-keys "^6.2.2"
|
||||
decamelize-keys "^1.1.0"
|
||||
hard-rejection "^2.1.0"
|
||||
minimist-options "^4.0.2"
|
||||
normalize-package-data "^2.5.0"
|
||||
read-pkg-up "^7.0.1"
|
||||
redent "^3.0.0"
|
||||
trim-newlines "^3.0.0"
|
||||
type-fest "^0.13.1"
|
||||
yargs-parser "^18.1.3"
|
||||
|
||||
meow@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/meow/-/meow-7.1.1.tgz#7c01595e3d337fcb0ec4e8eed1666ea95903d306"
|
||||
@ -5293,7 +5629,7 @@ minimatch@^3.0.4:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist-options@4.1.0:
|
||||
minimist-options@4.1.0, minimist-options@^4.0.2:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619"
|
||||
integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==
|
||||
@ -6114,6 +6450,11 @@ pify@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
|
||||
integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
|
||||
|
||||
pify@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f"
|
||||
integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==
|
||||
|
||||
pkg-dir@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
|
||||
@ -6781,6 +7122,23 @@ react-fast-compare@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
||||
|
||||
react-intl@^5.8.0:
|
||||
version "5.8.0"
|
||||
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.8.0.tgz#4d365ee992b35cdb81576abd2fb06e4d78a8e461"
|
||||
integrity sha512-03FHg9u9gW+fc9zyVQS0WwZc3AkIzwRVE73O6FJx10ZCJ5XDDHWzgNCK6H65rX0Hq9+Hw9m7IJiU6YIvV3xLFw==
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract" "^1.2.0"
|
||||
"@formatjs/intl" "^1.3.0"
|
||||
"@formatjs/intl-displaynames" "^3.3.6"
|
||||
"@formatjs/intl-listformat" "^4.2.5"
|
||||
"@formatjs/intl-relativetimeformat" "^7.2.5"
|
||||
"@types/hoist-non-react-statics" "^3.3.1"
|
||||
fast-memoize "^2.5.2"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
intl-messageformat "^9.3.6"
|
||||
intl-messageformat-parser "^6.0.5"
|
||||
shallow-equal "^1.2.1"
|
||||
|
||||
react-is@16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.9.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
@ -6845,6 +7203,14 @@ react@^16.13.1:
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
read-babelrc-up@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/read-babelrc-up/-/read-babelrc-up-1.1.0.tgz#10fd5baaf6ca03eaba6748fa65ddae25bca61e70"
|
||||
integrity sha512-fcl0JeI85Ss3//kfC3z2rsG2VxSiHl1bJgpjQWrne2YuQEewZpAgAjb17A6q/Q3ozWeZsUSroiIBVsnjmOU8vw==
|
||||
dependencies:
|
||||
find-up "^4.1.0"
|
||||
json5 "^2.1.2"
|
||||
|
||||
read-cache@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
|
||||
@ -7318,6 +7684,15 @@ scheduler@^0.19.1:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
schema-utils@*, schema-utils@^2.6.1, schema-utils@^2.6.6:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
|
||||
integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.5"
|
||||
ajv "^6.12.4"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
schema-utils@2.6.6:
|
||||
version "2.6.6"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.6.tgz#299fe6bd4a3365dc23d99fd446caff8f1d6c330c"
|
||||
@ -7335,15 +7710,6 @@ schema-utils@^1.0.0:
|
||||
ajv-errors "^1.0.0"
|
||||
ajv-keywords "^3.1.0"
|
||||
|
||||
schema-utils@^2.6.1, schema-utils@^2.6.6:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
|
||||
integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.5"
|
||||
ajv "^6.12.4"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
semver-compare@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||
@ -7426,6 +7792,11 @@ shallow-clone@^3.0.0:
|
||||
dependencies:
|
||||
kind-of "^6.0.2"
|
||||
|
||||
shallow-equal@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da"
|
||||
integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==
|
||||
|
||||
shebang-command@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||
@ -7530,6 +7901,13 @@ snapdragon@^0.8.1:
|
||||
source-map-resolve "^0.5.0"
|
||||
use "^3.1.0"
|
||||
|
||||
sort-keys@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-4.0.0.tgz#56dc5e256637bfe3fec8db0dc57c08b1a2be22d6"
|
||||
integrity sha512-hlJLzrn/VN49uyNkZ8+9b+0q9DjmmYcYOnbMQtpkLrYpPwRApDPZfmqbUfJnAA3sb/nRib+nDot7Zi/1ER1fuA==
|
||||
dependencies:
|
||||
is-plain-obj "^2.0.0"
|
||||
|
||||
source-list-map@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
|
||||
@ -7857,6 +8235,11 @@ strip-bom@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
|
||||
integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
|
||||
|
||||
strip-bom@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
|
||||
integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==
|
||||
|
||||
strip-final-newline@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
|
||||
@ -8356,6 +8739,11 @@ typescript@^3.9.3:
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
|
||||
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
|
||||
|
||||
typescript@^4.0:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2"
|
||||
integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==
|
||||
|
||||
unfetch@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db"
|
||||
@ -8476,6 +8864,11 @@ unist-util-visit@^2.0.0:
|
||||
unist-util-is "^4.0.0"
|
||||
unist-util-visit-parents "^3.0.0"
|
||||
|
||||
universalify@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d"
|
||||
integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==
|
||||
|
||||
unquote@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"
|
||||
@ -8763,7 +9156,7 @@ wrappy@1:
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
write-file-atomic@^3.0.3:
|
||||
write-file-atomic@^3.0.0, write-file-atomic@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
|
||||
integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
|
||||
@ -8773,6 +9166,18 @@ write-file-atomic@^3.0.3:
|
||||
signal-exit "^3.0.2"
|
||||
typedarray-to-buffer "^3.1.5"
|
||||
|
||||
write-json-file@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-4.3.0.tgz#908493d6fd23225344af324016e4ca8f702dd12d"
|
||||
integrity sha512-PxiShnxf0IlnQuMYOPPhPkhExoCQuTUNPOa/2JWCYTmBquU9njyyDuwRKN26IZBlp4yn1nt+Agh2HOOBl+55HQ==
|
||||
dependencies:
|
||||
detect-indent "^6.0.0"
|
||||
graceful-fs "^4.1.15"
|
||||
is-plain-obj "^2.0.0"
|
||||
make-dir "^3.0.0"
|
||||
sort-keys "^4.0.0"
|
||||
write-file-atomic "^3.0.0"
|
||||
|
||||
write@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
|
||||
|
Loading…
Reference in New Issue
Block a user