Created
October 11, 2023 12:01
-
-
Save mrmartineau/72200126235114d4799f65a5e588cfbb to your computer and use it in GitHub Desktop.
Supabase / Next 13 user prefs question
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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