]> git.localhorst.tv Git - alttp.git/commitdiff
extract result verification interface
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 26 Nov 2025 11:02:52 +0000 (12:02 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 26 Nov 2025 11:02:52 +0000 (12:02 +0100)
resources/js/components/common/Icon.jsx
resources/js/components/protocol/Item.jsx
resources/js/components/results/DetailDialog.jsx
resources/js/components/results/Verification.jsx [new file with mode: 0644]
resources/js/i18n/de.js
resources/js/i18n/en.js
resources/sass/results.scss

index 5ffd927a7e1a652007a16802de9ea308d374031b..c6e0cfaab353b4306ec4d33bd3a3c5228228976f 100644 (file)
@@ -107,9 +107,10 @@ Icon.TWITCH = makePreset('TwitchIcon', ['fab', 'twitch']);
 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;
index 12d247f007bd457a8edd543350d8a0b62c5c4440..b7d60528f95b1b62491cc7407a110003c0c66f52 100644 (file)
@@ -137,10 +137,15 @@ const getEntryIcon = entry => {
        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':
index f3b1c990c5f321c337c7f139a9da4563e71b2fb4..17141fb8425a67c4b3772f8b9aae82cc38797adc 100644 (file)
@@ -3,7 +3,7 @@ import React from 'react';
 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';
@@ -22,8 +22,6 @@ const DetailDialog = ({
        tournament,
        user,
 }) => {
-       const [verifying, setVerifying] = React.useState(false);
-
        const { t } = useTranslation();
        const { user: authUser } = useUser();
 
@@ -40,16 +38,6 @@ const DetailDialog = ({
                [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>
@@ -94,19 +82,6 @@ const DetailDialog = ({
                                                </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>
@@ -115,6 +90,11 @@ const DetailDialog = ({
                                : 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')}
diff --git a/resources/js/components/results/Verification.jsx b/resources/js/components/results/Verification.jsx
new file mode 100644 (file)
index 0000000..281c979
--- /dev/null
@@ -0,0 +1,78 @@
+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;
index 7ccc32e43ead5c0327aebb599b86c1b90afa588f..4ee602bbf22e06f44d2162afca154e1217dee081 100644 (file)
@@ -416,8 +416,11 @@ export default {
                        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',
@@ -625,6 +628,9 @@ export default {
                        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',
index 8349e24d70223ed3b994444f64979d441ba641ee..945b2a1fa0e96240618447919faeb9c384aa963c 100644 (file)
@@ -416,8 +416,11 @@ export default {
                        TwitchIcon: 'Twitch',
                        UnknownIcon: 'Unknown',
                        UnlockedIcon: 'Unlocked',
-                       YoutubeIcon: 'YouTube',
+                       VerifiedIcon: 'Verified',
                        VideoIcon: 'Video',
+                       VolumeIcon: 'Volume',
+                       WarningIcon: 'Warning',
+                       YoutubeIcon: 'YouTube',
                        zelda: {
                                aga: 'Agahnim',
                                armos: 'Armos Knights',
@@ -625,6 +628,9 @@ export default {
                        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',
index 5af982d710fefc5e52b294322d0ae0897499d700..99fa5cf324525063775bbc073065f8c4ffacd5ba 100644 (file)
@@ -1,3 +1,9 @@
+.result-dialog {
+       .verification {
+               border-top: thin solid $light;
+       }
+}
+
 .result-badge {
        position: relative;
        display: inline-flex;