import React, {useEffect, useState} from "react";
import {Link, useLocation, useNavigationType, useParams} from "react-router-dom";
import {useDispatch} from 'react-redux';
import {useNavigate} from 'react-router-dom';
import {BarLoader, MoonLoader} from "react-spinners";
import {usersApi} from '../utils/reducers/usersSlice';
import {workoutsApi} from '../utils/reducers/workoutsSlice';
import {competitionsApi} from '../utils/reducers/competitionsSlice';
import {statsApi} from '../utils/reducers/statsSlice';
import {feedApi} from '../utils/reducers/feedSlice';
import {PageWrapper} from "../utils/miscellaneous";
import {sentryError} from "../utils/reducers/baseQueryWithReauth";
function BaseHome({children}) {
const navType = useNavigationType();
useEffect(() => {
if (navType === "POP") {
document.body.classList.remove("body-no-scroll");
}
}, [navType]);
return (
Workout Challenge
Compete with friends and co-workers across devices (Apple / Android / Garmin /
etc.)
using the metrics you want to use (km /
minutes / kcal / # of times /
etc.)
respecting your privacy (no data is sold or
shared and only for the competition
necessary data synced with Strava)
{children}
)
}
function useWaitForLocalStorage(key, expectedValue, interval = 500) {
const [matched, setMatched] = useState(false);
useEffect(() => {
const check = () => {
const value = localStorage.getItem(key);
if (value === expectedValue) {
setMatched(true);
}
};
check(); // Initial check
if (!matched) {
const id = setInterval(() => {
check();
}, interval);
return () => clearInterval(id);
}
}, [key, expectedValue, matched]);
return matched;
}
function LogoutPage() {
const dispatch = useDispatch();
const navigate = useNavigate();
console.log('Clear localStorage as new user wants to register');
dispatch(usersApi.util.resetApiState());
dispatch(workoutsApi.util.resetApiState());
dispatch(competitionsApi.util.resetApiState());
dispatch(statsApi.util.resetApiState());
dispatch(feedApi.util.resetApiState());
localStorage.clear();
const matched = useWaitForLocalStorage("refresh_token", null);
if (matched) {
navigate("/login");
}
;
return (
)
}
function WelcomePage() {
const location = useLocation();
return (
Create Account
Log In
);
}
const waitForLocalStorage = (key, timeout = 5000) =>
new Promise((resolve, reject) => {
const start = Date.now();
const interval = setInterval(() => {
const val = localStorage.getItem(key);
if (val !== null) {
clearInterval(interval);
resolve(val);
} else if (Date.now() - start > timeout) {
clearInterval(interval);
reject(new Error('Timeout waiting for localStorage key'));
}
}, 100);
});
const LoadingForm = () => {
return (
)
}
const apiCreateAccount = async (email, first_name, last_name, gender, password) => {
try {
const response = await fetch((process.env.REACT_APP_BACKEND_URL || '') + '/api/user/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email.toLowerCase(),
first_name: first_name,
last_name: last_name,
gender: gender,
password: password
}),
});
if (response.ok) {
console.log('Registration Success');
return [true, undefined];
} else {
console.log('Registration Error:', response.status, response.statusText);
let error_msg = 'Registration Error (' + response.status + '): ' + response.statusText + ', ';
try {
const error = await response.json();
for (const key in error) {
error_msg += key + ': ' + error[key] + ', ';
}
} catch (e) {
error_msg += ' Unknown error';
}
return [false, error_msg];
}
} catch (error) {
console.error('Network or server error during registration:', error);
// Capture network errors in Sentry
sentryError({
result: error,
errorSource: 'manual-api',
endpointName: 'register',
});
return [false, 'Network or server error occurred. Please try again.'];
}
}
const apiLogin = async (email, password) => {
try {
const response = await fetch((process.env.REACT_APP_BACKEND_URL || '') + '/api/token/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email.toLowerCase(),
password: password
}),
});
if (response.ok) {
console.log('Login Successful');
const token = await response.json();
localStorage.setItem('access_token', token.access);
localStorage.setItem('refresh_token', token.refresh);
return [true, undefined];
} else {
console.log('Login Error:', response.status, response.statusText);
let parsedError = null;
try {
parsedError = await response.json();
} catch (e) {
parsedError = null;
}
return [false, response.statusText + ' (' + response.status + ') - ' + (parsedError ? parsedError.detail : 'Unknown error')];
}
} catch (error) {
console.error('Network or server error during login:', error);
// Capture network errors in Sentry
sentryError({
result: error,
errorSource: 'manual-api',
endpointName: 'login',
});
return [false, 'Network or server error occurred. Please try again.'];
}
};
const apiRequestNewPassword = async (email) => {
try {
const response = await fetch((process.env.REACT_APP_BACKEND_URL || '') + '/api/password-reset/request/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
}),
});
if (response.ok) {
console.log('Password Reset Request Successful');
return [true, undefined];
} else {
console.log('Password Reset Request Error:', response.status, response.statusText, response);
let parsedError = null;
try {
parsedError = await response.json();
} catch (e) {
parsedError = null;
}
return [false, response.statusText + ' (' + response.status + ') - ' + (parsedError ? parsedError.detail : 'Unknown error')];
}
} catch (error) {
console.error('Network or server error during password reset request:', error);
// Capture network errors in Sentry
sentryError({
result: error,
errorSource: 'manual-api',
endpointName: 'new-password-request',
});
return [false, 'Network or server error occurred. Please try again.'];
}
};
const apiSetNewPassword = async (uid, token, newPassword) => {
try {
const response = await fetch((process.env.REACT_APP_BACKEND_URL || '') + '/api/password-reset/confirm/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
uid: uid,
token: token,
new_password: newPassword,
}),
});
if (response.ok) {
console.log('Set New Password Successful');
return [true, undefined];
} else {
console.log('Set New Password Error:', response.status, response.statusText, response);
let parsedError = null;
try {
parsedError = await response.json();
} catch (e) {
parsedError = null;
}
return [false, response.statusText + ' (' + response.status + ') - ' + (parsedError ? parsedError.detail : 'Unknown error')];
}
} catch (error) {
console.error('Network or server error during password reset:', error);
// Capture network errors in Sentry
sentryError({
result: error,
errorSource: 'manual-api',
endpointName: 'set-new-password',
});
return [false, 'Network or server error occurred. Please try again.'];
}
}
const apiRefreshToken = async (refreshToken) => {
try {
const response = await fetch((process.env.REACT_APP_BACKEND_URL || '') + '/api/token/refresh/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
refresh: refreshToken,
}),
});
if (response.ok) {
console.log('Token Refresh Successful');
const token = await response.json();
localStorage.setItem('access_token', token.access);
return [true, undefined];
} else {
console.log('Token Refresh Error:', response.status, response.statusText);
let error = null;
try {
error = await response.json();
} catch (e) {
error = { detail: 'Unknown error' };
}
localStorage.removeItem('refresh_token');
return [false, response.statusText + ' (' + response.status + ') - ' + error.detail];
}
} catch (error) {
console.error('Network or server error during token refresh:', error);
localStorage.removeItem('refresh_token');
// Capture network errors in Sentry
sentryError({
result: error,
errorSource: 'manual-api',
endpointName: 'refresh-token',
});
return [false, 'Network or server error occurred during token refresh. Please try again.'];
}
};
function RegisterPage() {
const dispatch = useDispatch();
const location = useLocation();
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState([]);
const navigate = useNavigate();
async function handleSubmit(e) {
e.preventDefault();
const email = e.target.email.value;
const first_name = e.target.first_name.value;
const last_name = e.target.last_name.value;
const gender = e.target.gender.value;
const password1 = e.target.password1.value;
const password2 = e.target.password2.value;
if (typeof (email) === "undefined" || email === null || email === "") {
setErrorMessage(['Please enter an email address.']);
} else if (typeof (first_name) === "undefined" || first_name === null || first_name === "") {
setErrorMessage(['Please enter a first name.']);
} else if (typeof (password1) === "undefined" || password1 === null || password1 === "") {
setErrorMessage(['Please enter a password.']);
} else if (password1 !== password2) {
setErrorMessage(['Passwords do not match.']);
} else {
setIsLoading(true);
const [success_register, msg_register] = await apiCreateAccount(email, first_name, last_name, gender, password1);
const [success_login, msg_login] = await apiLogin(email, password1);
const params = new URLSearchParams(location.search);
params.set('welcome', 'true');
if (success_register && success_login) {
await waitForLocalStorage('access_token');
console.log('Register and Login Successful - redirect ', localStorage.getItem('access_token'));
navigate(`/dashboard/?${params.toString()}`);
} else if (!success_register) {
setErrorMessage(msg_register.split(", "));
} else if (!success_login) {
setErrorMessage(['Successful Registration', 'Login ' + msg_login]);
navigate(`/dashboard/?${params.toString()}`);
}
setIsLoading(false);
}
};
const [gender, setGender] = useState('');
const handleDropDownChange = (e) => {
setGender(e.target.value);
}
useEffect(() => {
console.log('Clear localStorage as new user wants to register');
dispatch(usersApi.util.resetApiState());
dispatch(workoutsApi.util.resetApiState());
dispatch(competitionsApi.util.resetApiState());
dispatch(statsApi.util.resetApiState());
dispatch(feedApi.util.resetApiState());
localStorage.clear();
}, []);
return (
{
isLoading ? : (
)}
);
}
function LogInPage() {
const dispatch = useDispatch();
const location = useLocation();
const navigate = useNavigate();
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const params = new URLSearchParams(window.location.search);
// handle submit/login action from login form
async function handleSubmit(e) {
e.preventDefault();
setErrorMessage(null);
setIsLoading(true);
const email = e.target.email.value;
const password = e.target.password.value;
const [success, msg] = await apiLogin(email, password);
if (success) {
// success logging in - redirect to dashboard
await waitForLocalStorage('access_token');
setIsLoading(false);
console.log('redirect', localStorage.getItem('access_token'));
if (params.has('redirect')) {
const redirectUrl = decodeURIComponent(params.get('redirect'));
console.log('Redirect to:', redirectUrl);
navigate(redirectUrl);
} else {
navigate(`/dashboard/${location.search}`);
}
} else {
// error logging in - user try again
setErrorMessage(msg);
setIsLoading(false);
}
}
// check if refreshToken already exists and user is already logged in
async function checkRefreshToken(refreshToken) {
console.log('refresh_token already exists - check if still valid');
setIsLoading(true);
const [success, msg] = await apiRefreshToken(refreshToken);
if (success) {
// success refreshing access_token - redirecting to dashboard
await waitForLocalStorage('access_token');
console.log('refresh_token exists and is valid - redirect ', localStorage.getItem('access_token'));
navigate(`/dashboard/${location.search}`);
} else {
// error refreshing access_token - manual login required
localStorage.removeItem('refresh_token');
console.log('refresh_token exists but expired - new login required');
}
setIsLoading(false);
}
useEffect(() => {
dispatch(usersApi.util.resetApiState());
dispatch(workoutsApi.util.resetApiState());
dispatch(competitionsApi.util.resetApiState());
dispatch(statsApi.util.resetApiState());
dispatch(feedApi.util.resetApiState());
const refreshToken = localStorage.getItem('refresh_token');
if (refreshToken !== null) {
localStorage.removeItem('access_token');
checkRefreshToken(refreshToken);
}
}, []);
return (
{
isLoading ? : (
)
}
}/>
);
}
function ResetPasswordPage() {
const navigate = useNavigate();
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
// handle submit/reset action from reset password form
async function handleSubmit(e) {
e.preventDefault();
setErrorMessage(null);
setIsLoading(true);
const email = e.target.email.value;
const [success, msg] = await apiRequestNewPassword(email);
if (success) {
// success reset request - redirect to start page
window.alert('Success! Please check your email for a reset link.');
setIsLoading(false);
console.log('redirect to login page');
navigate(`/`);
} else {
// error reset request - user try again
setErrorMessage(msg);
setIsLoading(false);
}
}
return (
{
isLoading ? : (
)}
}/>
);
}
function SetNewPasswordPage() {
const {id, token} = useParams();
const navigate = useNavigate();
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
// handle submit/reset action from reset password form
async function handleSubmit(e) {
e.preventDefault();
setErrorMessage(null);
setIsLoading(true);
const password1 = e.target.password1.value;
const password2 = e.target.password2.value;
if (typeof (password1) === "undefined" || password1 === null || password1 === "") {
setErrorMessage(['Please enter a password.']);
setIsLoading(false);
} else if (password1 !== password2) {
setErrorMessage(['Passwords do not match.']);
setIsLoading(false);
} else {
const [success, msg] = await apiSetNewPassword(id, token, password1);
if (success) {
// success reset password - redirect to login page
setIsLoading(false);
console.log('redirect to login page');
navigate(`/login/`);
} else {
// error resetting password - user try again
setErrorMessage(msg);
setIsLoading(false);
}
}
}
return (
{
isLoading ? : (
)}
}/>
);
}
// NotFound page
const NotFound = () => {
return (
404
Page Not Found
The page you're looking for doesn't exist or has been moved.
Go to Home
);
};
export {WelcomePage, NotFound, RegisterPage, LogInPage, LogoutPage, ResetPasswordPage, SetNewPasswordPage};