Icon.UNKNOWN = makePreset('UnknownIcon', 'square-question');
Icon.UNLOCKED = makePreset('UnlockedIcon', 'lock-open');
Icon.UPLOAD = makePreset('UploadIcon', 'upload');
+Icon.VERIFIED = makePreset('VerifiedIcon', 'circle-check');
Icon.VIDEO = makePreset('VideoIcon', 'video');
-Icon.WARNING = makePreset('WarningIcon', 'triangle-exclamation');
Icon.VOLUME = makePreset('VolumeIcon', 'volume-high');
+Icon.WARNING = makePreset('WarningIcon', 'triangle-exclamation');
Icon.YOUTUBE = makePreset('YoutubeIcon', ['fab', 'youtube']);
export default Icon;
switch (entry.type) {
case 'result.report':
return <Icon.RESULT />;
+ case 'result.verify':
+ return <Icon.VERIFIED />;
case 'round.create':
return <Icon.ADD />;
case 'round.delete':
return <Icon.REMOVE />;
+ case 'round.edit':
+ case 'round.seed':
+ return <Icon.EDIT />;
case 'round.getseed':
return <Icon.DOWNLOAD />;
case 'round.lock':
import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
-import Icon from '../common/Icon';
+import Verification from './Verification';
import Box from '../users/Box';
import { getTime } from '../../helpers/Result';
import { formatNumberAlways } from '../../helpers/Round';
tournament,
user,
}) => {
- const [verifying, setVerifying] = React.useState(false);
-
const { t } = useTranslation();
const { user: authUser } = useUser();
[authUser, result, round, tournament],
);
- const handleVerifyClick = React.useCallback(async () => {
- setVerifying(true);
- try {
- await actions.verifyResult(result);
- } catch (e) {
- console.error(e);
- }
- setVerifying(false);
- }, [actions, result]);
-
return <Modal className="result-dialog" onHide={onHide} show={show}>
<Modal.Header closeButton>
<Modal.Title>
</div>
</Form.Group>
: null}
- {mayVerify && actions?.verifyResult ? (
- <Form.Group as={Col} sm={12}>
- <Form.Label>{t('results.verification')}</Form.Label>
- <div>
- <Button disabled={verifying} onClick={handleVerifyClick}>
- {verifying ?
- <Icon.LOADING className="me-2" />
- : null}
- {t('results.verifyButton')}
- </Button>
- </div>
- </Form.Group>
- ) : null}
{maySee && result && result.comment ?
<Form.Group as={Col} sm={12}>
<Form.Label>{t('results.comment')}</Form.Label>
: null}
</Row>
</Modal.Body>
+ {result?.verified_at || (mayVerify && actions?.verifyResult) ? (
+ <Modal.Body className="verification">
+ <Verification actions={actions} result={result} round={round} tournament={tournament} />
+ </Modal.Body>
+ ) : null}
<Modal.Footer>
<Button onClick={onHide} variant="secondary">
{t('button.close')}
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { Trans, useTranslation } from 'react-i18next';
+
+import Icon from '../common/Icon';
+import Box from '../users/Box';
+import { mayVerifyResult } from '../../helpers/permissions';
+import { useUser } from '../../hooks/user';
+
+const Verification = ({ actions, result, round, tournament }) => {
+ const [verifying, setVerifying] = React.useState(false);
+
+ const { t } = useTranslation();
+ const { user } = useUser();
+
+ const mayVerify = React.useMemo(
+ () => mayVerifyResult(user, tournament, round, result),
+ [user, result, round, tournament],
+ );
+
+ const handleVerifyClick = React.useCallback(async () => {
+ setVerifying(true);
+ try {
+ await actions.verifyResult(result);
+ } catch (e) {
+ console.error(e);
+ }
+ setVerifying(false);
+ }, [actions, result]);
+
+ const date = result?.verified_at ? new Date(result.verified_at) : null;
+
+ if (result?.verified_at) {
+ return <div>
+ {result.verified_by ?
+ <Trans i18nKey="results.verifiedAtBy">
+ {{date}}
+ <Box user={result.verified_by} />
+ </Trans>
+ :
+ t('results.verifiedAt', { date })
+ }
+ </div>;
+ }
+
+ if (!mayVerify) {
+ return <div>
+ {t('results.verificationPending')}
+ </div>;
+ }
+
+ return <div>
+ <Button disabled={verifying} onClick={handleVerifyClick}>
+ {verifying ?
+ <Icon.LOADING className="me-2" />
+ : null}
+ {t('results.verifyButton')}
+ </Button>
+ </div>;
+};
+
+Verification.propTypes = {
+ actions: PropTypes.shape({
+ verifyResult: PropTypes.func,
+ }),
+ result: PropTypes.shape({
+ verified_at: PropTypes.string,
+ verified_by: PropTypes.shape({
+ }),
+ }),
+ round: PropTypes.shape({
+ }),
+ tournament: PropTypes.shape({
+ }),
+};
+
+export default Verification;
TwitchIcon: 'Twitch',
UnknownIcon: 'Unbekannt',
UnlockedIcon: 'Offen',
- YoutubeIcon: 'YouTube',
+ VerifiedIcon: 'Verifiziert',
VideoIcon: 'Video',
+ VolumeIcon: 'Lautstärke',
+ WarningIcon: 'Achtung',
+ YoutubeIcon: 'YouTube',
zelda: {
aga: 'Agahnim',
armos: 'Armos Knights',
score: 'Punkte',
time: 'Zeit: {{ time }}',
verification: 'Verifikation',
+ verificationPending: 'Noch nicht verifiziert',
+ verifiedAt: 'Verifiziert am {{ date, L }}',
+ verifiedAtBy: 'Verifiziert am {{ date, L }} durch <1 />',
verifyButton: 'Ergebnis verifizieren',
verifyError: 'Fehler beim Verifizieren',
verifySuccess: 'Ergebnis verifiziert',
TwitchIcon: 'Twitch',
UnknownIcon: 'Unknown',
UnlockedIcon: 'Unlocked',
- YoutubeIcon: 'YouTube',
+ VerifiedIcon: 'Verified',
VideoIcon: 'Video',
+ VolumeIcon: 'Volume',
+ WarningIcon: 'Warning',
+ YoutubeIcon: 'YouTube',
zelda: {
aga: 'Agahnim',
armos: 'Armos Knights',
score: 'Score',
time: 'Time: {{ time }}',
verification: 'Verification',
+ verificationPending: 'Pending verification',
+ verifiedAt: 'Verified on {{ date, L }}',
+ verifiedAtBy: 'Verified on {{ date, L }} by <1 />',
verifyButton: 'Verify result',
verifyError: 'Error verifying result',
verifySuccess: 'Result verified',
+.result-dialog {
+ .verification {
+ border-top: thin solid $light;
+ }
+}
+
.result-badge {
position: relative;
display: inline-flex;