diff --git a/packages/design-system/organisms/app-shell/AppShell.tsx b/packages/design-system/organisms/app-shell/AppShell.tsx index c724e4e..bfb2e94 100644 --- a/packages/design-system/organisms/app-shell/AppShell.tsx +++ b/packages/design-system/organisms/app-shell/AppShell.tsx @@ -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} /> ) : ( diff --git a/packages/design-system/organisms/masthead/Authed.tsx b/packages/design-system/organisms/masthead/Authed.tsx index bb21c09..d25500e 100644 --- a/packages/design-system/organisms/masthead/Authed.tsx +++ b/packages/design-system/organisms/masthead/Authed.tsx @@ -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)} > - {() => } + {() => ( + + )} diff --git a/packages/web/src/contexts/app-shell/AppShellContext.tsx b/packages/web/src/contexts/app-shell/AppShellContext.tsx new file mode 100644 index 0000000..13432af --- /dev/null +++ b/packages/web/src/contexts/app-shell/AppShellContext.tsx @@ -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({ + 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 ( + + {props.children} + + ); +}; diff --git a/packages/web/src/contexts/recent-guilds/RecentGuildsContext.tsx b/packages/web/src/contexts/recent-guilds/RecentGuildsContext.tsx new file mode 100644 index 0000000..3319e50 --- /dev/null +++ b/packages/web/src/contexts/recent-guilds/RecentGuildsContext.tsx @@ -0,0 +1,59 @@ +import * as React from 'react'; + +type RecentGuildsT = { + recentGuilds: string[]; + pushRecentGuild: (id: string) => void; +}; + +export const RecentGuilds = React.createContext({ + 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(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 ( + + {props.children} + + ); +}; diff --git a/packages/web/src/index.tsx b/packages/web/src/index.tsx index 7d33083..1b9b869 100644 --- a/packages/web/src/index.tsx +++ b/packages/web/src/index.tsx @@ -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) => {acc}, + <>{props.children} + ); +}; + ReactDOM.render( - - - - - + + + , document.getElementById('root') ); diff --git a/packages/web/src/pages/picker.tsx b/packages/web/src/pages/picker.tsx index 707bd37..d64b207 100644 --- a/packages/web/src/pages/picker.tsx +++ b/packages/web/src/pages/picker.tsx @@ -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( 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 ;