Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the validator actually validate #3584

Merged
merged 1 commit into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 10 additions & 2 deletions app/actions/UserActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,15 @@ export function updatePicture({
);
};
}
export function fetchUser(username = 'me'): Thunk<any> {

const defaultOptions = {
propagateError: true,
};

export function fetchUser(
username = 'me',
{ propagateError } = defaultOptions
): Thunk<any> {
return callAPI({
types: User.FETCH,
endpoint: `/users/${username}/`,
Expand All @@ -276,7 +284,7 @@ export function fetchUser(username = 'me'): Thunk<any> {
errorMessage: 'Henting av bruker feilet',
isCurrentUser: username === 'me',
},
propagateError: true,
propagateError,
});
}
export function refreshToken(token: EncodedToken): Thunk<any> {
Expand Down
12 changes: 7 additions & 5 deletions app/components/Search/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@ import { Keyboard } from 'app/utils/constants';
import type { Location } from 'history';
import type { ChangeEventHandler, KeyboardEvent } from 'react';

type Props = {
type Props<T> = {
searching: boolean;
location: Location;
inputRef?: {
current: HTMLInputElement | null | undefined;
};
onQueryChanged: (arg0: string) => void;
placeholder?: string;
results: Array<SearchResult>;
handleSelect: (arg0: SearchResult) => Promise<void>;
results: Array<T>;
handleSelect: (arg0: T) => Promise<void>;
};

const SearchPage = (props: Props) => {
const SearchPage = <SearchType extends SearchResult>(
props: Props<SearchType>
) => {
const [selectedIndex, setSelectedIndex] = useState<number>(0);
const [query, setQuery] = useState<unknown>(
qs.parse(props.location.search, {
Expand Down Expand Up @@ -59,7 +61,7 @@ const SearchPage = (props: Props) => {
}
};

const handleSelect = (result: SearchResult) => {
const handleSelect = (result: SearchType) => {
setQuery('');
setSelectedIndex(0);
props.handleSelect(result);
Expand Down
58 changes: 27 additions & 31 deletions app/components/UserValidator/index.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,58 @@
import cx from 'classnames';
import { get } from 'lodash';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { QrReader } from 'react-qr-reader';
import goodSound from 'app/assets/good-sound.mp3';
import Button from 'app/components/Button';
import Icon from 'app/components/Icon';
import Modal from 'app/components/Modal';
import SearchPage from 'app/components/Search/SearchPage';
import type { SearchResult } from 'app/reducers/search';
import type { User } from 'app/models';
import type { UserSearchResult } from 'app/reducers/search';
import styles from './Validator.css';
import type { ComponentProps } from 'react';
import type { Required } from 'utility-types';

type UserWithUsername = Required<Partial<UserSearchResult>, 'username'>;

type Props = {
clearSearch: () => void;
handleSelect: (arg0: SearchResult) => Promise<void>;
location: Record<string, any>;
handleSelect: (arg0: UserWithUsername) => Promise<User>;
onQueryChanged: (arg0: string) => void;
results: Array<SearchResult>;
results: Array<UserSearchResult>;
searching: boolean;
};
} & ComponentProps<typeof SearchPage<UserSearchResult>>;

const Validator = (props: Props) => {
const { clearSearch, handleSelect } = props;
const input = useRef<HTMLInputElement | null | undefined>(null);
const [completed, setCompleted] = useState(false);
const [showScanner, setShowScanner] = useState(false);
const [scannerResult, setScannerResult] = useState('');

const showCompleted = () => {
setCompleted(true);
setTimeout(() => setCompleted(false), 2000);
};

const onSelect = useCallback(
(result: SearchResult) => {
(result: UserWithUsername) => {
clearSearch();
return handleSelect(result)
.then(
() => {
const sound = new window.Audio(goodSound);
sound.play();
showCompleted();
(user: User) => {
if (user.isAbakusMember) {
const sound = new window.Audio(goodSound);
sound.play();
showCompleted();
} else {
alert('Brukeren er ikke medlem av Abakus!');
}
},
(err) => {
const payload = get(err, 'payload.response.jsonData');

if (payload && payload.errorCode === 'not_registered') {
alert('Bruker er ikke påmeldt på eventet!');
} else if (payload && payload.errorCode === 'already_present') {
alert(payload.error);
if (payload && payload.detail === 'Not found.') {
alert(`Brukeren finnes ikke!\nBrukernavn: ${result.username}`);
} else {
alert(
`Det oppsto en uventet feil: ${JSON.stringify(payload || err)}`
Expand All @@ -63,22 +68,15 @@ const Validator = (props: Props) => {
},
[clearSearch, handleSelect]
);
useEffect(() => {

const handleScannerResult = (scannerResult: string) => {
if (scannerResult.length > 0 && !completed) {
onSelect({
username: scannerResult,
result: '',
color: '',
content: '',
icon: '',
label: '',
link: '',
path: '',
picture: '',
value: '',
});
}
}, [completed, onSelect, scannerResult]);
};

return (
<>
<div
Expand All @@ -103,14 +101,12 @@ const Validator = (props: Props) => {
<QrReader
onResult={(res, error) => {
if (res) {
setScannerResult(res.getText());
handleScannerResult(res.getText());
}

if (error) {
console.info(error);
}

setScannerResult('');
}}
constraints={{
facingMode: 'environment',
Expand All @@ -124,7 +120,7 @@ const Validator = (props: Props) => {
<Icon className={styles.qrIcon} name="qr-code" size={18} />
Vis scanner
</Button>
<SearchPage
<SearchPage<UserSearchResult>
{...props}
placeholder="Skriv inn brukernavn eller navn"
handleSelect={onSelect}
Expand Down
5 changes: 5 additions & 0 deletions app/declaration.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ declare module '*.jpg' {
const value: string;
export default value;
}

declare module '*.mp3' {
const value: string;
export default value;
}
1 change: 1 addition & 0 deletions app/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export type User = {
memberships?: UserMembership[];
abakusEmailLists?: EmailList[];
permissionsPerGroup?: PermissionPerGroup[];
isAbakusMember?: boolean;
};

export type Penalty = {
Expand Down
3 changes: 2 additions & 1 deletion app/reducers/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ type SearchResultBase = {
profilePicture?: string;
};

type UserSearchResult = SearchResultBase & {
export type UserSearchResult = SearchResultBase & {
username: string;
profilePicture: string;
type: 'Bruker';
isAbakusMember: boolean;
};

export type SearchResult = SearchResultBase | UserSearchResult;
Expand Down
29 changes: 22 additions & 7 deletions app/routes/userValidator/ValidatorRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import qs from 'qs';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { autocomplete } from 'app/actions/SearchActions';
import { fetchUser } from 'app/actions/UserActions';
import { Content } from 'app/components/Content';
import Validator from 'app/components/UserValidator';
import type { User } from 'app/models';
import { selectAutocompleteRedux as selectAutocomplete } from 'app/reducers/search';
import type { UserSearchResult } from 'app/reducers/search';
import withPreparedDispatch from 'app/utils/withPreparedDispatch';
import type { ComponentProps } from 'react';

const searchTypes = ['users.user'];

const loadData = async (props, dispatch): any => {
const loadData = async (props, dispatch): Promise<void> => {
const query = qs.parse(props.location.search, {
ignoreQueryPrefix: true,
}).q;
Expand All @@ -34,13 +38,24 @@ const mapStateToProps = (state, props) => {
};
};

const mapDispatchToProps = (dispatch, { eventId }) => {
const url = `/validator?q=`;
const mapDispatchToProps = (dispatch, { location }) => {
const search = qs.parse(location.search, {
ignoreQueryPrefix: true,
});

return {
clearSearch: () => dispatch(push(url)),
handleSelect: () => Promise.resolve(),
clearSearch: () =>
dispatch(push(`/validator?${qs.stringify({ ...search, q: '' })}`)),

handleSelect: async (result: UserSearchResult): Promise<User> => {
const fetchRes = await dispatch(
fetchUser(result.username, { propagateError: false })
);

return Object.values(fetchRes.payload?.entities?.users)[0] as User;
},
onQueryChanged: debounce((query) => {
dispatch(push(url + query));
dispatch(push(`/validator?${qs.stringify({ ...search, q: query })}`));

if (query) {
dispatch(autocomplete(query, searchTypes));
Expand All @@ -49,7 +64,7 @@ const mapDispatchToProps = (dispatch, { eventId }) => {
};
};

const WrappedValidator = (props) => (
const WrappedValidator = (props: ComponentProps<typeof Validator>) => (
<Content>
<Validator {...props} />
</Content>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"test:coverage": "yarn run test -- --coverage",
"test:watch": "yarn run test --watch",
"lint": "yarn run lint:js && yarn run lint:css && yarn run lint:prettier",
"lint:js": "eslint . --ignore-path .prettierignore --max-warnings 1119",
"lint:js": "eslint . --ignore-path .prettierignore --max-warnings 1075",
"lint:css": "stylelint './app/**/*.css'",
"lint:prettier": "prettier '**/*.{ts,tsx,js,css,md,json}' --check",
"prettier": "prettier '**/*.{ts,tsx,js,css,md,json}' --write",
Expand Down