Skip to content

Commit

Permalink
Enable public website sharing.
Browse files Browse the repository at this point in the history
  • Loading branch information
mikecao committed Aug 15, 2020
1 parent 48a524e commit 560f131
Show file tree
Hide file tree
Showing 36 changed files with 294 additions and 61 deletions.
1 change: 1 addition & 0 deletions assets/ellipsis-h.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/link.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions components/ProfileSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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>
Expand Down
32 changes: 27 additions & 5 deletions components/WebsiteSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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>
Expand Down Expand Up @@ -56,6 +72,7 @@ export default function WebsiteSettings() {
setEditWebsite(null);
setDeleteWebsite(null);
setShowCode(null);
setShowUrl(null);
}

async function loadData() {
Expand Down Expand Up @@ -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>
)}
</>
Expand Down
7 changes: 7 additions & 0 deletions components/common/Button.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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',
Expand All @@ -26,6 +32,7 @@ export default function Button({
>
{icon && <Icon icon={icon} size={size} />}
{children}
{tooltip && <ReactTooltip id={tooltipId}>{tooltip}</ReactTooltip>}
</button>
);
}
1 change: 1 addition & 0 deletions components/common/Button.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
outline: none;
cursor: pointer;
white-space: nowrap;
position: relative;
}

.button:hover {
Expand Down
27 changes: 27 additions & 0 deletions components/common/Checkbox.js
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions components/common/Checkbox.module.css
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 1 addition & 1 deletion components/forms/DeleteForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
30 changes: 30 additions & 0 deletions components/forms/ShareUrlForm.js
Original file line number Diff line number Diff line change
@@ -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>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
10 changes: 9 additions & 1 deletion components/forms/WebsiteEditForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down Expand Up @@ -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}
>
Expand All @@ -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
Expand Down
16 changes: 14 additions & 2 deletions components/layout/Footer.js
Original file line number Diff line number Diff line change
@@ -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>
);
}
11 changes: 11 additions & 0 deletions components/layout/Footer.module.css
Original file line number Diff line number Diff line change
@@ -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;
}
4 changes: 2 additions & 2 deletions components/layout/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -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} />
Expand Down
9 changes: 9 additions & 0 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { JWT, JWE, JWK } from 'jose';

const SALT_ROUNDS = 10;
const KEY = JWK.asKey(Buffer.from(secret()));
const CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

export function hash(...args) {
return crypto.createHash('sha512').update(args.join('')).digest('hex');
Expand All @@ -24,6 +25,14 @@ export function isValidId(s) {
return validate(s);
}

export function getRandomChars(n) {
let s = '';
for (let i = 0; i < n; i++) {
s += CHARS[Math.floor(Math.random() * CHARS.length)];
}
return s;
}

export async function hashPassword(password) {
return bcrypt.hash(password, SALT_ROUNDS);
}
Expand Down
10 changes: 10 additions & 0 deletions lib/queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ export async function getWebsiteByUuid(website_uuid) {
);
}

export async function getWebsiteByShareId(share_id) {
return runQuery(
prisma.website.findOne({
where: {
share_id,
},
}),
);
}

export async function getUserWebsites(user_id) {
return runQuery(
prisma.website.findMany({
Expand Down
Loading

0 comments on commit 560f131

Please sign in to comment.