Skip to content

Commit

Permalink
feat(Filter): star/unstar not owned filters
Browse files Browse the repository at this point in the history
  • Loading branch information
awinogradov committed Apr 24, 2023
1 parent 7c31300 commit ff51e2f
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 38 deletions.
59 changes: 49 additions & 10 deletions graphql/resolvers/Filter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { arg, nonNull } from 'nexus';
import { ObjectDefinitionBlock } from 'nexus/dist/core';

import { Filter, FilterCreateInput, FilterInput } from '../types';
import { Filter, FilterCreateInput, FilterInput, Activity, SubscriptionToggleInput } from '../types';
import { connectionMap } from '../queries/connections';

export const query = (t: ObjectDefinitionBlock<'Query'>) => {
t.field('filter', {
Expand All @@ -12,15 +13,22 @@ export const query = (t: ObjectDefinitionBlock<'Query'>) => {
resolve: async (_, { data }, { db, activity }) => {
if (!activity) return null;

try {
return db.filter.findUnique({
where: {
id: data.id,
},
});
} catch (error) {
throw Error(`${error}`);
}
const filter = await db.filter.findUnique({
where: {
id: data.id,
},
include: {
stargizers: true,
},
});

if (!filter) return null;

return {
...filter,
_isOwner: filter?.activityId === activity.id,
_isStarred: filter?.stargizers?.some((stargizer) => stargizer?.id === activity.id),
};
},
});
};
Expand All @@ -47,6 +55,37 @@ export const mutation = (t: ObjectDefinitionBlock<'Mutation'>) => {
},
});

t.field('toggleFilterStargizer', {
type: Activity,
args: {
data: nonNull(arg({ type: SubscriptionToggleInput })),
},
resolve: async (_, { data: { id, direction } }, { db, activity }) => {
if (!activity) return null;

const connection = { id };

try {
return db.activity.update({
where: { id: activity.id },
data: {
filterStargizers: { [connectionMap[String(direction)]]: connection },
},
});

// await mailServer.sendMail({
// from: `"Fred Foo 👻" <${process.env.MAIL_USER}>`,
// to: '[email protected], [email protected]',
// subject: 'Hello ✔',
// text: `new post '${title}'`,
// html: `new post <b>${title}</b>`,
// });
} catch (error) {
throw Error(`${error}`);
}
},
});

