feat(web): add recent guilds context, and app shell helper context

This commit is contained in:
41666 2021-03-13 18:10:10 -05:00
parent 980f0cee60
commit 8ace9abf63
6 changed files with 132 additions and 7 deletions

View file

@ -13,6 +13,7 @@ export type AppShellProps = {
small?: boolean;
activeGuildId?: string | null;
guilds?: GuildSlug[];
recentGuilds?: string[];
disableGuildPicker?: boolean;
};
@ -26,6 +27,7 @@ export const AppShell = (props: AppShellProps) => (
guilds={props.guilds || []}
activeGuildId={props.activeGuildId || null}
user={props.user}
recentGuilds={props.recentGuilds}
/>
) : (
<Masthead.Guest />

View file

@ -22,6 +22,7 @@ type Props = {
activeGuildId: string | null;
guilds: GuildSlug[];
disableGuildPicker?: boolean;
recentGuilds: string[];
};
export const Authed = (props: Props) => {
@ -65,7 +66,12 @@ export const Authed = (props: Props) => {
preferredWidth={560}
onExit={() => setServerPopoverState(false)}
>
{() => <GuildNav guilds={props.guilds} />}
{() => (
<GuildNav
guilds={props.guilds}
recentGuilds={props.recentGuilds}
/>
)}
</Popover>
</MastheadLeft>
<MastheadRight>

View file

@ -0,0 +1,35 @@
import { AppShellProps } from '@roleypoly/design-system/organisms/app-shell';
import * as React from 'react';
import { useRecentGuilds } from '../recent-guilds/RecentGuildsContext';
import { useSessionContext } from '../session/SessionContext';
type AppShellPropsT = {
user: AppShellProps['user'];
guilds: AppShellProps['guilds'];
recentGuilds: AppShellProps['recentGuilds'];
};
export const AppShellPropsContext = React.createContext<AppShellPropsT>({
user: undefined,
guilds: undefined,
recentGuilds: [],
});
export const useAppShellProps = () => React.useContext(AppShellPropsContext);
export const AppShellPropsProvider = (props: { children: React.ReactNode }) => {
const { session } = useSessionContext();
const { recentGuilds } = useRecentGuilds();
const appShellProps: AppShellPropsT = {
user: session?.user,
guilds: session?.guilds,
recentGuilds,
};
return (
<AppShellPropsContext.Provider value={appShellProps}>
{props.children}
</AppShellPropsContext.Provider>
);
};

View file

@ -0,0 +1,59 @@
import * as React from 'react';
type RecentGuildsT = {
recentGuilds: string[];
pushRecentGuild: (id: string) => void;
};
export const RecentGuilds = React.createContext<RecentGuildsT>({
recentGuilds: [],
pushRecentGuild: () => {},
});
export const useRecentGuilds = () => React.useContext(RecentGuilds);
const saveState = (state: string[]) => {
localStorage.setItem('rp_recent_guilds', JSON.stringify(state));
};
const pullState = (): string[] => {
const rawState = localStorage.getItem('rp_recent_guilds');
if (!rawState) {
return [];
}
try {
return JSON.parse(rawState);
} catch (e) {
console.warn('RecentGuilds failed to re-hydrate saved state', e);
return [];
}
};
export const RecentGuildsProvider = (props: { children: React.ReactNode }) => {
const [recentGuilds, setRecentGuilds] = React.useState<string[]>(pullState());
const recentGuildsData: RecentGuildsT = {
recentGuilds,
pushRecentGuild: (id: string) => {
const nextState = [
id,
...recentGuilds.slice(0, 19).filter((guild) => guild !== id),
];
if (recentGuilds[0] !== id) {
setRecentGuilds(nextState);
}
},
};
React.useEffect(() => {
saveState(recentGuilds);
}, [recentGuilds]);
return (
<RecentGuilds.Provider value={recentGuildsData}>
{props.children}
</RecentGuilds.Provider>
);
};

View file

@ -2,15 +2,32 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { AppRouter } from './app-router/AppRouter';
import { ApiContextProvider } from './contexts/api/ApiContext';
import { AppShellPropsProvider } from './contexts/app-shell/AppShellContext';
import { RecentGuildsProvider } from './contexts/recent-guilds/RecentGuildsContext';
import { SessionContextProvider } from './contexts/session/SessionContext';
const ProviderProvider = (props: {
providerChain: typeof ApiContextProvider[];
children: React.ReactNode;
}) => {
return props.providerChain.reduceRight(
(acc, Provider) => <Provider>{acc}</Provider>,
<>{props.children}</>
);
};
ReactDOM.render(
<React.StrictMode>
<ApiContextProvider>
<SessionContextProvider>
<AppRouter />
</SessionContextProvider>
</ApiContextProvider>
<ProviderProvider
providerChain={[
ApiContextProvider,
SessionContextProvider,
RecentGuildsProvider,
AppShellPropsProvider,
]}
>
<AppRouter />
</ProviderProvider>
</React.StrictMode>,
document.getElementById('root')
);

View file

@ -3,6 +3,7 @@ import { RolePickerTemplate } from '@roleypoly/design-system/templates/role-pick
import { ServerSetupTemplate } from '@roleypoly/design-system/templates/server-setup';
import { PresentableGuild, RoleUpdate, UserGuildPermissions } from '@roleypoly/types';
import * as React from 'react';
import { useRecentGuilds } from '../contexts/recent-guilds/RecentGuildsContext';
import { useSessionContext } from '../contexts/session/SessionContext';
import { makeRoleTransactions } from '../utils/roleTransactions';
@ -12,6 +13,7 @@ type PickerProps = {
const Picker = (props: PickerProps) => {
const { session, authedFetch, isAuthenticated } = useSessionContext();
const { pushRecentGuild } = useRecentGuilds();
const [pickerData, setPickerData] = React.useState<PresentableGuild | null | false>(
null
@ -32,7 +34,11 @@ const Picker = (props: PickerProps) => {
};
fetchPickerData();
}, [props.serverID, authedFetch]);
}, [props.serverID, authedFetch, pushRecentGuild]);
React.useCallback((serverID) => pushRecentGuild(serverID), [pushRecentGuild])(
props.serverID
);
if (!isAuthenticated) {
return <Redirect to={`/auth/login?r=${props.serverID}`} replace />;