Skip to content

Instantly share code, notes, and snippets.

@mrmartineau
Created October 11, 2023 12:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrmartineau/72200126235114d4799f65a5e588cfbb to your computer and use it in GitHub Desktop.
Save mrmartineau/72200126235114d4799f65a5e588cfbb to your computer and use it in GitHub Desktop.
Supabase / Next 13 user prefs question
export default async function AppLayout({ children }: LayoutProps) {
const supabaseClient = createServerComponentClient<Database>({ cookies });
const {
data: { user },
} = await supabaseClient.auth.getUser();
const userProfile = await supabaseClient
.from('profiles')
.select('*')
.match({ id: user?.id })
.single();
return (
<UserProvider profile={userProfile?.data as UserProfile} id={user?.id}>
{children}
</UserProvider>
);
}
export const useRealtimeProfile = (initialData: UserProfile | null) => {
const [profile, setProfile] = useState<UserProfile | null>(initialData);
const supabaseClient = createClientComponentClient<Database>();
useEffect(() => {
setProfile(initialData);
}, [initialData]);
useEffect(() => {
const channel = supabaseClient
.channel('realtime profile')
.on(
'postgres_changes',
{ event: 'UPDATE', schema: 'public', table: 'profiles' },
(payload) => {
setProfile(payload.new as UserProfile);
},
)
.subscribe();
return () => {
supabaseClient.removeChannel(channel);
};
}, [supabaseClient, setProfile, profile]);
return profile;
};
'use client';
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import { ReactNode, createContext, useCallback, useContext } from 'react';
import { useRealtimeProfile } from '../hooks/useRealtime';
import { UserProfile } from '../types/db';
export type UseUpdateReturn = (action: UIStateAction) => void;
type UIState = UserProfile['settings'];
export type UIStateAction =
| { type: 'pinnedTagAdd'; payload: string }
| { type: 'pinnedTagRemove'; payload: string }
| { type: 'tags'; payload: boolean }
| { type: 'types'; payload: boolean }
| { type: 'groupByDate' }
| {
type: 'topTags';
payload: {
limit: number;
active: boolean;
};
};
interface UserContextType {
profile: UserProfile | null;
settings: UserProfile['settings'];
id: string | undefined;
handleUpdateUISettings: UseUpdateReturn;
}
const UserContext = createContext<UserContextType | null>(null);
export const useUser = () => {
const userContext = useContext(UserContext);
if (!userContext) {
throw new Error('useUser has to be used within <UserContext.Provider>');
}
return userContext;
};
export const UIStateReducer = (
state: UIState,
action: UIStateAction,
): UIState => {
switch (action.type) {
case 'pinnedTagAdd':
return {
...state,
uiState: {
...state.uiState,
pinnedTags: [...state.uiState.pinnedTags, action.payload],
},
};
case 'pinnedTagRemove':
return {
...state,
uiState: {
...state.uiState,
pinnedTags: state.uiState.pinnedTags.filter(
(item) => item !== action.payload,
),
},
};
case 'tags':
return {
...state,
uiState: {
...state.uiState,
tags: action.payload,
},
};
case 'types':
return {
...state,
uiState: {
...state.uiState,
types: action.payload,
},
};
case 'groupByDate':
return {
...state,
uiState: {
...state.uiState,
groupByDate: !state.uiState.groupByDate,
},
};
case 'topTags':
return {
...state,
uiState: {
...state.uiState,
topTags: action.payload.active,
topTagsLimit: action.payload.limit,
},
};
default:
return state;
}
};
interface UserProviderProps extends Pick<UserContextType, 'profile' | 'id'> {
children: ReactNode;
}
export const UserProvider = ({ children, id, profile }: UserProviderProps) => {
const realtimeProfile = useRealtimeProfile(profile);
const supabaseClient = createClientComponentClient();
const handleUpdateUISettings = useCallback(
async (action: UIStateAction) => {
if (realtimeProfile?.settings) {
const newSettings = UIStateReducer(realtimeProfile.settings, action);
await supabaseClient
.from('profiles')
.update({ settings: newSettings })
.match({ id })
.single();
}
},
[id, realtimeProfile?.settings],
);
return (
<UserContext.Provider
value={{
id,
profile: realtimeProfile,
// @ts-ignore
settings: realtimeProfile?.settings,
handleUpdateUISettings,
}}
>
{children}
</UserContext.Provider>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment