Skip to content

Commit

Permalink
Adding interface on EditUser page to handle users' calendar tokens.
Browse files Browse the repository at this point in the history
  • Loading branch information
krulis-martin committed Aug 3, 2022
1 parent 19ad1fe commit 6768475
Show file tree
Hide file tree
Showing 9 changed files with 365 additions and 29 deletions.
193 changes: 193 additions & 0 deletions src/components/Users/CalendarTokens/CalendarTokens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Table, OverlayTrigger, Tooltip } from 'react-bootstrap';
import { CopyToClipboard } from 'react-copy-to-clipboard';

import Button from '../../widgets/TheButton';
import DateTime from '../../widgets/DateTime';
import InsetPanel from '../../widgets/InsetPanel';
import Icon, { CopyIcon, InfoIcon, LoadingIcon, WarningIcon, RefreshIcon } from '../../icons';

import { API_BASE } from '../../../helpers/config';

class CalendarTokens extends Component {
state = { createPending: false, copiedCalendar: null };

createButtonHandler = () => {
const { create, calendars, setExpired } = this.props;
const activeCalendars = calendars.some(calendar => calendar && !calendar.expiredAt);
this.setState({ createPending: true });

if (activeCalendars && setExpired) {
return Promise.all(calendars.filter(c => c && !c.expiredAt).map(c => setExpired(c.id)))
.then(create)
.then(() => this.setState({ createPending: false }));
} else {
return create().then(() => this.setState({ createPending: false }));
}
};

calendarCopied = copiedCalendar => {
this.setState({ copiedCalendar });
if (this.resetCalendarTimer) {
clearTimeout(this.resetCalendarTimer);
}
this.resetCalendarTimer = setTimeout(() => {
this.setState({ copiedCalendar: null });
this.resetCalendarTimer = undefined;
}, 2000);
};

componentWillUnmount() {
if (this.resetCalendarTimer) {
clearTimeout(this.resetCalendarTimer);
this.resetCalendarTimer = undefined;
}
}

render() {
const { calendars = null, create = null, setExpired = null, reload = null } = this.props;
const baseUrl = `${API_BASE}/users/ical/`;
const activeCalendars = calendars.some(calendar => calendar && !calendar.expiredAt);

return (
<>
<p className="mt-4 mx-4 mb-2">
<InfoIcon gapRight className="text-muted" />
<FormattedMessage
id="app.calendarTokens.explain"
defaultMessage="ReCodEx API can feed iCal data to your calendar. It will export deadline events for all assignments in all groups related to you. Anyone with the iCal identifier will be able to read the calendar and the calendar is read-only. When activated, you will get a calendar URL in the following format."
/>
</p>

<InsetPanel className="mt-2 mx-3 mb-3" size="small">
<code>
{baseUrl}
<em>&lt;identifier&gt;</em>
</code>
</InsetPanel>

{calendars && calendars.length > 0 && (
<Table hover size="sm" className="mb-0">
<thead>
<tr>
<th className="px-3">
<FormattedMessage id="app.calendarTokens.id" defaultMessage="Existing iCal identifiers" />
</th>
<th className="text-nowrap px-3">
<FormattedMessage id="generic.createdAt" defaultMessage="Created at" />
</th>
<th className="text-nowrap px-3">
<FormattedMessage id="app.calendarTokens.expiredAt" defaultMessage="Expired at" />
</th>
<th />
</tr>
</thead>

<tbody>
{calendars.map((calendar, idx) =>
calendar ? (
<tr key={calendar.id} className={calendar.expiredAt ? 'text-muted' : ''}>
<td className="full-width px-3">
<code className={calendar.expiredAt ? 'text-muted' : ''}>{calendar.id}</code>
{!calendar.expiredAt &&
(this.state.copiedCalendar === calendar.id ? (
<Icon icon="clipboard-check" gapLeft className="text-success" />
) : (
<OverlayTrigger
placement="right"
overlay={
<Tooltip id={calendar.id}>
<FormattedMessage
id="app.calendarTokens.copyToClipboard"
defaultMessage="Copy the URL into clipboard"
/>
</Tooltip>
}>
<CopyToClipboard
text={`${baseUrl}${calendar.id}`}
onCopy={() => this.calendarCopied(calendar.id)}>
<CopyIcon timid gapLeft className="clickable" />
</CopyToClipboard>
</OverlayTrigger>
))}
</td>

<td className="text-nowrap px-3">
<DateTime unixts={calendar.createdAt} />
</td>

<td className="text-nowrap px-3">
<DateTime unixts={calendar.expiredAt} />
</td>

<td className="text-nowrap px-3">
{!calendar.expiredAt && setExpired && calendar.expiring !== false && (
<Button
variant="danger"
size="xs"
onClick={() => setExpired(calendar.id)}
disabled={calendar.expiring || this.state.createPending}>
<Icon icon={['far', 'calendar-xmark']} gapRight />
<FormattedMessage id="app.calendarTokens.setExpired" defaultMessage="Set expired" />
{calendar.expiring && <LoadingIcon gapLeft />}
</Button>
)}
{calendar.expiring === false && (
<span>
<WarningIcon className="text-danger" gapRight />
<FormattedMessage
id="app.calendarTokens.setExpiredFailed"
defaultMessage="operation failed"
/>
{reload && !this.state.createPending && (
<RefreshIcon gapLeft className="text-primary" onClick={reload} />
)}
</span>
)}
</td>
</tr>
) : (
<tr key={`loading-${idx}`}>
<td colSpan={4} className="text-center">
<LoadingIcon />
</td>
</tr>
)
)}
</tbody>
</Table>
)}

<hr className="m-0" />

{create && (
<div className="text-center my-3">
<Button
variant={activeCalendars ? 'warning' : 'success'}
onClick={this.createButtonHandler}
disabled={this.state.createPending}>
<Icon icon={['far', 'calendar-plus']} gapRight />
{activeCalendars ? (
<FormattedMessage id="app.calendarTokens.refresh" defaultMessage="Expire old and create a new one" />
) : (
<FormattedMessage id="app.calendarTokens.activate" defaultMessage="Activate calendar" />
)}
{this.state.createPending && <LoadingIcon gapLeft />}
</Button>
</div>
)}
</>
);
}
}

