mirror of
https://github.com/umami-software/umami.git
synced 2026-02-09 07:07:17 +01:00
Enable public website sharing.
This commit is contained in:
parent
48a524e09c
commit
560f1316c1
36 changed files with 294 additions and 61 deletions
|
|
@ -4,6 +4,7 @@ import PageHeader from 'components/layout/PageHeader';
|
|||
import Button from 'components/common/Button';
|
||||
import ChangePasswordForm from './forms/ChangePasswordForm';
|
||||
import Modal from 'components/common/Modal';
|
||||
import Dots from 'assets/ellipsis-h.svg';
|
||||
|
||||
export default function ProfileSettings() {
|
||||
const user = useSelector(state => state.user);
|
||||
|
|
@ -14,8 +15,8 @@ export default function ProfileSettings() {
|
|||
<>
|
||||
<PageHeader>
|
||||
<div>Profile</div>
|
||||
<Button size="small" onClick={() => setChangePassword(true)}>
|
||||
Change password
|
||||
<Button icon={<Dots />} size="small" onClick={() => setChangePassword(true)}>
|
||||
<div>Change password</div>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<dl>
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@ import PageHeader from 'components/layout/PageHeader';
|
|||
import Modal from 'components/common/Modal';
|
||||
import WebsiteEditForm from './forms/WebsiteEditForm';
|
||||
import DeleteForm from './forms/DeleteForm';
|
||||
import WebsiteCodeForm from './forms/WebsiteCodeForm';
|
||||
import TrackingCodeForm from './forms/TrackingCodeForm';
|
||||
import ShareUrlForm from './forms/ShareUrlForm';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
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 { get } from 'lib/web';
|
||||
import styles from './WebsiteSettings.module.css';
|
||||
|
||||
|
|
@ -20,13 +22,27 @@ export default function WebsiteSettings() {
|
|||
const [deleteWebsite, setDeleteWebsite] = useState();
|
||||
const [addWebsite, setAddWebsite] = useState();
|
||||
const [showCode, setShowCode] = useState();
|
||||
const [showUrl, setShowUrl] = useState();
|
||||
const [saved, setSaved] = useState(0);
|
||||
|
||||
const Buttons = row => (
|
||||
<>
|
||||
<Button icon={<Code />} size="small" onClick={() => setShowCode(row)}>
|
||||
<div>Get Code</div>
|
||||
</Button>
|
||||
{row.share_id && (
|
||||
<Button
|
||||
icon={<Link />}
|
||||
size="small"
|
||||
tooltip="Share URL"
|
||||
tooltipId={`button-share-${row.website_id}`}
|
||||
onClick={() => setShowUrl(row)}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
icon={<Code />}
|
||||
size="small"
|
||||
tooltip="Get tracking code"
|
||||
tooltipId={`button-code-${row.website_id}`}
|
||||
onClick={() => setShowCode(row)}
|
||||
/>
|
||||
<Button icon={<Pen />} size="small" onClick={() => setEditWebsite(row)}>
|
||||
<div>Edit</div>
|
||||
</Button>
|
||||
|
|
@ -56,6 +72,7 @@ export default function WebsiteSettings() {
|
|||
setEditWebsite(null);
|
||||
setDeleteWebsite(null);
|
||||
setShowCode(null);
|
||||
setShowUrl(null);
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
|
|
@ -108,7 +125,12 @@ export default function WebsiteSettings() {
|
|||
)}
|
||||
{showCode && (
|
||||
<Modal title="Tracking code">
|
||||
<WebsiteCodeForm values={showCode} onClose={handleClose} />
|
||||
<TrackingCodeForm values={showCode} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
{showUrl && (
|
||||
<Modal title="Share URL">
|
||||
<ShareUrlForm values={showUrl} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import classNames from 'classnames';
|
||||
import Icon from './Icon';
|
||||
import styles from './Button.module.css';
|
||||
|
|
@ -10,10 +11,15 @@ export default function Button({
|
|||
variant,
|
||||
children,
|
||||
className,
|
||||
tooltip,
|
||||
tooltipId,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
data-tip={tooltip}
|
||||
data-effect="solid"
|
||||
data-for={tooltipId}
|
||||
type={type}
|
||||
className={classNames(styles.button, className, {
|
||||
[styles.large]: size === 'large',
|
||||
|
|
@ -26,6 +32,7 @@ export default function Button({
|
|||
>
|
||||
{icon && <Icon icon={icon} size={size} />}
|
||||
{children}
|
||||
{tooltip && <ReactTooltip id={tooltipId}>{tooltip}</ReactTooltip>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
outline: none;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
|
|
|
|||
27
components/common/Checkbox.js
Normal file
27
components/common/Checkbox.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import React, { useRef } from 'react';
|
||||
import Icon from 'components/common/Icon';
|
||||
import Check from 'assets/check.svg';
|
||||
import styles from './Checkbox.module.css';
|
||||
|
||||
export default function Checkbox({ name, value, label, onChange }) {
|
||||
const ref = useRef();
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.checkbox} onClick={() => ref.current.click()}>
|
||||
{value && <Icon icon={<Check />} size="small" />}
|
||||
</div>
|
||||
<label className={styles.label} htmlFor={name}>
|
||||
{label}
|
||||
</label>
|
||||
<input
|
||||
ref={ref}
|
||||
className={styles.input}
|
||||
type="checkbox"
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
27
components/common/Checkbox.module.css
Normal file
27
components/common/Checkbox.module.css
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 1px solid var(--gray500);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.input {
|
||||
position: absolute;
|
||||
height: 0;
|
||||
width: 0;
|
||||
bottom: -1px;
|
||||
right: -1px;
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ export default function DeleteForm({ values, onSave, onClose }) {
|
|||
Type <b>DELETE</b> in the box below to confirm.
|
||||
</p>
|
||||
<FormRow>
|
||||
<Field name="confirmation" />
|
||||
<Field name="confirmation" type="text" />
|
||||
<FormError name="confirmation" />
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
|
|
|
|||
30
components/forms/ShareUrlForm.js
Normal file
30
components/forms/ShareUrlForm.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import React, { useRef } from 'react';
|
||||
import Button from 'components/common/Button';
|
||||
import FormLayout, { FormButtons, FormRow } from 'components/layout/FormLayout';
|
||||
import CopyButton from '../common/CopyButton';
|
||||
|
||||
export default function TrackingCodeForm({ values, onClose }) {
|
||||
const ref = useRef();
|
||||
const { name, share_id } = values;
|
||||
|
||||
return (
|
||||
<FormLayout>
|
||||
<p>
|
||||
This is the public URL for <b>{values.name}</b>.
|
||||
</p>
|
||||
<FormRow>
|
||||
<textarea
|
||||
ref={ref}
|
||||
rows={3}
|
||||
cols={60}
|
||||
defaultValue={`${document.location.origin}/share/${share_id}/${name}`}
|
||||
readOnly
|
||||
/>
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<CopyButton type="submit" variant="action" element={ref} />
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
</FormButtons>
|
||||
</FormLayout>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import Button from 'components/common/Button';
|
|||
import FormLayout, { FormButtons, FormRow } from 'components/layout/FormLayout';
|
||||
import CopyButton from '../common/CopyButton';
|
||||
|
||||
export default function WebsiteCodeForm({ values, onClose }) {
|
||||
export default function TrackingCodeForm({ values, onClose }) {
|
||||
const ref = useRef();
|
||||
|
||||
return (
|
||||
|
|
@ -8,10 +8,12 @@ import FormLayout, {
|
|||
FormMessage,
|
||||
FormRow,
|
||||
} from 'components/layout/FormLayout';
|
||||
import Checkbox from '../common/Checkbox';
|
||||
|
||||
const initialValues = {
|
||||
name: '',
|
||||
domain: '',
|
||||
public: false,
|
||||
};
|
||||
|
||||
const validate = ({ name, domain }) => {
|
||||
|
|
@ -43,7 +45,7 @@ export default function WebsiteEditForm({ values, onSave, onClose }) {
|
|||
return (
|
||||
<FormLayout>
|
||||
<Formik
|
||||
initialValues={{ ...initialValues, ...values }}
|
||||
initialValues={{ ...initialValues, ...values, make_public: !!values?.share_id }}
|
||||
validate={validate}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
|
|
@ -59,6 +61,12 @@ export default function WebsiteEditForm({ values, onSave, onClose }) {
|
|||
<Field name="domain" type="text" />
|
||||
<FormError name="domain" />
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<label></label>
|
||||
<Field name="make_public">
|
||||
{({ field }) => <Checkbox {...field} label="Make public" />}
|
||||
</Field>
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<Button type="submit" variant="action">
|
||||
Save
|
||||
|
|
|
|||
|
|
@ -1,11 +1,23 @@
|
|||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import Button from 'components/common/Button';
|
||||
import Logo from 'assets/logo.svg';
|
||||
import styles from './Footer.module.css';
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className={classNames(styles.footer, 'container mt-5 mb-5')}>
|
||||
umami - deliciously simple web stats
|
||||
<footer className="container">
|
||||
<div className={classNames(styles.footer, 'row justify-content-center')}>
|
||||
<div>powered by</div>
|
||||
<Link href="https://umami.is">
|
||||
<a>
|
||||
<Button icon={<Logo />} size="small">
|
||||
<b>umami</b>
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,14 @@
|
|||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-small);
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.footer button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ export default function Header() {
|
|||
const user = useSelector(state => state.user);
|
||||
|
||||
return (
|
||||
<header className={classNames(styles.header, 'container')}>
|
||||
<div className="row align-items-center">
|
||||
<header className="container">
|
||||
<div className={classNames(styles.header, 'row align-items-center')}>
|
||||
<div className="col">
|
||||
<div className={styles.title}>
|
||||
<Icon icon={<Logo />} size="large" className={styles.logo} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue