Skip to content

Commit ea993a0

Browse files
committed
wip geolocation validation
1 parent c3be9dc commit ea993a0

File tree

12 files changed

+153
-7
lines changed

12 files changed

+153
-7
lines changed

apps/app/src/components/RegistrationForms/FirstStepRegistrationForm/FirstStepRegistrationForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
allFieldsAreFilled,
77
validateField,
88
validateRepeatedPassword,
9-
} from '@/helpers/validateInput';
9+
} from '@/validation/validateInput';
1010
import { UserDataFields } from '@/types/enums';
1111
import { useRegistration } from '@/providers/RegistrationContext';
1212
import { CheckboxFieldsWrapper } from '../CheckboxFieldsWrapper';

apps/app/src/components/RegistrationForms/SecondStepRegistrationForm/SecondStepRegistrationForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
allFieldsAreFilled,
88
validateField,
99
validations,
10-
} from '@/helpers/validateInput';
10+
} from '@/validation/validateInput';
1111
import { RegistrationFormErrors } from '@/types/errors/errors.dto';
1212
import { UserDataFields, UserProfileFields } from '@/types/enums';
1313
import { useRegistration } from '@/providers/RegistrationContext';
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { useCallback, useEffect, useMemo, useState } from 'react';
2+
import { validateWithinRadius } from '@/validation/validateGeolocation';
3+
4+
type GeoResult = {
5+
ok: boolean;
6+
distanceMeters: number;
7+
accuracyMeters: number | null;
8+
};
9+
10+
export function useGeoValidation(options?: {
11+
accuracyLimitMeters?: number;
12+
geolocationOptions?: PositionOptions;
13+
}) {
14+
const accuracyLimit = options?.accuracyLimitMeters ?? 250;
15+
// Current - new object every render
16+
17+
const geoOptions = useMemo(
18+
() =>
19+
options?.geolocationOptions ?? {
20+
enableHighAccuracy: true,
21+
timeout: 15000,
22+
maximumAge: 0,
23+
},
24+
[options?.geolocationOptions],
25+
);
26+
27+
const [isOk, setIsOk] = useState<boolean | null>(null);
28+
const [error, setError] = useState<string | null>(null);
29+
const [result, setResult] = useState<GeoResult | null>(null);
30+
31+
const validate = useCallback(() => {
32+
setError(null);
33+
34+
if (typeof window === 'undefined' || !('geolocation' in navigator)) {
35+
setIsOk(false);
36+
setError('Geolocation is not available in this environment.');
37+
return;
38+
}
39+
40+
navigator.geolocation.getCurrentPosition(
41+
(pos) => {
42+
const accuracy = pos.coords.accuracy ?? null;
43+
44+
// Reject low-accuracy fixes first
45+
if (accuracy == null || accuracy > accuracyLimit) {
46+
const msg = `Location accuracy too low (${accuracy ? Math.round(accuracy) : '?'}m).`;
47+
setIsOk(false);
48+
setError(msg);
49+
setResult(
50+
accuracy == null
51+
? null
52+
: { ok: false, distanceMeters: NaN, accuracyMeters: accuracy },
53+
);
54+
return;
55+
}
56+
57+
const r = validateWithinRadius(pos.coords);
58+
59+
const payload: GeoResult = {
60+
ok: r.ok,
61+
distanceMeters: r.distanceMeters,
62+
accuracyMeters: accuracy,
63+
};
64+
65+
setResult(payload);
66+
setIsOk(r.ok);
67+
},
68+
(err) => {
69+
setIsOk(false);
70+
setError(err.message);
71+
},
72+
geoOptions,
73+
);
74+
}, [accuracyLimit, geoOptions]);
75+
76+
// Run validation once on mount
77+
useEffect(() => {
78+
validate();
79+
}, [validate]);
80+
81+
return { validate, isOk, error, result };
82+
}

apps/app/src/hooks/useInputHandlers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ChangeEvent } from 'react';
22
import { DropdownOption } from '../components/Dropdown/DropdownOption';
33
import { UserDataFields, UserProfileFields } from '../types/enums';
4-
import { validations } from '@/helpers/validateInput';
4+
import { validations } from '@/validation/validateInput';
55

66
export const useInputHandlers = (
77
updateState: (data: Record<string, unknown>) => void,

apps/app/src/pages/FlyTalksApplyPage/FlyTalksApplyPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useLocation } from 'react-router-dom';
55
import { Input } from '../../components/Input';
66
import Button from '../../components/Button';
77
import FileInput from '../../components/FileInput';
8-
import { validateFlyTalksInput } from '@/helpers/validateInput';
8+
import { validateFlyTalksInput } from '@/validation/validateInput';
99
import ConfirmationPopup from './ConfirmationPopup';
1010
import { useGetAllFlyTalkGroups } from '@/api/flyTalks/useGetGroupCompanies';
1111
import { usePostApplyToFlyTalks } from '@/api/flyTalks/usePostApplyToFlyTalks';

