-
Notifications
You must be signed in to change notification settings - Fork 264
Send email when an issue is escalating #1960
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| 'use server' | ||
|
|
||
| import { MembershipsRepository } from '@latitude-data/core/repositories' | ||
| import { updateEscalatingIssuesEmailPreference } from '@latitude-data/core/services/memberships/updateEscalatingIssuesEmailPreference' | ||
| import { z } from 'zod' | ||
|
|
||
| import { authProcedure } from '../procedures' | ||
|
|
||
| export const updateEscalatingIssuesEmailPreferenceAction = authProcedure | ||
| .inputSchema(z.object({ wantToReceive: z.boolean() })) | ||
| .action( | ||
| async ({ parsedInput: { wantToReceive }, ctx: { workspace, user } }) => { | ||
| const membershipsScope = new MembershipsRepository(workspace.id) | ||
| const membership = await membershipsScope | ||
| .findByUserId(user.id) | ||
| .then((r) => r.unwrap()) | ||
|
|
||
| return await updateEscalatingIssuesEmailPreference({ | ||
| membership, | ||
| wantToReceive, | ||
| userEmail: user.email, | ||
| }).then((r) => r.unwrap()) | ||
| }, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| 'use server' | ||
|
|
||
| import { MembershipsRepository } from '@latitude-data/core/repositories' | ||
| import { updateWeeklyEmailPreference } from '@latitude-data/core/services/memberships/updateWeeklyEmailPreference' | ||
| import { z } from 'zod' | ||
|
|
||
| import { authProcedure } from '../procedures' | ||
|
|
||
| export const updateWeeklyEmailPreferenceAction = authProcedure | ||
| .inputSchema(z.object({ wantToReceive: z.boolean() })) | ||
| .action( | ||
| async ({ parsedInput: { wantToReceive }, ctx: { workspace, user } }) => { | ||
| const membershipsScope = new MembershipsRepository(workspace.id) | ||
| const membership = await membershipsScope | ||
| .findByUserId(user.id) | ||
| .then((r) => r.unwrap()) | ||
|
|
||
| return await updateWeeklyEmailPreference({ | ||
| membership, | ||
| wantToReceive, | ||
| userEmail: user.email, | ||
| }).then((r) => r.unwrap()) | ||
| }, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { Metadata } from 'next' | ||
| import buildMetatags from '$/app/_lib/buildMetatags' | ||
| import NotificationsModal from '$/components/Notifications/Modal' | ||
|
|
||
| export const metadata: Promise<Metadata> = buildMetatags({ | ||
| title: 'Notifications', | ||
| locationDescription: 'Email Notifications', | ||
| }) | ||
|
|
||
| export default function NotificationsModalPage() { | ||
| return <NotificationsModal /> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export default function Default() { | ||
| return null | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| 'use client' | ||
|
|
||
| import { ROUTES } from '$/services/routes' | ||
| import { Text } from '@latitude-data/web-ui/atoms/Text' | ||
| import { useOnce } from '$/hooks/useMount' | ||
| import { switchWorkspaceAction } from '$/actions/workspaces/switch' | ||
| import useLatitudeAction from '$/hooks/useLatitudeAction' | ||
| import { useToast } from '@latitude-data/web-ui/atoms/Toast' | ||
|
|
||
| /** | ||
| * We do this here because next.js page can't change server cookies on the fly | ||
| * so we need to have a client component that calls the action to switch workspace | ||
| * and then redirect to the notifications page. | ||
| */ | ||
| export function WorkspaceSwitcherRedirect({ | ||
| targetWorkspaceId, | ||
| }: { | ||
| targetWorkspaceId: number | ||
| }) { | ||
| const { toast } = useToast() | ||
| const { execute: switchWorkspace, isPending: isSwitching } = | ||
| useLatitudeAction(switchWorkspaceAction) | ||
|
|
||
| useOnce(() => { | ||
| async function performSwitch() { | ||
| const [, error] = await switchWorkspace({ | ||
| workspaceId: targetWorkspaceId, | ||
| redirectTo: ROUTES.dashboard.notifications.root, | ||
| }) | ||
|
|
||
| if (error) { | ||
|
Contributor
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. why not add this as the onFailure of the action? |
||
| toast({ | ||
| title: 'Failed to switch workspace', | ||
| description: error.message, | ||
| variant: 'destructive', | ||
| }) | ||
|
|
||
| return | ||
| } | ||
| } | ||
|
|
||
| performSwitch() | ||
| }) | ||
|
|
||
| return ( | ||
| <div className='flex items-center justify-center min-h-screen'> | ||
| <div className='flex flex-col items-center gap-4'> | ||
| <Text.H4> | ||
| {isSwitching | ||
| ? 'Switching workspace...' | ||
| : 'Redirecting to notifications...'} | ||
| </Text.H4> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { notFound, redirect } from 'next/navigation' | ||
| import { getCurrentUserOrRedirect } from '$/services/auth/getCurrentUser' | ||
| import { WorkspaceSwitcherRedirect } from './_components/WorkspaceSwitcherRedirect' | ||
| import { ROUTES } from '$/services/routes' | ||
| import { WorkspacesRepository } from '@latitude-data/core/repositories' | ||
| import { Result } from '@latitude-data/core/lib/Result' | ||
|
|
||
| /** | ||
| * Handle notifications page by workspace id | ||
| * | ||
| * 1. If this workspace is valid we find if the user in the current session | ||
| * (if any) has a membership in that workspace. If not current session we | ||
| * redirect to login page with `redirectTo` param set to this page. | ||
| * | ||
| * 2. If the user has a membership in that workspace but is not the current | ||
| * workspace we render workspace switcher redirect component to switch to that | ||
| * so frontend can set the right session in cookies. | ||
| * | ||
| * 3. If the user has membership to that workspace and it's the current workspace | ||
| * we redirect to the default notifications page | ||
|
Contributor
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. tremenda logica |
||
| * | ||
| */ | ||
| export default async function NotificationsByWorkspacePage({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ workspaceId: string }> | ||
| }) { | ||
| const { user: currentUser, workspace: currentWorkspace } = | ||
| await getCurrentUserOrRedirect() | ||
|
|
||
| const paramsResolved = await params | ||
| const workspaceId = parseInt(paramsResolved.workspaceId, 10) | ||
| const workspaceRepo = new WorkspacesRepository(currentUser.id) | ||
| const workspaceResult = await workspaceRepo.find(workspaceId) | ||
|
|
||
| if (!Result.isOk(workspaceResult)) return notFound() | ||
|
|
||
| const workspace = workspaceResult.value | ||
| if (workspace.id === currentWorkspace.id) { | ||
| return redirect(ROUTES.dashboard.notifications.root) | ||
| } | ||
|
|
||
| return <WorkspaceSwitcherRedirect targetWorkspaceId={workspace.id} /> | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { Metadata } from 'next' | ||
| import NotificationsModal from '$/components/Notifications/Modal' | ||
| import { metadata as modalMetadata } from '../../notifications/page' | ||
| import { ROUTES } from '$/services/routes' | ||
|
|
||
| export const metadata: Promise<Metadata> = modalMetadata | ||
|
|
||
| export default function DashboardNotificationsPage() { | ||
| return <NotificationsModal route={ROUTES.dashboard.root} /> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { Metadata } from 'next' | ||
| import buildMetatags from '$/app/_lib/buildMetatags' | ||
|
|
||
| import Notifications from '$/components/Notifications' | ||
| import { FocusLayout } from '$/components/layouts' | ||
|
|
||
| export const metadata: Promise<Metadata> = buildMetatags({ | ||
| title: 'Notifications', | ||
| locationDescription: 'Email Notifications', | ||
| }) | ||
|
|
||
| export default function NotificationsPage() { | ||
| return ( | ||
| <FocusLayout> | ||
| <Notifications /> | ||
| </FocusLayout> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,7 +60,7 @@ export function IssuesDetailPanel({ | |
|
|
||
| return ( | ||
| <DetailsPanel bordered ref={ref}> | ||
| <div className='relative w-full overflow-hidden'> | ||
| <div className='relative w-full overflow-hidden custom-scrollbar'> | ||
|
Contributor
Author
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. Fix sliding horizontal overflow |
||
| {/* === SLIDING PANEL WRAPPER === */} | ||
| <div | ||
| className={cn( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used on the normal switch selector and now on the swich redirect for the notifications