CalendarTokens.propTypes = {
calendars: PropTypes.array,
create: PropTypes.func,
setExpired: PropTypes.func,
reload: PropTypes.func,
};

export default CalendarTokens;
2 changes: 2 additions & 0 deletions src/components/Users/CalendarTokens/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import CalendarTokens from './CalendarTokens';
export default CalendarTokens;
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class HeaderNotification extends Component {
</span>
<span className="fa">
<span className={styles.copy} ref={copy => (this.copy = copy)}>
<CopyToClipboard text={msg} onCopy={() => this.onCopy()}>
<CopyToClipboard text={msg} onCopy={this.onCopy}>
<CopyIcon gapRight fixedWidth />
</CopyToClipboard>
</span>
Expand Down
9 changes: 9 additions & 0 deletions src/locales/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@
"app.broker.stats": "Aktuální statistiky",
"app.broker.title": "ZeroMQ Broker",
"app.broker.unfreeze": "Rozmrazit",
"app.calendarTokens.activate": "Aktivovat kalendář",
"app.calendarTokens.copyToClipboard": "Zkopírovat URL do schránky",
"app.calendarTokens.expiredAt": "Konec platnosti",
"app.calendarTokens.explain": "API ReCodExu může exportovat data pro váš iCal kalendář. Exportovány budou koncové termíny všech zadaných úloh ve všech vašich skupinách. Ke kalendářovým datům bude mít přístup každý, kdo zná platný iCal identifikátor a tato data jsou pouze pro čtení. Pokud aktivujete kalendář, dostanete URL v následujícím formátu.",
"app.calendarTokens.id": "Existující iCal identifikátory",
"app.calendarTokens.refresh": "Zneplatnit starý a vytvořit nový kalendář",
"app.calendarTokens.setExpired": "Zneplatnit",
"app.calendarTokens.setExpiredFailed": "operace selhala",
"app.changePassword.description": "Zapomenuté heslo lze změnit v tomto formuláři",
"app.changePassword.requestAnotherLink": "Prosíme zažádejte o (další) odkaz s unikátním tokenem.",
"app.changePassword.title": "Změna zapomenutého hesla",
Expand Down Expand Up @@ -567,6 +575,7 @@
"app.editTestsTest.weight": "Váha testu:",
"app.editUser.emailStillNotVerified": "Vaše e-mailová adresa dosud nebyla ověřena. ReCodEx se potřebuje spolehnout na platnost adres, protože řada notifikací je zasílána e-mailem. Pomocí tlačítka níže si můžete nechat opětovně zaslat ověřovací e-mail. Ten obsahuje odkaz, který potvrzuje platnost adresy. Prosíme, ověřte vaši adresu co nejdříve.",
"app.editUser.emailStillNotVerifiedTitle": "E-mailová adresa není ověřena",
"app.editUser.icalTitle": "Export termínů úloh do iCal formátu",
"app.editUser.isEmailAlreadyVefiried": "Pokud jste právě ověřili vaši adresu a stále vidíte tuto hlášku, prosím občerstvěte stránku.",
"app.editUser.makeLocal": "Vytvořit lokální účet",
"app.editUser.title": "Upravit uživatelský profil",
Expand Down
9 changes: 9 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@
"app.broker.stats": "Current Statistics",
"app.broker.title": "ZeroMQ Broker",
"app.broker.unfreeze": "Unfreeze",
"app.calendarTokens.activate": "Activate calendar",
"app.calendarTokens.copyToClipboard": "Copy the URL into clipboard",
"app.calendarTokens.expiredAt": "Expired at",
"app.calendarTokens.explain": "ReCodEx API can feed iCal data to your calendar. It will export deadline events for all assignments in all groups related to you. Anyone with the iCal identifier will be able to read the calendar and the calendar is read-only. When activated, you will get a calendar URL in the following format.",
"app.calendarTokens.id": "Existing iCal identifiers",
"app.calendarTokens.refresh": "Expire old and create a new one",
"app.calendarTokens.setExpired": "Set expired",
"app.calendarTokens.setExpiredFailed": "operation failed",
"app.changePassword.description": "You can change your forgotten password in this form",
"app.changePassword.requestAnotherLink": "Please request (another) link with an unique token.",
"app.changePassword.title": "Change Forgotten Password",
Expand Down Expand Up @@ -567,6 +575,7 @@
"app.editTestsTest.weight": "Test weight:",
"app.editUser.emailStillNotVerified": "Your email addres has not been verified yet. ReCodEx needs to rely on vaild addresses since many notifications are sent via email. You may send yourself a validation email using the button below and then use a link from that email to verify its acceptance. Please validate your address as soon as possible.",
"app.editUser.emailStillNotVerifiedTitle": "Email Address Is Not Verified",
"app.editUser.icalTitle": "Deadlines Export to iCal",
"app.editUser.isEmailAlreadyVefiried": "If you have just verified your email and still see the message, please refresh the page.",
"app.editUser.makeLocal": "Create local account",
"app.editUser.title": "Edit User's Profile",
Expand Down
Loading

0 comments on commit 6768475

Please sign in to comment.