t.field('deleteFilter', {
type: Filter,
args: {
Expand Down
3 changes: 3 additions & 0 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ input EstimateInput {
}

type Filter {
_isOwner: Boolean
_isStarred: Boolean
activity: Activity
activityId: String!
createdAt: DateTime!
Expand Down Expand Up @@ -231,6 +233,7 @@ type Mutation {
deleteComment(data: CommentDeleteInput!): Comment
deleteFilter(data: FilterInput!): Filter
deleteProject(data: ProjectDelete!): Project
toggleFilterStargizer(data: SubscriptionToggleInput!): Activity
toggleGoalArchive(data: GoalArchiveInput!): Activity
toggleGoalDependency(data: GoalDependencyToggleInput!): Goal
toggleGoalStargizer(data: SubscriptionToggleInput!): Activity
Expand Down
4 changes: 4 additions & 0 deletions graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ export const Filter = objectType({
t.list.field('stargizers', { type: Activity });
t.field(FilterModel.createdAt);
t.field(FilterModel.updatedAt);

// calculated fields
t.boolean('_isStarred');
t.boolean('_isOwner');
},
});

Expand Down
5 changes: 3 additions & 2 deletions src/components/FiltersPanel/FiltersPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,14 @@ export const FiltersPanel: React.FC<FiltersPanelProps> = ({
/>
))}

{Boolean(queryString) && !currentPreset && (
{((Boolean(queryString) && !currentPreset) ||
(currentPreset && !currentPreset._isOwner && !currentPreset._isStarred)) && (
<StyledFiltersAction onClick={onFilterStar}>
<StarIcon size="s" noWrap />
</StyledFiltersAction>
)}

{currentPreset && (
{currentPreset && (currentPreset._isOwner || currentPreset._isStarred) && (
<StyledFiltersAction onClick={onFilterStar}>
<StarFilledIcon size="s" noWrap />
</StyledFiltersAction>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
"Voila! Saved successfully 🎉! Use and share it with teammates 😉": "Voila! Saved successfully 🎉! Use and share it with teammates 😉",
"Something went wrong 😿": "Something went wrong 😿",
"We are deleting your filter...": "We are deleting your filter...",
"Deleted successfully 🎉!": "Deleted successfully 🎉!"
"Deleted successfully 🎉!": "Deleted successfully 🎉!",
"We are calling owner...": "We are calling owner...",
"So sad! We will miss you": "So sad! We will miss you",
"Voila! You are stargizer now 🎉": "Voila! You are stargizer now 🎉"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
"Voila! Saved successfully 🎉! Use and share it with teammates 😉": "Ура! Используй сам и делись с другими членами команды 😉",
"Something went wrong 😿": "Кажется что-то пошло не по плану 😿. Мы уже в курсе и чиним проблему. Прости 🙏",
"We are deleting your filter...": "Удаляем твой фильтр...",
"Deleted successfully 🎉!": "Удалили 🎉!"
"Deleted successfully 🎉!": "Удалили 🎉!",
"We are calling owner...": "Звоним владельцу...",
"So sad! We will miss you": "Как грустно! Мы будем скучать",
"Voila! You are stargizer now 🎉": "Ура! Добавили в избранное 🎉"
}
52 changes: 39 additions & 13 deletions src/components/pages/GoalsPage/GoalsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import { nullable, Button } from '@taskany/bricks';
import { Filter, Goal, GoalsMetaOutput, Project } from '../../../../graphql/@generated/genql';
import { createFetcher, refreshInterval } from '../../../utils/createFetcher';
import { declareSsrProps, ExternalPageProps } from '../../../utils/declareSsrProps';
import { ModalEvent, dispatchModalEvent } from '../../../utils/dispatchModal';
import { createFilterKeys } from '../../../utils/hotkeys';
import { parseFilterValues, useUrlFilterParams } from '../../../hooks/useUrlFilterParams';
import { useFilterResource } from '../../../hooks/useFilterResource';
import { Priority } from '../../../types/priority';
import { Page, PageContent } from '../../Page';
import { CommonHeader } from '../../CommonHeader';
import { FiltersPanel } from '../../FiltersPanel/FiltersPanel';
import { parseFilterValues, useUrlFilterParams } from '../../../hooks/useUrlFilterParams';
import { GoalsGroup, GoalsGroupProjectTitle } from '../../GoalsGroup';
import { ModalEvent, dispatchModalEvent } from '../../../utils/dispatchModal';
import { createFilterKeys } from '../../../utils/hotkeys';
import { PageTitle } from '../../PageTitle';

import { tr } from './GoalsPage.i18n';
Expand Down Expand Up @@ -157,23 +158,29 @@ const filterFetcher = createFetcher((_, id = '') => ({
description: true,
mode: true,
params: true,
_isOwner: true,
_isStarred: true,
},
],
}));

export const getServerSideProps = declareSsrProps(
async ({ user, query }) => {
const { filter: preset } = query.filter ? await filterFetcher(user, query.filter) : { filter: null };
const presetData = query.filter ? await filterFetcher(user, query.filter) : { filter: null };

return {
preset,
fallback: {
[unstable_serialize(query)]: await fetcher(
user,
...Object.values(
parseFilterValues(preset ? Object.fromEntries(new URLSearchParams(preset.params)) : query),
parseFilterValues(
presetData.filter
? Object.fromEntries(new URLSearchParams(presetData.filter.params))
: query,
),
),
),
[unstable_serialize(query.filter)]: presetData,
},
};
},
Expand All @@ -182,9 +189,18 @@ export const getServerSideProps = declareSsrProps(
},
);

export const GoalsPage = ({ user, ssrTime, locale, fallback, preset }: ExternalPageProps) => {
export const GoalsPage = ({ user, ssrTime, locale, fallback }: ExternalPageProps) => {
const router = useRouter();
const [preview, setPreview] = useState<Goal | null>(null);
const { toggleFilterStar } = useFilterResource();

const { data: presetData, mutate: presetMutate } = useSWR(
unstable_serialize(router.query.filter),
(f: string) => filterFetcher(user, f),
{
fallback,
},
);

const {
currentPreset,
Expand All @@ -201,7 +217,7 @@ export const GoalsPage = ({ user, ssrTime, locale, fallback, preset }: ExternalP
resetQueryState,
setPreset,
} = useUrlFilterParams({
preset,
preset: presetData?.filter,
});

const { data, isLoading } = useSWR(
Expand Down Expand Up @@ -260,11 +276,21 @@ export const GoalsPage = ({ user, ssrTime, locale, fallback, preset }: ExternalP

const selectedGoalResolver = useCallback((id: string) => id === preview?.id, [preview]);

const onFilterStar = useCallback(() => {
currentPreset
? dispatchModalEvent(ModalEvent.FilterDeleteModal)()
: dispatchModalEvent(ModalEvent.FilterCreateModal)();
}, [currentPreset]);
const onFilterStar = useCallback(async () => {
if (currentPreset) {
if (currentPreset._isOwner) {
dispatchModalEvent(ModalEvent.FilterDeleteModal)();
} else {
await toggleFilterStar({
id: currentPreset.id,
direction: !currentPreset._isStarred,
});
await presetMutate();
}
} else {
dispatchModalEvent(ModalEvent.FilterCreateModal)();
}
}, [currentPreset, toggleFilterStar, presetMutate]);

const onFilterCreated = useCallback(
(data: Partial<Filter>) => {
Expand Down
44 changes: 34 additions & 10 deletions src/components/pages/ProjectPage/ProjectPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { FiltersPanel } from '../../FiltersPanel/FiltersPanel';
import { ModalEvent, dispatchModalEvent } from '../../../utils/dispatchModal';
import { parseFilterValues, useUrlFilterParams } from '../../../hooks/useUrlFilterParams';
import { useLocalStorage } from '../../../hooks/useLocalStorage';
import { useFilterResource } from '../../../hooks/useFilterResource';
import { useWillUnmount } from '../../../hooks/useWillUnmount';
import { ProjectPageLayout } from '../../ProjectPageLayout/ProjectPageLayout';
import { Page, PageContent } from '../../Page';
Expand Down Expand Up @@ -224,27 +225,31 @@ const filterFetcher = createFetcher((_, id = '') => ({
description: true,
mode: true,
params: true,
_isOwner: true,
_isStarred: true,
},
],
}));

export const getServerSideProps = declareSsrProps(
async ({ user, params: { id }, query }) => {
const { filter: preset } = query.filter ? await filterFetcher(user, query.filter) : { filter: null };
const presetData = query.filter ? await filterFetcher(user, query.filter) : { filter: null };

const ssrData = await fetcher(
user,
id,
...Object.values(
parseFilterValues(preset ? Object.fromEntries(new URLSearchParams(preset.params)) : query),
parseFilterValues(
presetData.filter ? Object.fromEntries(new URLSearchParams(presetData.filter.params)) : query,
),
),
);

return ssrData.project
? {
preset,
fallback: {
[unstable_serialize(query)]: ssrData,
[unstable_serialize(query.filter)]: presetData,
},
}
: {
Expand All @@ -256,10 +261,19 @@ export const getServerSideProps = declareSsrProps(
},
);

export const ProjectPage = ({ user, locale, ssrTime, fallback, preset, params: { id } }: ExternalPageProps) => {
export const ProjectPage = ({ user, locale, ssrTime, fallback, params: { id } }: ExternalPageProps) => {
const nextRouter = useNextRouter();
const [preview, setPreview] = useState<Goal | null>(null);
const [, setCurrentProjectCache] = useLocalStorage('currentProjectCache', null);
const { toggleFilterStar } = useFilterResource();

const { data: presetData, mutate: presetMutate } = useSWR(
unstable_serialize(nextRouter.query.filter),
(f: string) => filterFetcher(user, f),
{
fallback,
},
);

const {
currentPreset,
Expand All @@ -276,7 +290,7 @@ export const ProjectPage = ({ user, locale, ssrTime, fallback, preset, params: {
resetQueryState,
setPreset,
} = useUrlFilterParams({
preset,
preset: presetData?.filter,
});

const { data, isLoading } = useSWR(
Expand Down Expand Up @@ -352,11 +366,21 @@ export const ProjectPage = ({ user, locale, ssrTime, fallback, preset, params: {
setCurrentProjectCache(null);
});

const onFilterStar = useCallback(() => {
currentPreset
? dispatchModalEvent(ModalEvent.FilterDeleteModal)()
: dispatchModalEvent(ModalEvent.FilterCreateModal)();
}, [currentPreset]);
const onFilterStar = useCallback(async () => {
if (currentPreset) {
if (currentPreset._isOwner) {
dispatchModalEvent(ModalEvent.FilterDeleteModal)();
} else {
await toggleFilterStar({
id: currentPreset.id,
direction: !currentPreset._isStarred,
});
await presetMutate();
}
} else {
dispatchModalEvent(ModalEvent.FilterCreateModal)();
}
}, [currentPreset, toggleFilterStar, presetMutate]);

const onFilterCreated = useCallback(
(data: Partial<Filter>) => {
Expand Down
22 changes: 21 additions & 1 deletion src/hooks/useFilterResource.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { gql } from '../utils/gql';
import { notifyPromise } from '../utils/notifyPromise';
import { CreateFormType } from '../schema/filter';
import { FilterInput } from '../../graphql/@generated/genql';
import { FilterInput, SubscriptionToggleInput } from '../../graphql/@generated/genql';

export const useFilterResource = () => {
const createFilter = (data: CreateFormType) =>
Expand All @@ -23,6 +23,25 @@ export const useFilterResource = () => {
},
);

const toggleFilterStar = (data: SubscriptionToggleInput) =>
notifyPromise(
gql.mutation({
toggleFilterStargizer: [
{
data,
},
{
id: true,
},
],
}),
{
onPending: 'We are calling owner...',
onSuccess: data.direction ? 'Voila! You are stargizer now 🎉' : 'So sad! We will miss you',
onError: 'Something went wrong 😿',
},
);

const deleteFilter = (data: FilterInput) =>
notifyPromise(
gql.mutation({
Expand All @@ -44,6 +63,7 @@ export const useFilterResource = () => {

return {
createFilter,
toggleFilterStar,
deleteFilter,
};
};

0 comments on commit ff51e2f

Please sign in to comment.