apps/app/src/pages/PasswordResetPage/PasswordResetPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { SuccessStep } from './steps/SuccessStep';
1010
import {
1111
validations,
1212
validateRepeatedPassword,
13-
} from '../../helpers/validateInput';
13+
} from '../../validation/validateInput';
1414
import { useNavigate, useParams } from 'react-router-dom';
1515
import { useResetPassword } from '../../api/user/useResetPassword';
1616
import { sendVerificationEmail } from '../../helpers/handleVerificationSent';

apps/app/src/pages/RateCompanyPage/RateCompanyPage.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useRatingAddMultiple } from '@/api/rating/useRatingAddMultiple';
1111
import toast from 'react-hot-toast';
1212
import { useGetUserRatings } from '@/api/rating/useGetUserRatings';
1313
import { useCompanyGetById } from '@/api/company/useGetCompanyById';
14+
import { useGeoValidation } from '@/hooks/useGeoValidation';
1415

1516
export const RateCompanyPage = () => {
1617
const navigate = useNavigate();
@@ -26,6 +27,7 @@ export const RateCompanyPage = () => {
2627
const { mutate: addRatings } = useRatingAddMultiple();
2728
const [answers, setAnswers] = useState<Record<number, number | null>>({});
2829
const { data: userRatings } = useGetUserRatings();
30+
const geolocation = useGeoValidation();
2931

3032
useEffect(() => {
3133
if (!questions) return;
@@ -51,6 +53,14 @@ export const RateCompanyPage = () => {
5153
toast.dismiss(toastId);
5254
}
5355

56+
if (geolocation.isOk === false) {
57+
toast.error(
58+
`Ne možete ocijeniti štand izvan dozvoljene lokacije ili je preciznost GPS-a preniska.`,
59+
);
60+
navigate(RouteNames.HOME);
61+
return;
62+
}
63+
5464
if (isCompanyError) {
5565
toast.error('Greška prilikom učitavanja kompanije.', {
5666
position: 'top-center',

apps/app/src/pages/ScannerPage/ScannerPage.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import AppliedCodeAchievementPopup from '@/components/AppliedCodeAchievementPopu
1313
import { useLoggedInUser } from '@/api/auth/useLoggedInUser';
1414
import ScannedAchievementPopup from './popups/ScannedAchievementPopup';
1515
import ScannedCodePopup from './popups/ScannedCodePopup';
16+
import { useGeoValidation } from '@/hooks/useGeoValidation';
1617

1718
const ScannerPage = () => {
1819
const [scannedAchievement, setScannedAchievement] = useState('');
@@ -25,6 +26,7 @@ const ScannerPage = () => {
2526

2627
const { data: achievement } = useAchievementGetByUuid(scannedAchievement);
2728
const { data: completedAchievements } = useAchievementGetCompleted();
29+
const geolocation = useGeoValidation();
2830

2931
const { data: appliedCodes } = useCodeGetApplied();
3032
const { data: user } = useLoggedInUser();
@@ -39,6 +41,13 @@ const ScannerPage = () => {
3941
return;
4042
}
4143

44+
if (geolocation.isOk === false) {
45+
toast.error(
46+
`Ne možete skenirati QR kod izvan dozvoljene lokacije ili je preciznost GPS-a preniska. ${geolocation.error ?? 'neki error'}`,
47+
);
48+
return;
49+
}
50+
4251
lastScanTimeRef.current = currentTime;
4352

4453
if (!data || isOpen) return;

apps/app/src/pages/SettingsPage/sections/ChangePassword/ChangePassword.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
allFieldsAreFilled,
1717
validateField,
1818
validateRepeatedPassword,
19-
} from '@/helpers/validateInput';
19+
} from '@/validation/validateInput';
2020
import { useChangeUserPassword } from '@/api/user/useChangeUserPassword';
2121

2222
export const ChangePassword: React.FC = () => {

apps/app/src/pages/SettingsPage/sections/EditProfileSection/EditProfileSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Checkbox } from '@/components/Checkbox';
1111
import { Input } from '@/components/Input';
1212
import { useRegistration } from '@/context/RegistrationContext';
1313
import { SettingsEdits, UserDataFields } from '@/types/enums';
14-
import { allFieldsAreFilled, validateField } from '@/helpers/validateInput';
14+
import { allFieldsAreFilled, validateField } from '@/validation/validateInput';
1515
import { RegistrationFormErrors } from '@/types/errors/errors.dto';
1616
import { usePatchCurrentUser } from '@/api/user/usePatchCurrentUser';
1717
import { UserModifyDto } from '@ddays-app/types/src/dto/user';

0 commit comments

Comments
 (0)