kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Add React Query
rodzic
b7abb11ca2
commit
f16da850fd
|
@ -202,9 +202,7 @@ export const rememberAuthAccount = (accountUrl: string) =>
|
||||||
|
|
||||||
export const loadCredentials = (token: string, accountUrl: string) =>
|
export const loadCredentials = (token: string, accountUrl: string) =>
|
||||||
(dispatch: AppDispatch) => dispatch(rememberAuthAccount(accountUrl))
|
(dispatch: AppDispatch) => dispatch(rememberAuthAccount(accountUrl))
|
||||||
.then(() => {
|
.then(() => dispatch(verifyCredentials(token, accountUrl)))
|
||||||
dispatch(verifyCredentials(token, accountUrl));
|
|
||||||
})
|
|
||||||
.catch(() => dispatch(verifyCredentials(token, accountUrl)));
|
.catch(() => dispatch(verifyCredentials(token, accountUrl)));
|
||||||
|
|
||||||
/** Trim the username and strip the leading @. */
|
/** Trim the username and strip the leading @. */
|
||||||
|
|
|
@ -17,6 +17,7 @@ import * as BuildConfig from 'soapbox/build_config';
|
||||||
import GdprBanner from 'soapbox/components/gdpr-banner';
|
import GdprBanner from 'soapbox/components/gdpr-banner';
|
||||||
import Helmet from 'soapbox/components/helmet';
|
import Helmet from 'soapbox/components/helmet';
|
||||||
import LoadingScreen from 'soapbox/components/loading-screen';
|
import LoadingScreen from 'soapbox/components/loading-screen';
|
||||||
|
import { AuthProvider, useAuth } from 'soapbox/contexts/auth-context';
|
||||||
import AuthLayout from 'soapbox/features/auth_layout';
|
import AuthLayout from 'soapbox/features/auth_layout';
|
||||||
import EmbeddedStatus from 'soapbox/features/embedded-status';
|
import EmbeddedStatus from 'soapbox/features/embedded-status';
|
||||||
import PublicLayout from 'soapbox/features/public_layout';
|
import PublicLayout from 'soapbox/features/public_layout';
|
||||||
|
@ -40,6 +41,7 @@ import {
|
||||||
} from 'soapbox/hooks';
|
} from 'soapbox/hooks';
|
||||||
import MESSAGES from 'soapbox/locales/messages';
|
import MESSAGES from 'soapbox/locales/messages';
|
||||||
import { queryClient } from 'soapbox/queries/client';
|
import { queryClient } from 'soapbox/queries/client';
|
||||||
|
import { useAxiosInterceptors } from 'soapbox/queries/client';
|
||||||
import { useCachedLocationHandler } from 'soapbox/utils/redirect';
|
import { useCachedLocationHandler } from 'soapbox/utils/redirect';
|
||||||
import { generateThemeCss } from 'soapbox/utils/theme';
|
import { generateThemeCss } from 'soapbox/utils/theme';
|
||||||
|
|
||||||
|
@ -63,7 +65,7 @@ const loadInitial = () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return async(dispatch, getState) => {
|
return async(dispatch, getState) => {
|
||||||
// Await for authenticated fetch
|
// Await for authenticated fetch
|
||||||
await dispatch(fetchMe());
|
const account = await dispatch(fetchMe());
|
||||||
// Await for feature detection
|
// Await for feature detection
|
||||||
await dispatch(loadInstance());
|
await dispatch(loadInstance());
|
||||||
// Await for configuration
|
// Await for configuration
|
||||||
|
@ -76,12 +78,15 @@ const loadInitial = () => {
|
||||||
if (pepeEnabled && !state.me) {
|
if (pepeEnabled && !state.me) {
|
||||||
await dispatch(fetchVerificationConfig());
|
await dispatch(fetchVerificationConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return account;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Highest level node with the Redux store. */
|
/** Highest level node with the Redux store. */
|
||||||
const SoapboxMount = () => {
|
const SoapboxMount = () => {
|
||||||
useCachedLocationHandler();
|
useCachedLocationHandler();
|
||||||
|
|
||||||
const me = useAppSelector(state => state.me);
|
const me = useAppSelector(state => state.me);
|
||||||
const instance = useAppSelector(state => state.instance);
|
const instance = useAppSelector(state => state.instance);
|
||||||
const account = useOwnAccount();
|
const account = useOwnAccount();
|
||||||
|
@ -204,6 +209,9 @@ interface ISoapboxLoad {
|
||||||
/** Initial data loader. */
|
/** Initial data loader. */
|
||||||
const SoapboxLoad: React.FC<ISoapboxLoad> = ({ children }) => {
|
const SoapboxLoad: React.FC<ISoapboxLoad> = ({ children }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { setAccount, token, baseApiUri } = useAuth();
|
||||||
|
|
||||||
|
useAxiosInterceptors(token, baseApiUri);
|
||||||
|
|
||||||
const me = useAppSelector(state => state.me);
|
const me = useAppSelector(state => state.me);
|
||||||
const account = useOwnAccount();
|
const account = useOwnAccount();
|
||||||
|
@ -233,7 +241,8 @@ const SoapboxLoad: React.FC<ISoapboxLoad> = ({ children }) => {
|
||||||
|
|
||||||
// Load initial data from the API
|
// Load initial data from the API
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(loadInitial()).then(() => {
|
dispatch(loadInitial()).then((account) => {
|
||||||
|
setAccount(account);
|
||||||
setIsLoaded(true);
|
setIsLoaded(true);
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
setIsLoaded(true);
|
setIsLoaded(true);
|
||||||
|
@ -292,13 +301,15 @@ const SoapboxHead: React.FC<ISoapboxHead> = ({ children }) => {
|
||||||
const Soapbox: React.FC = () => {
|
const Soapbox: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<AuthProvider>
|
||||||
<SoapboxHead>
|
<QueryClientProvider client={queryClient}>
|
||||||
<SoapboxLoad>
|
<SoapboxHead>
|
||||||
<SoapboxMount />
|
<SoapboxLoad>
|
||||||
</SoapboxLoad>
|
<SoapboxMount />
|
||||||
</SoapboxHead>
|
</SoapboxLoad>
|
||||||
</QueryClientProvider>
|
</SoapboxHead>
|
||||||
|
</QueryClientProvider>
|
||||||
|
</AuthProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
|
||||||
|
import React, {
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { localState } from 'soapbox/reducers/auth';
|
||||||
|
import { parseBaseURL } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
const AuthContext = createContext(null as any);
|
||||||
|
|
||||||
|
interface IAccount {
|
||||||
|
acct: string
|
||||||
|
avatar: string
|
||||||
|
avatar_static: string
|
||||||
|
bot: boolean
|
||||||
|
created_at: string
|
||||||
|
discoverable: boolean
|
||||||
|
display_name: string
|
||||||
|
emojis: string[]
|
||||||
|
fields: any // not sure
|
||||||
|
followers_count: number
|
||||||
|
following_count: number
|
||||||
|
group: boolean
|
||||||
|
header: string
|
||||||
|
header_static: string
|
||||||
|
id: string
|
||||||
|
last_status_at: string
|
||||||
|
location: string
|
||||||
|
locked: boolean
|
||||||
|
note: string
|
||||||
|
statuses_count: number
|
||||||
|
url: string
|
||||||
|
username: string
|
||||||
|
verified: boolean
|
||||||
|
website: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AuthProvider: React.FC<any> = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [account, setAccount] = useState<IAccount>();
|
||||||
|
const [token, setToken] = useState<string>();
|
||||||
|
const [baseApiUri, setBaseApiUri] = useState<string>();
|
||||||
|
|
||||||
|
const value = useMemo(() => ({
|
||||||
|
account,
|
||||||
|
baseApiUri,
|
||||||
|
setAccount,
|
||||||
|
token,
|
||||||
|
}), [account]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const cachedAuth: any = localState?.toJS();
|
||||||
|
|
||||||
|
if (cachedAuth?.me) {
|
||||||
|
setToken(cachedAuth.users[cachedAuth.me].access_token);
|
||||||
|
setBaseApiUri(parseBaseURL(cachedAuth.users[cachedAuth.me].url));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAuth {
|
||||||
|
account: IAccount
|
||||||
|
baseApiUri: string
|
||||||
|
setAccount(account: IAccount): void
|
||||||
|
token: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAuth = (): IAuth => useContext(AuthContext);
|
|
@ -1,5 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import { QueryClientProvider } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { HotKeys } from 'react-hotkeys';
|
import { HotKeys } from 'react-hotkeys';
|
||||||
|
@ -119,6 +120,7 @@ import { WrappedRoute } from './util/react_router_helpers';
|
||||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||||
// Without this it ends up in ~8 very commonly used bundles.
|
// Without this it ends up in ~8 very commonly used bundles.
|
||||||
import 'soapbox/components/status';
|
import 'soapbox/components/status';
|
||||||
|
import { queryClient } from 'soapbox/queries/client';
|
||||||
|
|
||||||
const EmptyPage = HomePage;
|
const EmptyPage = HomePage;
|
||||||
|
|
||||||
|
@ -648,51 +650,53 @@ const UI: React.FC = ({ children }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys keyMap={keyMap} handlers={me ? handlers : undefined} ref={setHotkeysRef} attach={window} focused>
|
<QueryClientProvider client={queryClient}>
|
||||||
<div ref={node} style={style}>
|
<HotKeys keyMap={keyMap} handlers={me ? handlers : undefined} ref={setHotkeysRef} attach={window} focused>
|
||||||
<BackgroundShapes />
|
<div ref={node} style={style}>
|
||||||
|
<BackgroundShapes />
|
||||||
|
|
||||||
<div className='z-10 flex flex-col'>
|
<div className='z-10 flex flex-col'>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Layout.Sidebar>
|
<Layout.Sidebar>
|
||||||
{!standalone && <SidebarNavigation />}
|
{!standalone && <SidebarNavigation />}
|
||||||
</Layout.Sidebar>
|
</Layout.Sidebar>
|
||||||
|
|
||||||
<SwitchingColumnsArea>
|
<SwitchingColumnsArea>
|
||||||
{children}
|
{children}
|
||||||
</SwitchingColumnsArea>
|
</SwitchingColumnsArea>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
{me && floatingActionButton}
|
{me && floatingActionButton}
|
||||||
|
|
||||||
<BundleContainer fetchComponent={UploadArea}>
|
<BundleContainer fetchComponent={UploadArea}>
|
||||||
{Component => <Component active={draggingOver} onClose={closeUploadModal} />}
|
{Component => <Component active={draggingOver} onClose={closeUploadModal} />}
|
||||||
</BundleContainer>
|
</BundleContainer>
|
||||||
|
|
||||||
{me && (
|
{me && (
|
||||||
<BundleContainer fetchComponent={SidebarMenu}>
|
<BundleContainer fetchComponent={SidebarMenu}>
|
||||||
|
{Component => <Component />}
|
||||||
|
</BundleContainer>
|
||||||
|
)}
|
||||||
|
{me && features.chats && !mobile && (
|
||||||
|
<BundleContainer fetchComponent={ChatPanes}>
|
||||||
|
{Component => <Component />}
|
||||||
|
</BundleContainer>
|
||||||
|
)}
|
||||||
|
<ThumbNavigation />
|
||||||
|
|
||||||
|
<BundleContainer fetchComponent={ProfileHoverCard}>
|
||||||
{Component => <Component />}
|
{Component => <Component />}
|
||||||
</BundleContainer>
|
</BundleContainer>
|
||||||
)}
|
|
||||||
{me && features.chats && !mobile && (
|
<BundleContainer fetchComponent={StatusHoverCard}>
|
||||||
<BundleContainer fetchComponent={ChatPanes}>
|
|
||||||
{Component => <Component />}
|
{Component => <Component />}
|
||||||
</BundleContainer>
|
</BundleContainer>
|
||||||
)}
|
</div>
|
||||||
<ThumbNavigation />
|
|
||||||
|
|
||||||
<BundleContainer fetchComponent={ProfileHoverCard}>
|
|
||||||
{Component => <Component />}
|
|
||||||
</BundleContainer>
|
|
||||||
|
|
||||||
<BundleContainer fetchComponent={StatusHoverCard}>
|
|
||||||
{Component => <Component />}
|
|
||||||
</BundleContainer>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</HotKeys>
|
||||||
</HotKeys>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,38 @@
|
||||||
import { QueryClient } from '@tanstack/react-query';
|
import { QueryClient } from '@tanstack/react-query';
|
||||||
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
import * as BuildConfig from 'soapbox/build_config';
|
||||||
|
import { isURL } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
const maybeParseJSON = (data: string) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (Exception) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const API = axios.create({
|
||||||
|
transformResponse: [maybeParseJSON],
|
||||||
|
});
|
||||||
|
|
||||||
|
const useAxiosInterceptors = (token: string, baseApiUri: string) => {
|
||||||
|
API.interceptors.request.use(
|
||||||
|
async (config: AxiosRequestConfig) => {
|
||||||
|
if (token) {
|
||||||
|
config.baseURL = isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : baseApiUri;
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
config.headers = {
|
||||||
|
...config.headers,
|
||||||
|
Authorization: (token ? `Bearer ${token}` : null) as string | number | boolean | string[] | undefined,
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => Promise.reject(error),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
|
@ -10,4 +44,4 @@ const queryClient = new QueryClient({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export { queryClient };
|
export { API as default, queryClient, useAxiosInterceptors };
|
||||||
|
|
|
@ -38,7 +38,7 @@ const getSessionUser = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const sessionUser = getSessionUser();
|
const sessionUser = getSessionUser();
|
||||||
const localState = fromJS(JSON.parse(localStorage.getItem(STORAGE_KEY)));
|
export const localState = fromJS(JSON.parse(localStorage.getItem(STORAGE_KEY)));
|
||||||
|
|
||||||
// Checks if the user has an ID and access token
|
// Checks if the user has an ID and access token
|
||||||
const validUser = user => {
|
const validUser = user => {
|
||||||
|
|
Ładowanie…
Reference in New Issue