Skip to content

Commit

Permalink
fix(GoalCreateForm): cannot create goal into archived project
Browse files Browse the repository at this point in the history
  • Loading branch information
LamaEats committed Apr 3, 2024
1 parent eb91050 commit c1a339c
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 13 deletions.
3 changes: 2 additions & 1 deletion src/components/GoalCreateForm/GoalCreateForm.i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"Create and go to the goal page": "Create and go to the goal page",
"Create and open new form for the next goal": "Create and open new form for the next goal",
"Create goal and close form": "Create goal and close form",
"Cancel": "Cancel"
"Cancel": "Cancel",
"Cannot create goal in archived project": "Cannot create goal. Project \"{project} ({key})\" now is archived"
}
3 changes: 2 additions & 1 deletion src/components/GoalCreateForm/GoalCreateForm.i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"Create and go to the goal page": "Создать и перейти на страницу цели",
"Create and open new form for the next goal": "Создать и открыть новую форму для следующей цели",
"Create goal and close form": "Создать цель и закрыть форму",
"Cancel": "Отмена"
"Cancel": "Отмена",
"Cannot create goal in archived project": "Невозможно создать цель. Проект \"{project} ({key})\" теперь не доступен"
}
27 changes: 25 additions & 2 deletions src/components/GoalCreateForm/GoalCreateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ interface GoalCreateFormProps {
personal?: boolean;
}

const preconditionErrorCode = 412;

const GoalCreateForm: React.FC<GoalCreateFormProps> = ({ title, onGoalCreate, personal }) => {
const router = useRouter();
const { user } = usePageContext();
Expand Down Expand Up @@ -79,8 +81,29 @@ const GoalCreateForm: React.FC<GoalCreateFormProps> = ({ title, onGoalCreate, pe

const createGoal = async (form: GoalCommon) => {
setBusy(true);

const res = await goalCreate(form);
const res = await goalCreate(
form,
form.parent != null
? {
PROJECT_IS_ARCHIVED: tr
.raw('Cannot create goal in archived project', {
project: form.parent.title,
key: form.parent.id,
})
.join(''),
}
: undefined,
).catch((error: any) => {
if (error.data.httpStatus === preconditionErrorCode) {
if (form.parent) {
const nextRecentProjectsCache = { ...recentProjectsCache };

delete nextRecentProjectsCache[form.parent.id];

setRecentProjectsCache(nextRecentProjectsCache);
}
}
});

setBusy(false);

Expand Down
6 changes: 5 additions & 1 deletion src/components/NotificationsHub/NotificationsHub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ const NotificationsHub: React.FC = () => {
})
.catch((error) => {
toast.dismiss(id);
toast.error(tr(e.detail.events.onError as I18nKey));
const message =
(e.detail.errorHandler && e.detail.errorHandler(error)) ?? tr(e.detail.events.onError as I18nKey);

toast.error(message);

return [null, error];
});
};
Expand Down
23 changes: 20 additions & 3 deletions src/hooks/useGoalResource.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useMemo } from 'react';
import type { Activity } from '@prisma/client';
import { TRPCClientError } from '@trpc/client';

import { trpc } from '../utils/trpcClient';
import { notifyPromise } from '../utils/notifyPromise';
Expand Down Expand Up @@ -150,12 +151,28 @@ export const useGoalResource = (fields: GoalFields, config?: Configuration) => {
);

const goalCreate = useCallback(
async (form: GoalCommon) => {
async (form: GoalCommon, errorMessages?: Record<string, string>) => {
const promise = createGoalMutation.mutateAsync(form);

notifyPromise(promise, 'goalsCreate');
try {
notifyPromise(promise, 'goalsCreate', (error) => {
if (errorMessages == null) {
return;
}

return promise;
if (error instanceof TRPCClientError) {
const { data } = error;

if ('httpStatus' in data && data.httpStatus === 412) {
return errorMessages.PROJECT_IS_ARCHIVED;
}
}
});

return promise;
} catch (_error) {
/* handle error in `try` block */
}
},
[createGoalMutation],
);
Expand Down
4 changes: 3 additions & 1 deletion src/utils/dispatchNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export interface NotificationsEventPromiseData {
onSuccess?: string;
onError?: string;
};
errorHandler?: (error: any) => string | void;
}

export const dispatchPromisedNotificationsEvent = (
promise: NotificationsEventPromiseData['promise'],
events: NotificationsEventPromiseData['events'],
errorHandler: NotificationsEventPromiseData['errorHandler'],
) => {
window.dispatchEvent(new CustomEvent('notifyPromise', { detail: { promise, events } }));
window.dispatchEvent(new CustomEvent('notifyPromise', { detail: { promise, events, errorHandler } }));
};

export const dispatchErrorNotification = (key: keyof NotificationMap) => {
Expand Down
16 changes: 12 additions & 4 deletions src/utils/notifyPromise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ import { getNotificicationKeyMap, NotificationNamespaces } from '../components/N
import { NotificationsEventPromiseData, dispatchPromisedNotificationsEvent } from './dispatchNotification';

interface NotifyPromise {
<T>(promise: Promise<T>, events: NotificationsEventPromiseData['events']): PromiseLike<[T, null] | [null, T]>;
<T>(promise: Promise<T>, namespace: NotificationNamespaces): PromiseLike<[T, null] | [null, T]>;
<T>(
promise: Promise<T>,
events: NotificationsEventPromiseData['events'],
errorHandler?: (error: any) => string | void,
): PromiseLike<[T, null] | [null, T]>;
<T>(
promise: Promise<T>,
namespace: NotificationNamespaces,
errorHandler?: (error: any) => string | void,
): PromiseLike<[T, null] | [null, T]>;
}

export const notifyPromise: NotifyPromise = (promise, eventsOrNamespace) => {
export const notifyPromise: NotifyPromise = (promise, eventsOrNamespace, errorHandler) => {
let events: NotificationsEventPromiseData['events'];

if (typeof eventsOrNamespace === 'string') {
Expand All @@ -22,7 +30,7 @@ export const notifyPromise: NotifyPromise = (promise, eventsOrNamespace) => {
events = eventsOrNamespace;
}

dispatchPromisedNotificationsEvent(promise, events);
dispatchPromisedNotificationsEvent(promise, events, errorHandler);

return promise.then(
(data) => [data, null],
Expand Down
7 changes: 7 additions & 0 deletions trpc/router/goal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,13 @@ export const goal = router({
return null;
}

if (actualProject.archived) {
throw new TRPCError({
code: 'PRECONDITION_FAILED',
message: `Cannot create goal. Project "${actualProject.title} (${actualProject.id})" now is achived`,
});
}

try {
const newGoal = await createGoal(input, actualProject.id, activityId, role);
await updateProjectUpdatedAt(actualProject.id);
Expand Down

0 comments on commit c1a339c

Please sign in to comment.