- Notifications
You must be signed in to change notification settings - Fork927
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
fc282e7
997e0d3
73f8471
33c9ab0
bbc8cbb
7227a42
788de88
1262f7b
e449e3d
0154b94
aedf159
1a2efae
a1f363c
7412eb9
1ff0973
2acde04
7cc7bdb
4956409
1c62242
a020619
0efc40d
1aedb92
786b005
2ecbe5f
ec7ab40
c986e51
e037423
341f550
d97dd82
4399cd6
d403eed
fb02aec
013ccff
7858a5a
File 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.
Uh oh!
There was an error while loading.Please reload this page.