mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +07:00
removed redux completely
This commit is contained in:
parent
26e6d8d41b
commit
a4bb07e9cf
323 changed files with 2464 additions and 3348 deletions
20
src/hooks/auth/useAuth.ts
Normal file
20
src/hooks/auth/useAuth.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { useUser } from '~/hooks/auth/useUser';
|
||||
import { useAuthStore } from '~/store/auth/useAuthStore';
|
||||
import { useLoginLogoutRestore } from '~/hooks/auth/useLoginLogoutRestore';
|
||||
|
||||
export const useAuth = () => {
|
||||
const { user } = useUser();
|
||||
const auth = useAuthStore();
|
||||
|
||||
const { login, logout } = useLoginLogoutRestore();
|
||||
|
||||
return {
|
||||
user,
|
||||
logout,
|
||||
login,
|
||||
isUser: auth.isUser,
|
||||
setToken: auth.setToken,
|
||||
isTester: auth.isTester,
|
||||
setIsTester: auth.setIsTester,
|
||||
};
|
||||
};
|
16
src/hooks/auth/useLastSeenBoris.ts
Normal file
16
src/hooks/auth/useLastSeenBoris.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useUser } from '~/hooks/auth/useUser';
|
||||
|
||||
export const useLastSeenBoris = () => {
|
||||
const { user, update } = useUser();
|
||||
const lastSeen = user.last_seen_boris;
|
||||
|
||||
const setLastSeen = useCallback(
|
||||
async (date: string) => {
|
||||
await update({ last_seen_boris: date }, false);
|
||||
},
|
||||
[update]
|
||||
);
|
||||
|
||||
return { setLastSeen, lastSeen };
|
||||
};
|
48
src/hooks/auth/useLoginForm.ts
Normal file
48
src/hooks/auth/useLoginForm.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { Asserts, object, string } from 'yup';
|
||||
import { ERRORS } from '~/constants/errors';
|
||||
import { useCallback } from 'react';
|
||||
import { FormikConfig, useFormik } from 'formik';
|
||||
import { IUser } from '~/types/auth';
|
||||
import { showToastSuccess } from '~/utils/toast';
|
||||
import { getRandomPhrase } from '~/constants/phrases';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
import { getValidationErrors } from '~/utils/errors/getValidationErrors';
|
||||
|
||||
const validationSchema = object({
|
||||
username: string().required(ERRORS.REQUIRED),
|
||||
password: string().required(ERRORS.REQUIRED),
|
||||
});
|
||||
|
||||
export type LoginFormData = Asserts<typeof validationSchema>;
|
||||
|
||||
export const useLoginForm = (
|
||||
fetcher: (username: string, password: string) => Promise<IUser>,
|
||||
onSuccess: () => void
|
||||
) => {
|
||||
const onSubmit = useCallback<FormikConfig<LoginFormData>['onSubmit']>(
|
||||
async (values, { setErrors }) => {
|
||||
try {
|
||||
await fetcher(values.username, values.password);
|
||||
onSuccess();
|
||||
showToastSuccess(getRandomPhrase('WELCOME'));
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
|
||||
const validationErrors = getValidationErrors(error);
|
||||
if (validationErrors) {
|
||||
setErrors(validationErrors);
|
||||
}
|
||||
}
|
||||
},
|
||||
[fetcher, onSuccess]
|
||||
);
|
||||
|
||||
return useFormik({
|
||||
validationSchema,
|
||||
onSubmit,
|
||||
initialValues: {
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
};
|
20
src/hooks/auth/useLoginLogoutRestore.ts
Normal file
20
src/hooks/auth/useLoginLogoutRestore.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { useAuthStore } from '~/store/auth/useAuthStore';
|
||||
import { useCallback } from 'react';
|
||||
import { apiUserLogin } from '~/api/auth';
|
||||
|
||||
export const useLoginLogoutRestore = () => {
|
||||
const auth = useAuthStore();
|
||||
|
||||
const logout = useCallback(() => auth.logout(), [auth]);
|
||||
|
||||
const login = useCallback(
|
||||
async (username: string, password: string) => {
|
||||
const result = await apiUserLogin({ username, password });
|
||||
auth.setToken(result.token);
|
||||
return result.user;
|
||||
},
|
||||
[auth]
|
||||
);
|
||||
|
||||
return { logout, login };
|
||||
};
|
53
src/hooks/auth/useMessageEventReactions.ts
Normal file
53
src/hooks/auth/useMessageEventReactions.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { useEffect } from 'react';
|
||||
import { EventMessageType } from '~/constants/events';
|
||||
import { includes, path, values } from 'ramda';
|
||||
import { useOAuth } from '~/hooks/auth/useOAuth';
|
||||
import { Dialog } from '~/constants/modal';
|
||||
import { useModal } from '~/hooks/modal/useModal';
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
|
||||
/** reacts to events passed by window.postMessage */
|
||||
export const useMessageEventReactions = () => {
|
||||
const { loginWithSocial, createSocialAccount, attachAccount } = useOAuth();
|
||||
const { showModal } = useModal();
|
||||
const { isUser } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
const onMessage = (event: MessageEvent) => {
|
||||
const type: EventMessageType | undefined = path(['data', 'type'], event);
|
||||
|
||||
if (!type || !includes(type, values(EventMessageType))) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('caught event:', type);
|
||||
|
||||
switch (type) {
|
||||
case EventMessageType.OAuthLogin:
|
||||
// TODO: do we really need it?
|
||||
loginWithSocial(path(['data', 'payload', 'token'], event));
|
||||
break;
|
||||
case EventMessageType.OAuthProcessed:
|
||||
if (isUser) {
|
||||
void attachAccount(path(['data', 'payload', 'token'], event));
|
||||
} else {
|
||||
void createSocialAccount(path(['data', 'payload', 'token'], event));
|
||||
}
|
||||
break;
|
||||
case EventMessageType.OpenProfile:
|
||||
const username: string | undefined = path(['data', 'username'], event);
|
||||
if (!username) {
|
||||
return;
|
||||
}
|
||||
|
||||
showModal(Dialog.Profile, { username });
|
||||
break;
|
||||
default:
|
||||
console.log('unknown message', event.data);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', onMessage);
|
||||
return () => window.removeEventListener('message', onMessage);
|
||||
}, [attachAccount, createSocialAccount, loginWithSocial, isUser, showModal]);
|
||||
};
|
101
src/hooks/auth/useOAuth.ts
Normal file
101
src/hooks/auth/useOAuth.ts
Normal file
|
@ -0,0 +1,101 @@
|
|||
import { useCallback } from 'react';
|
||||
import { OAuthProvider } from '~/types/auth';
|
||||
import { API } from '~/constants/api';
|
||||
import { apiAttachSocial, apiDropSocial, apiGetSocials, apiLoginWithSocial } from '~/api/auth';
|
||||
import { useModal } from '~/hooks/modal/useModal';
|
||||
import { Dialog } from '~/constants/modal';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
import { path } from 'ramda';
|
||||
import useSWR from 'swr';
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
|
||||
export const useOAuth = () => {
|
||||
const { isUser, setToken } = useAuth();
|
||||
const { showModal, hideModal } = useModal();
|
||||
|
||||
const { data, isValidating: isLoading, mutate } = useSWR(
|
||||
isUser ? API.USER.GET_SOCIALS : null,
|
||||
async () => {
|
||||
const result = await apiGetSocials();
|
||||
return result.accounts;
|
||||
}
|
||||
);
|
||||
|
||||
const openOauthWindow = useCallback((provider: OAuthProvider) => {
|
||||
window.open(API.USER.OAUTH_WINDOW(provider), '', 'width=600,height=400');
|
||||
}, []);
|
||||
|
||||
const createSocialAccount = useCallback(
|
||||
async (token?: string) => {
|
||||
try {
|
||||
if (!token) return;
|
||||
|
||||
const result = await apiLoginWithSocial({ token });
|
||||
|
||||
setToken(result.token);
|
||||
hideModal();
|
||||
} catch (error) {
|
||||
const needsRegister: string | undefined = path(
|
||||
['response', 'data', 'needs_register'],
|
||||
error
|
||||
);
|
||||
|
||||
if (needsRegister && token) {
|
||||
showModal(Dialog.LoginSocialRegister, { token });
|
||||
return;
|
||||
}
|
||||
|
||||
showErrorToast(error);
|
||||
}
|
||||
},
|
||||
[showModal, hideModal, setToken]
|
||||
);
|
||||
|
||||
const loginWithSocial = useCallback(
|
||||
(token: string | undefined) => {
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
setToken(token);
|
||||
hideModal();
|
||||
},
|
||||
[setToken, hideModal]
|
||||
);
|
||||
|
||||
const attachAccount = useCallback(
|
||||
async (token?: string) => {
|
||||
try {
|
||||
if (!token) return;
|
||||
|
||||
await apiAttachSocial({ token });
|
||||
await mutate();
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
}
|
||||
},
|
||||
[mutate]
|
||||
);
|
||||
|
||||
const dropAccount = useCallback(
|
||||
async (provider: OAuthProvider, id: string) => {
|
||||
try {
|
||||
await apiDropSocial({ id, provider });
|
||||
await mutate();
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
}
|
||||
},
|
||||
[mutate]
|
||||
);
|
||||
|
||||
return {
|
||||
openOauthWindow,
|
||||
loginWithSocial,
|
||||
createSocialAccount,
|
||||
attachAccount,
|
||||
dropAccount,
|
||||
accounts: data || [],
|
||||
isLoading: !data && isLoading,
|
||||
};
|
||||
};
|
|
@ -2,32 +2,34 @@ import { useUploader } from '~/hooks/data/useUploader';
|
|||
import { UploadSubject, UploadTarget } from '~/constants/uploads';
|
||||
import { useCallback } from 'react';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
import { ApiUpdateUserRequest, IUser } from '~/redux/auth/types';
|
||||
import { apiUpdateUser } from '~/redux/auth/api';
|
||||
import { apiUpdateUser } from '~/api/auth';
|
||||
import { ApiUpdateUserRequest } from '~/api/auth/types';
|
||||
import { useUser } from '~/hooks/auth/useUser';
|
||||
|
||||
export const usePatchProfile = (updateUserData: (user: Partial<IUser>) => void) => {
|
||||
export const usePatchUser = () => {
|
||||
const { update } = useUser();
|
||||
const { uploadFile } = useUploader(UploadSubject.Avatar, UploadTarget.Profiles);
|
||||
|
||||
const updateProfile = useCallback(
|
||||
const save = useCallback(
|
||||
async (user: Partial<ApiUpdateUserRequest['user']>) => {
|
||||
const result = await apiUpdateUser({ user });
|
||||
await updateUserData(result.user);
|
||||
await update(result.user);
|
||||
return result.user;
|
||||
},
|
||||
[updateUserData]
|
||||
[update]
|
||||
);
|
||||
|
||||
const updatePhoto = useCallback(
|
||||
async (file: File) => {
|
||||
try {
|
||||
const photo = await uploadFile(file);
|
||||
await updateProfile({ photo });
|
||||
await save({ photo });
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
}
|
||||
},
|
||||
[uploadFile, updateProfile]
|
||||
[uploadFile, save]
|
||||
);
|
||||
|
||||
return { updatePhoto, updateProfile };
|
||||
return { updatePhoto, save };
|
||||
};
|
14
src/hooks/auth/useRestoreCode.ts
Normal file
14
src/hooks/auth/useRestoreCode.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { apiCheckRestoreCode } from '~/api/auth';
|
||||
import useSWR from 'swr';
|
||||
import { API } from '~/constants/api';
|
||||
import { getErrorMessage } from '~/utils/errors/getErrorMessage';
|
||||
|
||||
export const useRestoreCode = (code: string) => {
|
||||
const { data, isValidating, error } = useSWR(API.USER.REQUEST_CODE(code), () =>
|
||||
apiCheckRestoreCode({ code })
|
||||
);
|
||||
|
||||
const codeUser = data?.user;
|
||||
|
||||
return { codeUser, isLoading: isValidating, error: getErrorMessage(error) };
|
||||
};
|
56
src/hooks/auth/useRestorePasswordForm.ts
Normal file
56
src/hooks/auth/useRestorePasswordForm.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { Asserts, object, string } from 'yup';
|
||||
import { FormikConfig, useFormik } from 'formik';
|
||||
import { useCallback } from 'react';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
import { getValidationErrors } from '~/utils/errors/getValidationErrors';
|
||||
import { ERRORS } from '~/constants/errors';
|
||||
import { IUser } from '~/types/auth';
|
||||
import { useAuthStore } from '~/store/auth/useAuthStore';
|
||||
|
||||
const validationSchema = object({
|
||||
newPassword: string()
|
||||
.required(ERRORS.REQUIRED)
|
||||
.min(6, ERRORS.PASSWORD_IS_SHORT),
|
||||
newPasswordAgain: string()
|
||||
.required(ERRORS.REQUIRED)
|
||||
.test(
|
||||
'sameAsPassword',
|
||||
'Должен совпадать с паролем',
|
||||
(val, ctx) => val === ctx.parent.newPassword
|
||||
),
|
||||
});
|
||||
|
||||
export type RestorePasswordData = Asserts<typeof validationSchema>;
|
||||
|
||||
export const useRestorePasswordForm = (
|
||||
code: string,
|
||||
fetcher: (props: { code: string; password: string }) => Promise<{ token: string; user: IUser }>,
|
||||
onSuccess: () => void
|
||||
) => {
|
||||
const auth = useAuthStore();
|
||||
|
||||
const onSubmit = useCallback<FormikConfig<RestorePasswordData>['onSubmit']>(
|
||||
async (values, { setErrors }) => {
|
||||
try {
|
||||
const { token, user } = await fetcher({ password: values.newPassword, code });
|
||||
auth.setUser(user);
|
||||
auth.setToken(token);
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
|
||||
const validationErrors = getValidationErrors(error);
|
||||
if (validationErrors) {
|
||||
setErrors(validationErrors);
|
||||
}
|
||||
}
|
||||
},
|
||||
[onSuccess, fetcher, code, auth]
|
||||
);
|
||||
|
||||
return useFormik<RestorePasswordData>({
|
||||
onSubmit,
|
||||
validationSchema,
|
||||
initialValues: { newPassword: '', newPasswordAgain: '' },
|
||||
});
|
||||
};
|
21
src/hooks/auth/useRestorePasswordRedirect.ts
Normal file
21
src/hooks/auth/useRestorePasswordRedirect.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useModal } from '~/hooks/modal/useModal';
|
||||
import { Dialog } from '~/constants/modal';
|
||||
|
||||
/** redirects to the password redirect modal */
|
||||
export const useRestorePasswordRedirect = () => {
|
||||
const history = useHistory();
|
||||
const { showModal } = useModal();
|
||||
|
||||
useEffect(() => {
|
||||
const match = window.location.pathname.match(/^\/restore\/([\w\d-]+)$/);
|
||||
|
||||
if (!match?.[1]) {
|
||||
return;
|
||||
}
|
||||
|
||||
history.push('/');
|
||||
showModal(Dialog.RestorePassword, { code: match[1] });
|
||||
}, [showModal, history]);
|
||||
};
|
40
src/hooks/auth/useRestoreRequestForm.ts
Normal file
40
src/hooks/auth/useRestoreRequestForm.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { Asserts, object, string } from 'yup';
|
||||
import { ERRORS } from '~/constants/errors';
|
||||
import { FormikConfig, useFormik } from 'formik';
|
||||
import { useCallback } from 'react';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
import { getValidationErrors } from '~/utils/errors/getValidationErrors';
|
||||
|
||||
const validationSchema = object({
|
||||
field: string().required(ERRORS.REQUIRED),
|
||||
});
|
||||
|
||||
type RestoreRequestData = Asserts<typeof validationSchema>;
|
||||
|
||||
export const useRestoreRequestForm = (
|
||||
fetcher: (field: string) => Promise<unknown>,
|
||||
onSuccess: () => void
|
||||
) => {
|
||||
const onSubmit = useCallback<FormikConfig<RestoreRequestData>['onSubmit']>(
|
||||
async (values, { setErrors }) => {
|
||||
try {
|
||||
await fetcher(values.field);
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
|
||||
const validationErrors = getValidationErrors(error);
|
||||
if (validationErrors) {
|
||||
setErrors(validationErrors);
|
||||
}
|
||||
}
|
||||
},
|
||||
[fetcher, onSuccess]
|
||||
);
|
||||
|
||||
return useFormik({
|
||||
onSubmit,
|
||||
validationSchema,
|
||||
initialValues: { field: '' },
|
||||
});
|
||||
};
|
55
src/hooks/auth/useSocialRegisterForm.ts
Normal file
55
src/hooks/auth/useSocialRegisterForm.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { Asserts, object, string } from 'yup';
|
||||
import { ERRORS } from '~/constants/errors';
|
||||
import { FormikConfig, useFormik } from 'formik';
|
||||
import { useCallback } from 'react';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
import { getValidationErrors } from '~/utils/errors/getValidationErrors';
|
||||
|
||||
const validationSchema = object({
|
||||
username: string().required(ERRORS.REQUIRED),
|
||||
password: string()
|
||||
.required(ERRORS.REQUIRED)
|
||||
.min(6, ERRORS.PASSWORD_IS_SHORT),
|
||||
});
|
||||
|
||||
type SocialRegisterData = Asserts<typeof validationSchema>;
|
||||
|
||||
export const useSocialRegisterForm = (
|
||||
token: string,
|
||||
fetcher: (props: {
|
||||
token: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}) => Promise<{ token: string }>,
|
||||
onSuccess: (token: string) => void
|
||||
) => {
|
||||
const onSubmit = useCallback<FormikConfig<SocialRegisterData>['onSubmit']>(
|
||||
async (values, { setErrors }) => {
|
||||
try {
|
||||
const result = await fetcher({
|
||||
token,
|
||||
username: values.username,
|
||||
password: values.password || '',
|
||||
});
|
||||
onSuccess(result.token);
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
|
||||
const validationErrors = getValidationErrors(error);
|
||||
if (validationErrors) {
|
||||
setErrors(validationErrors);
|
||||
}
|
||||
}
|
||||
},
|
||||
[token, onSuccess, fetcher]
|
||||
);
|
||||
|
||||
return useFormik<SocialRegisterData>({
|
||||
validationSchema,
|
||||
onSubmit,
|
||||
initialValues: {
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
};
|
30
src/hooks/auth/useUser.ts
Normal file
30
src/hooks/auth/useUser.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import useSWR from 'swr';
|
||||
import { useAuthStore } from '~/store/auth/useAuthStore';
|
||||
import { API } from '~/constants/api';
|
||||
import { apiAuthGetUser } from '~/api/auth';
|
||||
import { EMPTY_USER } from '~/constants/auth';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
import { useCallback } from 'react';
|
||||
import { IUser } from '~/types/auth';
|
||||
|
||||
export const useUser = () => {
|
||||
const { token, setUser } = useAuthStore();
|
||||
const { data, mutate } = useSWR(token ? API.USER.ME : null, () => apiAuthGetUser(), {
|
||||
onSuccess: data => setUser(data?.user || EMPTY_USER),
|
||||
onError: error => showErrorToast(error),
|
||||
});
|
||||
|
||||
const update = useCallback(
|
||||
async (user: Partial<IUser>, revalidate?: boolean) => {
|
||||
if (!data?.user) {
|
||||
console.warn('mutating user before loaded it :-(');
|
||||
return;
|
||||
}
|
||||
|
||||
await mutate({ ...data, user: { ...data.user, ...user } }, revalidate);
|
||||
},
|
||||
[data, mutate]
|
||||
);
|
||||
|
||||
return { user: data?.user || EMPTY_USER, update };
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { IUser } from '~/redux/auth/types';
|
||||
import { IUser } from '~/types/auth';
|
||||
import { useRandomPhrase } from '~/constants/phrases';
|
||||
import { differenceInDays, parseISO } from 'date-fns';
|
||||
import { INACTIVE_ACCOUNT_DAYS } from '~/constants/user';
|
|
@ -1,17 +1,16 @@
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import isBefore from 'date-fns/isBefore';
|
||||
import { authSetState, authSetUser } from '~/redux/auth/actions';
|
||||
import { useUser } from '~/hooks/user/userUser';
|
||||
import { IComment } from '~/redux/types';
|
||||
import { useShallowSelect } from '~/hooks/data/useShallowSelect';
|
||||
import { selectAuthIsTester } from '~/redux/auth/selectors';
|
||||
import { IComment } from '~/types';
|
||||
import { useRandomPhrase } from '~/constants/phrases';
|
||||
import { useBorisStats } from '~/hooks/boris/useBorisStats';
|
||||
import { useLastSeenBoris } from '~/hooks/auth/useLastSeenBoris';
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
|
||||
export const useBoris = (comments: IComment[]) => {
|
||||
const dispatch = useDispatch();
|
||||
const user = useUser();
|
||||
const title = useRandomPhrase('BORIS_TITLE');
|
||||
|
||||
const { lastSeen, setLastSeen } = useLastSeenBoris();
|
||||
const { isTester, setIsTester } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
const last_comment = comments[0];
|
||||
|
@ -19,26 +18,24 @@ export const useBoris = (comments: IComment[]) => {
|
|||
if (!last_comment) return;
|
||||
|
||||
if (
|
||||
user.last_seen_boris &&
|
||||
last_comment.created_at &&
|
||||
!isBefore(new Date(user.last_seen_boris), new Date(last_comment.created_at))
|
||||
)
|
||||
!last_comment.created_at ||
|
||||
!lastSeen ||
|
||||
isBefore(new Date(lastSeen), new Date(last_comment.created_at))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(authSetUser({ last_seen_boris: last_comment.created_at }));
|
||||
}, [user.last_seen_boris, dispatch, comments]);
|
||||
void setLastSeen(last_comment.created_at);
|
||||
}, [lastSeen, setLastSeen, comments]);
|
||||
|
||||
const { stats, isLoading: isLoadingStats } = useBorisStats();
|
||||
|
||||
const setIsBetaTester = useCallback(
|
||||
(is_tester: boolean) => {
|
||||
dispatch(authSetState({ is_tester }));
|
||||
(isTester: boolean) => {
|
||||
setIsTester(isTester);
|
||||
},
|
||||
[dispatch]
|
||||
[setIsTester]
|
||||
);
|
||||
|
||||
const isTester = useShallowSelect(selectAuthIsTester);
|
||||
const title = useRandomPhrase('BORIS_TITLE');
|
||||
|
||||
return { setIsBetaTester, isTester, stats, title, isLoadingStats };
|
||||
};
|
||||
|
|
|
@ -10,9 +10,7 @@ export const useBorisStats = () => {
|
|||
() => getBorisBackendStats()
|
||||
);
|
||||
|
||||
const { data: issues = [], isValidating: isValidatingGit } = useSWR(API.BORIS.GITHUB_ISSUES, () =>
|
||||
getGithubIssues()
|
||||
);
|
||||
const { data: issues = [] } = useSWR(API.BORIS.GITHUB_ISSUES, () => getGithubIssues());
|
||||
|
||||
const stats: BorisUsageStats = {
|
||||
backend,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IComment, INode } from '~/redux/types';
|
||||
import { IComment, INode } from '~/types';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { FormikHelpers, useFormik, useFormikContext } from 'formik';
|
||||
import { array, object, string } from 'yup';
|
||||
|
|
|
@ -4,7 +4,7 @@ import useSWRInfinite, { SWRInfiniteKeyLoader } from 'swr/infinite';
|
|||
import { apiGetNodeComments } from '~/api/node';
|
||||
import { COMMENTS_DISPLAY } from '~/constants/node';
|
||||
import { useCallback } from 'react';
|
||||
import { IComment } from '~/redux/types';
|
||||
import { IComment } from '~/types';
|
||||
|
||||
const getKey: (nodeId: number) => SWRInfiniteKeyLoader = (nodeId: number) => (
|
||||
pageIndex,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useCallback } from 'react';
|
||||
import { IComment } from '~/redux/types';
|
||||
import { IComment } from '~/types';
|
||||
import { useGetComments } from '~/hooks/comments/useGetComments';
|
||||
import { apiLockComment, apiPostComment } from '~/api/node';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
|
|
|
@ -7,13 +7,13 @@ export const usePersistedState = (key: string, initial: string): [string, (val:
|
|||
} catch (e) {
|
||||
return initial;
|
||||
}
|
||||
}, [key]);
|
||||
}, [key, initial]);
|
||||
|
||||
const [val, setVal] = useState<string>(stored);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(`vault_${key}`, val);
|
||||
}, [val]);
|
||||
}, [val, key]);
|
||||
|
||||
return [val, setVal];
|
||||
};
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { IState } from '~/redux/store';
|
||||
|
||||
export const useShallowSelect = <T extends (state: IState) => any>(selector: T): ReturnType<T> =>
|
||||
useSelector(selector, shallowEqual);
|
|
@ -1,5 +1,5 @@
|
|||
import { UploadSubject, UploadTarget } from '~/constants/uploads';
|
||||
import { IFile } from '~/redux/types';
|
||||
import { IFile } from '~/types';
|
||||
import { useCallback } from 'react';
|
||||
import { apiUploadFile } from '~/api/uploads';
|
||||
import { keys } from 'ramda';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback } from "react";
|
||||
import { useCallback } from 'react';
|
||||
|
||||
/** wraps text inside textarea with tags */
|
||||
export const useFormatWrapper = (onChange: (val: string) => void) => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect } from "react";
|
||||
import { useFlowStore } from "~/store/flow/useFlowStore";
|
||||
import { hideLoader } from "~/utils/dom/hideLoader";
|
||||
import { useFlowLoader } from "~/hooks/flow/useFlowLoader";
|
||||
import { useEffect } from 'react';
|
||||
import { useFlowStore } from '~/store/flow/useFlowStore';
|
||||
import { hideLoader } from '~/utils/dom/hideLoader';
|
||||
import { useFlowLoader } from '~/hooks/flow/useFlowLoader';
|
||||
|
||||
/** simply waits for all data to settle and then show the app */
|
||||
export const useGlobalLoader = () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useCallback } from "react";
|
||||
import { getImageFromPaste } from "~/utils/uploader";
|
||||
import { useCallback } from 'react';
|
||||
import { getImageFromPaste } from '~/utils/uploader';
|
||||
|
||||
// useInputPasteUpload attaches event listener to input, that calls onUpload if user pasted any image
|
||||
export const useInputPasteUpload = (onUpload: (files: File[]) => void) => {
|
||||
|
|
|
@ -2,18 +2,21 @@ import { useEffect } from 'react';
|
|||
import { NEW_COMMENT_CLASSNAME } from '~/constants/comment';
|
||||
|
||||
export const useScrollToTop = (deps?: any[]) => {
|
||||
useEffect(() => {
|
||||
const targetElement = document.querySelector(`.${NEW_COMMENT_CLASSNAME}`);
|
||||
useEffect(
|
||||
() => {
|
||||
const targetElement = document.querySelector(`.${NEW_COMMENT_CLASSNAME}`);
|
||||
|
||||
if (!targetElement) {
|
||||
window.scrollTo(0, 0);
|
||||
return;
|
||||
}
|
||||
if (!targetElement) {
|
||||
window.scrollTo(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const bounds = targetElement.getBoundingClientRect();
|
||||
window.scrollTo({
|
||||
top: bounds.top - 100,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}, deps || []);
|
||||
const bounds = targetElement.getBoundingClientRect();
|
||||
window.scrollTo({
|
||||
top: bounds.top - 100,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
},
|
||||
deps && Array.isArray(deps) ? deps : []
|
||||
);
|
||||
};
|
||||
|
|
16
src/hooks/dom/useScrollTop.ts
Normal file
16
src/hooks/dom/useScrollTop.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const useScrollTop = () => {
|
||||
const [top, setTop] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => {
|
||||
setTop(window.scrollY);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', onScroll);
|
||||
return () => window.removeEventListener('scroll', onScroll);
|
||||
}, []);
|
||||
|
||||
return top;
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
import { useCallback } from 'react';
|
||||
import { FlowDisplay, INode } from '~/redux/types';
|
||||
import { FlowDisplay, INode } from '~/types';
|
||||
|
||||
export const useFlowCellControls = (
|
||||
id: INode['id'],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback } from "react";
|
||||
import { usePersistedState } from "~/hooks/data/usePersistedState";
|
||||
import { experimentalFeatures } from "~/constants/features";
|
||||
import { useCallback } from 'react';
|
||||
import { usePersistedState } from '~/hooks/data/usePersistedState';
|
||||
import { experimentalFeatures } from '~/constants/features';
|
||||
|
||||
enum Layout {
|
||||
Fluid = 'fluid',
|
||||
|
|
|
@ -4,7 +4,6 @@ import { uniq } from 'ramda';
|
|||
import { useFlowStore } from '~/store/flow/useFlowStore';
|
||||
import { runInAction } from 'mobx';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
import { delay } from 'redux-saga/effects';
|
||||
|
||||
export const useFlowLoader = () => {
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
|
@ -74,7 +73,7 @@ export const useFlowLoader = () => {
|
|||
});
|
||||
|
||||
// wait a little to debounce
|
||||
await delay(1000);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
} finally {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useFlowStore } from '~/store/flow/useFlowStore';
|
||||
import { useCallback } from 'react';
|
||||
import { FlowDisplay } from '~/redux/types';
|
||||
import { FlowDisplay } from '~/types';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
import { postCellView } from '~/api/flow';
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ import { GetLabNodesRequest, ILabNode } from '~/types/lab';
|
|||
import { getLabNodes } from '~/api/lab';
|
||||
import { flatten, last, uniqBy } from 'ramda';
|
||||
import { useLabStore } from '~/store/lab/useLabStore';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { INode } from '~/redux/types';
|
||||
import { useUser } from '~/hooks/user/userUser';
|
||||
import { useCallback } from 'react';
|
||||
import { INode } from '~/types';
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
|
||||
const getKey: (isUser: boolean) => SWRInfiniteKeyLoader = isUser => (index, prev: ILabNode[]) => {
|
||||
if (!isUser) return null;
|
||||
|
@ -33,10 +33,10 @@ const parseKey = (key: string): GetLabNodesRequest => {
|
|||
|
||||
export const useGetLabNodes = () => {
|
||||
const labStore = useLabStore();
|
||||
const { is_user } = useUser();
|
||||
const { isUser } = useAuth();
|
||||
|
||||
const { data, isValidating, size, setSize, mutate } = useSWRInfinite(
|
||||
getKey(is_user),
|
||||
getKey(isUser),
|
||||
async (key: string) => {
|
||||
const result = await getLabNodes(parseKey(key));
|
||||
return result.nodes;
|
||||
|
@ -71,14 +71,5 @@ export const useGetLabNodes = () => {
|
|||
[data, mutate]
|
||||
);
|
||||
|
||||
/** purge cache on exit */
|
||||
useEffect(() => {
|
||||
if (is_user) {
|
||||
return;
|
||||
}
|
||||
|
||||
labStore.setNodes([]);
|
||||
}, [is_user, labStore]);
|
||||
|
||||
return { nodes, isLoading: !data && isValidating, hasMore, loadMore, unshift, updateNode };
|
||||
};
|
||||
|
|
|
@ -2,17 +2,17 @@ import useSWR from 'swr';
|
|||
import { API } from '~/constants/api';
|
||||
import { getLabStats, getLabUpdates } from '~/api/lab';
|
||||
import { useLabStore } from '~/store/lab/useLabStore';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useUser } from '~/hooks/user/userUser';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
|
||||
const refreshInterval = 1000 * 60 * 5; // 5 minutes
|
||||
|
||||
export const useGetLabStats = () => {
|
||||
const lab = useLabStore();
|
||||
const { is_user } = useUser();
|
||||
const { isUser } = useAuth();
|
||||
|
||||
const { data: stats, isValidating: isValidatingStats } = useSWR(
|
||||
is_user ? API.LAB.STATS : null,
|
||||
isUser ? API.LAB.STATS : null,
|
||||
async () => getLabStats(),
|
||||
{
|
||||
fallbackData: {
|
||||
|
@ -28,7 +28,7 @@ export const useGetLabStats = () => {
|
|||
);
|
||||
|
||||
const { data: updatesData, isValidating: isValidatingUpdates, mutate: mutateUpdates } = useSWR(
|
||||
is_user ? API.LAB.UPDATES : null,
|
||||
isUser ? API.LAB.UPDATES : null,
|
||||
async () => {
|
||||
const result = await getLabUpdates();
|
||||
return result.nodes;
|
||||
|
@ -42,9 +42,9 @@ export const useGetLabStats = () => {
|
|||
}
|
||||
);
|
||||
|
||||
const heroes = stats?.heroes || [];
|
||||
const tags = stats?.tags || [];
|
||||
const updates = updatesData || [];
|
||||
const heroes = useMemo(() => stats?.heroes || [], [stats]);
|
||||
const tags = useMemo(() => stats?.tags || [], [stats]);
|
||||
const updates = useMemo(() => updatesData || [], [updatesData]);
|
||||
|
||||
const isLoading = (!stats || !updates) && (isValidatingStats || isValidatingUpdates);
|
||||
const seenNode = useCallback(
|
||||
|
@ -57,16 +57,5 @@ export const useGetLabStats = () => {
|
|||
[mutateUpdates, updates]
|
||||
);
|
||||
|
||||
/** purge cache on exit */
|
||||
useEffect(() => {
|
||||
if (is_user) {
|
||||
return;
|
||||
}
|
||||
|
||||
lab.setHeroes([]);
|
||||
lab.setTags([]);
|
||||
lab.setUpdates([]);
|
||||
}, [is_user, lab]);
|
||||
|
||||
return { heroes, tags, updates, isLoading, seenNode };
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import useSWR from 'swr';
|
||||
import { API } from '~/constants/api';
|
||||
import { apiGetUserMessages } from '~/api/messages';
|
||||
import { IMessage } from '~/redux/types';
|
||||
import { IMessage } from '~/types';
|
||||
|
||||
const getKey = (username: string): string | null => {
|
||||
return username ? `${API.USER.MESSAGES}/${username}` : null;
|
||||
|
|
|
@ -10,6 +10,7 @@ export const useYoutubeMetadata = (id: string) => {
|
|||
}
|
||||
|
||||
enqueue(id);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id]);
|
||||
|
||||
return metadata[id];
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import { useEffect } from "react";
|
||||
import { history } from "~/redux/store";
|
||||
|
||||
/**
|
||||
* useBlockBackButton - blocks back navigation and calls {callback}
|
||||
* @param callback
|
||||
*/
|
||||
export const useBlockBackButton = (callback?: () => void) => {
|
||||
useEffect(
|
||||
() =>
|
||||
history.listen((newLocation, action) => {
|
||||
if (action !== 'POP') {
|
||||
return;
|
||||
}
|
||||
|
||||
history.goForward();
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}),
|
||||
[callback]
|
||||
);
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
import { useCallback } from 'react';
|
||||
import { IFile } from '~/redux/types';
|
||||
import { IFile } from '~/types';
|
||||
import { useShowModal } from '~/hooks/modal/useShowModal';
|
||||
import { Dialog } from '~/constants/modal';
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useCallback } from 'react';
|
||||
import { INode } from '~/redux/types';
|
||||
import { INode } from '~/types';
|
||||
import { apiPostNode } from '~/api/node';
|
||||
import { useFlowStore } from '~/store/flow/useFlowStore';
|
||||
import { useGetLabNodes } from '~/hooks/lab/useGetLabNodes';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { INode } from '~/redux/types';
|
||||
import { INode } from '~/types';
|
||||
import useSWR from 'swr';
|
||||
import { ApiGetNodeRelatedResult } from '~/types/node';
|
||||
import { API } from '~/constants/api';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { INode } from "~/redux/types";
|
||||
import { useHistory } from "react-router";
|
||||
import { useCallback } from "react";
|
||||
import { URLS } from "~/constants/urls";
|
||||
import { INode } from '~/types';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useCallback } from 'react';
|
||||
import { URLS } from '~/constants/urls';
|
||||
|
||||
// useGotoNode returns fn, that navigates to node
|
||||
export const useGotoNode = (id: INode['id']) => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IComment } from '~/redux/types';
|
||||
import { IComment } from '~/types';
|
||||
import { useMemo } from 'react';
|
||||
import { groupCommentsByUser } from '~/utils/fn';
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { API } from '~/constants/api';
|
|||
import { useOnNodeSeen } from '~/hooks/node/useOnNodeSeen';
|
||||
import { apiGetNode } from '~/api/node';
|
||||
import { useCallback } from 'react';
|
||||
import { INode } from '~/redux/types';
|
||||
import { INode } from '~/types';
|
||||
import { EMPTY_NODE } from '~/constants/node';
|
||||
|
||||
export const useLoadNode = (id: number) => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { INode } from '~/redux/types';
|
||||
import { INode } from '~/types';
|
||||
import { useCallback } from 'react';
|
||||
import { apiLockNode, apiPostNodeHeroic, apiPostNodeLike } from '~/api/node';
|
||||
import { showErrorToast } from '~/utils/errors/showToast';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { INode } from '~/redux/types';
|
||||
import { INode } from '~/types';
|
||||
import { useMemo } from 'react';
|
||||
import { UploadType } from '~/constants/uploads';
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { INode } from "~/redux/types";
|
||||
import { createElement, FC, useCallback, useMemo } from "react";
|
||||
import { isNil, prop } from "ramda";
|
||||
import { INodeComponentProps, LAB_PREVIEW_LAYOUT, NODE_COMPONENTS, NODE_HEADS, NODE_INLINES } from "~/constants/node";
|
||||
import { INode } from '~/types';
|
||||
import { createElement, FC, useCallback, useMemo } from 'react';
|
||||
import { isNil, prop } from 'ramda';
|
||||
import { INodeComponentProps, LAB_PREVIEW_LAYOUT, NODE_COMPONENTS, NODE_HEADS, NODE_INLINES } from '~/constants/node';
|
||||
|
||||
// useNodeBlocks returns head, block and inline blocks of node
|
||||
export const useNodeBlocks = (node: INode, isLoading: boolean) => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { INode } from '~/redux/types';
|
||||
import { INode } from '~/types';
|
||||
import { usePageCover } from '~/components/containers/PageCoverProvider/usePageCover';
|
||||
|
||||
export const useNodeCoverImage = (node: INode) => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { INode } from '~/redux/types';
|
||||
import { INode } from '~/types';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { FormikConfig, FormikHelpers, useFormik, useFormikContext } from 'formik';
|
||||
import { object } from 'yup';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { INode } from '~/redux/types';
|
||||
import { INode } from '~/types';
|
||||
import { useMemo } from 'react';
|
||||
import { UploadType } from '~/constants/uploads';
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { useMemo } from 'react';
|
||||
import { canEditNode, canLikeNode, canStarNode } from '~/utils/node';
|
||||
import { useShallowSelect } from '~/hooks/data/useShallowSelect';
|
||||
import { selectUser } from '~/redux/auth/selectors';
|
||||
import { INode } from '~/redux/types';
|
||||
import { INode } from '~/types';
|
||||
import { useUser } from '~/hooks/auth/useUser';
|
||||
|
||||
export const useNodePermissions = (node?: INode) => {
|
||||
const user = useShallowSelect(selectUser);
|
||||
const { user } = useUser();
|
||||
|
||||
const edit = useMemo(() => canEditNode(node, user), [node, user]);
|
||||
const like = useMemo(() => canLikeNode(node, user), [node, user]);
|
||||
const star = useMemo(() => canStarNode(node, user), [node, user]);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useHistory } from 'react-router';
|
||||
import { useCallback } from 'react';
|
||||
import { ITag } from '~/redux/types';
|
||||
import { ITag } from '~/types';
|
||||
import { URLS } from '~/constants/urls';
|
||||
import { useLoadNode } from '~/hooks/node/useLoadNode';
|
||||
import { apiDeleteNodeTag, apiPostNodeTags } from '~/api/node';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { INode } from "~/redux/types";
|
||||
import { useEffect } from "react";
|
||||
import { useFlowStore } from "~/store/flow/useFlowStore";
|
||||
import { useGetLabStats } from "~/hooks/lab/useGetLabStats";
|
||||
import { INode } from '~/types';
|
||||
import { useEffect } from 'react';
|
||||
import { useFlowStore } from '~/store/flow/useFlowStore';
|
||||
import { useGetLabStats } from '~/hooks/lab/useGetLabStats';
|
||||
|
||||
// useOnNodeSeen updates node seen status across all needed places
|
||||
export const useOnNodeSeen = (node?: INode) => {
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import { useLoadNode } from '~/hooks/node/useLoadNode';
|
||||
import { useCallback } from 'react';
|
||||
import { INode } from '~/redux/types';
|
||||
import { INode } from '~/types';
|
||||
import { apiPostNode } from '~/api/node';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useFlowStore } from '~/store/flow/useFlowStore';
|
||||
import { useGetLabNodes } from '~/hooks/lab/useGetLabNodes';
|
||||
|
||||
export const useUpdateNode = (id: number) => {
|
||||
const dispatch = useDispatch();
|
||||
const { update } = useLoadNode(id);
|
||||
const flow = useFlowStore();
|
||||
const lab = useGetLabNodes();
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import useSWR from 'swr';
|
||||
import { API } from '~/constants/api';
|
||||
import { apiAuthGetUserProfile } from '~/redux/auth/api';
|
||||
import { EMPTY_USER } from '~/redux/auth/constants';
|
||||
import { apiAuthGetUserProfile } from '~/api/auth';
|
||||
import { EMPTY_USER } from '~/constants/auth';
|
||||
import { useCallback } from 'react';
|
||||
import { IUser } from '~/redux/auth/types';
|
||||
import { IUser } from '~/types/auth';
|
||||
|
||||
const getKey = (username?: string): string | null => {
|
||||
return username ? `${API.USER.PROFILE}/${username}` : null;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IUser } from '~/redux/auth/types';
|
||||
import { IUser } from '~/types/auth';
|
||||
import { Asserts, object, string } from 'yup';
|
||||
import { ERRORS } from '~/constants/errors';
|
||||
import { FormikConfig, useFormik } from 'formik';
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||
import useSWRInfinite, { SWRInfiniteKeyLoader } from 'swr/infinite';
|
||||
import { flatten } from 'ramda';
|
||||
import { getSearchResults } from '~/api/flow';
|
||||
import { INode } from '~/redux/types';
|
||||
import { INode } from '~/types';
|
||||
import { GetSearchResultsRequest } from '~/types/flow';
|
||||
|
||||
const RESULTS_COUNT = 20;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { INode } from '~/redux/types';
|
||||
import { INode } from '~/types';
|
||||
import { API } from '~/constants/api';
|
||||
import { flatten, isNil } from 'ramda';
|
||||
import useSWRInfinite, { SWRInfiniteKeyLoader } from 'swr/infinite';
|
||||
|
|
17
src/hooks/updates/useUpdates.ts
Normal file
17
src/hooks/updates/useUpdates.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import useSWR from 'swr';
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
import { apiAuthGetUpdates } from '~/api/auth';
|
||||
import { API } from '~/constants/api';
|
||||
|
||||
export const useUpdates = () => {
|
||||
const { isUser } = useAuth();
|
||||
const { data } = useSWR(
|
||||
isUser ? API.USER.GET_UPDATES : null,
|
||||
() => apiAuthGetUpdates({ exclude_dialogs: 0, last: '' }),
|
||||
{ refreshInterval: 5 * 60 * 1000 }
|
||||
);
|
||||
|
||||
const borisCommentedAt = data?.boris?.commented_at || '';
|
||||
|
||||
return { borisCommentedAt };
|
||||
};
|
|
@ -1,4 +0,0 @@
|
|||
import { useShallowSelect } from '~/hooks/data/useShallowSelect';
|
||||
import { selectUser } from '~/redux/auth/selectors';
|
||||
|
||||
export const useUser = () => useShallowSelect(selectUser);
|
Loading…
Add table
Add a link
Reference in a new issue