- Notifications
You must be signed in to change notification settings - Fork1.1k
feat(site): implement notification ui#14175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
fc282e7997e0d373f847133c9ab0bbc8cbb7227a42788de881262f7be449e3d0154b94aedf1591a2efaea1f363c7412eb91ff09732acde047cc7bdb49564091c62242a0206190efc40d1aedb92786b0052ecbe5fec7ab40c986e51e037423341f550d97dd824399cd6d403eedfb02aec013ccff7858a5aFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| import type { QueryClient, UseMutationOptions } from "react-query"; | ||
| import { API } from "api/api"; | ||
| import type { | ||
| NotificationPreference, | ||
| NotificationTemplate, | ||
| UpdateNotificationTemplateMethod, | ||
| UpdateUserNotificationPreferences, | ||
| } from "api/typesGenerated"; | ||
| export const userNotificationPreferencesKey = (userId: string) => [ | ||
| "users", | ||
| userId, | ||
| "notifications", | ||
| "preferences", | ||
| ]; | ||
| export const userNotificationPreferences = (userId: string) => { | ||
| return { | ||
| queryKey: userNotificationPreferencesKey(userId), | ||
| queryFn: () => API.getUserNotificationPreferences(userId), | ||
| }; | ||
| }; | ||
| export const updateUserNotificationPreferences = ( | ||
| userId: string, | ||
| queryClient: QueryClient, | ||
| ) => { | ||
| return { | ||
| mutationFn: (req) => { | ||
| return API.putUserNotificationPreferences(userId, req); | ||
| }, | ||
| onMutate: (data) => { | ||
| queryClient.setQueryData( | ||
| userNotificationPreferencesKey(userId), | ||
| Object.entries(data.template_disabled_map).map( | ||
| ([id, disabled]) => | ||
| ({ | ||
| id, | ||
| disabled, | ||
| updated_at: new Date().toISOString(), | ||
| }) satisfies NotificationPreference, | ||
| ), | ||
| ); | ||
| }, | ||
| } satisfies UseMutationOptions< | ||
| NotificationPreference[], | ||
| unknown, | ||
| UpdateUserNotificationPreferences | ||
| >; | ||
| }; | ||
| export const systemNotificationTemplatesKey = [ | ||
| "notifications", | ||
| "templates", | ||
| "system", | ||
| ]; | ||
| export const systemNotificationTemplates = () => { | ||
| return { | ||
| queryKey: systemNotificationTemplatesKey, | ||
| queryFn: () => API.getSystemNotificationTemplates(), | ||
| }; | ||
| }; | ||
| export function selectTemplatesByGroup( | ||
| data: NotificationTemplate[], | ||
| ): Record<string, NotificationTemplate[]> { | ||
| const grouped = data.reduce( | ||
| (acc, tpl) => { | ||
| if (!acc[tpl.group]) { | ||
| acc[tpl.group] = []; | ||
| } | ||
| acc[tpl.group].push(tpl); | ||
| return acc; | ||
| }, | ||
| {} as Record<string, NotificationTemplate[]>, | ||
| ); | ||
| // Sort templates within each group | ||
| for (const group in grouped) { | ||
| grouped[group].sort((a, b) => a.name.localeCompare(b.name)); | ||
| } | ||
| // Sort groups by name | ||
| const sortedGroups = Object.keys(grouped).sort((a, b) => a.localeCompare(b)); | ||
| const sortedGrouped: Record<string, NotificationTemplate[]> = {}; | ||
| for (const group of sortedGroups) { | ||
| sortedGrouped[group] = grouped[group]; | ||
| } | ||
| return sortedGrouped; | ||
| } | ||
| export const notificationDispatchMethodsKey = [ | ||
| "notifications", | ||
| "dispatchMethods", | ||
| ]; | ||
| export const notificationDispatchMethods = () => { | ||
| return { | ||
| staleTime: Infinity, | ||
| queryKey: notificationDispatchMethodsKey, | ||
| queryFn: () => API.getNotificationDispatchMethods(), | ||
| }; | ||
| }; | ||
| export const updateNotificationTemplateMethod = ( | ||
| templateId: string, | ||
| queryClient: QueryClient, | ||
| ) => { | ||
| return { | ||
| mutationFn: (req: UpdateNotificationTemplateMethod) => | ||
| API.updateNotificationTemplateMethod(templateId, req), | ||
| onMutate: (data) => { | ||
| const prevData = queryClient.getQueryData<NotificationTemplate[]>( | ||
| systemNotificationTemplatesKey, | ||
| ); | ||
| if (!prevData) { | ||
| return; | ||
| } | ||
| queryClient.setQueryData( | ||
| systemNotificationTemplatesKey, | ||
| prevData.map((tpl) => | ||
| tpl.id === templateId | ||
| ? { | ||
| ...tpl, | ||
| method: data.method, | ||
| } | ||
| : tpl, | ||
| ), | ||
| ); | ||
| }, | ||
| } satisfies UseMutationOptions< | ||
| void, | ||
| unknown, | ||
| UpdateNotificationTemplateMethod | ||
| >; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import EmailIcon from "@mui/icons-material/EmailOutlined"; | ||
| import WebhookIcon from "@mui/icons-material/WebhookOutlined"; | ||
| // TODO: This should be provided by the auto generated types from codersdk | ||
BrunoQuaresma marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| const notificationMethods = ["smtp", "webhook"] as const; | ||
| export type NotificationMethod = (typeof notificationMethods)[number]; | ||
| export const methodIcons: Record<NotificationMethod, typeof EmailIcon> = { | ||
| smtp: EmailIcon, | ||
| webhook: WebhookIcon, | ||
| }; | ||
| export const methodLabels: Record<NotificationMethod, string> = { | ||
| smtp: "SMTP", | ||
| webhook: "Webhook", | ||
| }; | ||
| export const castNotificationMethod = (value: string) => { | ||
| if (notificationMethods.includes(value as NotificationMethod)) { | ||
| return value as NotificationMethod; | ||
| } | ||
| throw new Error( | ||
| `Invalid notification method: ${value}. Accepted values: ${notificationMethods.join( | ||
| ", ", | ||
| )}`, | ||
| ); | ||
| }; | ||
Comment on lines +19 to +29 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Tagging this for now while I look through the other files. I don't fully understand the point of this function, and at the very least, I think that it should be redefined as a type predicate There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. This is just easier to use IMO. Type predicate: constisNotificationMethod=(v:string):v isNotificationMethod=>{}// Usagevalues.map(v=>{if(!isNotificationMethod(v)){thrownewError("v is not valid")}return<Componentmethod={method}/>}) Cast function constcastNotificationMethod=(v:string):NotificationMethod=>{}// Usagevalues.map(v=>{constmethod=castNotificationMethod(v)return<Componentmethod={method}/>}) | ||
Uh oh!
There was an error while loading.Please reload this page.