Skip to content
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

feat: event & filter rating #19

Merged
merged 5 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions src/components/circle/FilterDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { useGetWorkType } from '@/hooks/circle/useGetWorkType';
import { prettifyError } from '@/utils/helper';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { CircleFilterWithNoSearch } from '@/types/circle';
import { CircleFilterWithNoSearch, RATING_ENUM } from '@/types/circle';
import { useParseCircleQueryToParams } from '@/hooks/circle/useParseClientQueryToParams';

const WorkTypeSection = () => {
Expand Down Expand Up @@ -224,25 +224,54 @@ const MainFilter = () => {
(state) => state.setDrawerFilterStep,
);
const filterForm = useFormContext<CircleFilterWithNoSearch>();
const { filter } = useParseCircleQueryToParams();

return (
<>
{!!filter.event && (
<Controller
control={filterForm.control}
name="day"
render={({ field: { disabled, onChange, ...fields } }) => {
return (
<RadioGroup
label={<div className="font-medium">Day</div>}
orientation="horizontal"
isDisabled={disabled}
{...fields}
onValueChange={(a) => onChange(a)}
>
<Radio value="first">First Day</Radio>
<Radio value="second">Second Day</Radio>
<Radio value="both">Both Days</Radio>
</RadioGroup>
);
}}
/>
)}

<Controller
control={filterForm.control}
name="day"
render={({ field: { disabled, onChange, ...fields } }) => {
name="rating"
render={({ field }) => {
return (
<RadioGroup
label={<div className="font-medium">Day</div>}
orientation="horizontal"
isDisabled={disabled}
{...fields}
onValueChange={(a) => onChange(a)}
<CheckboxGroup
name={field.name}
ref={field.ref}
value={(field?.value ?? []).map(String)}
onChange={(val) => field.onChange(val)}
label={<div className="font-medium">Rating</div>}
>
<Radio value="first">First Day</Radio>
<Radio value="second">Second Day</Radio>
<Radio value="both">Both Days</Radio>
</RadioGroup>
<div className="flex gap-2">
{RATING_ENUM.map((x) => {
return (
<Checkbox key={x} value={x}>
{x}
</Checkbox>
);
})}
</div>
</CheckboxGroup>
);
}}
/>
Expand Down Expand Up @@ -318,6 +347,7 @@ const FilterDrawer = () => {
day: filter.day,
fandom_id: filter.fandom_id,
work_type_id: filter.work_type_id,
rating: filter.rating,
});
}
}, [open]);
Expand All @@ -343,6 +373,7 @@ const FilterDrawer = () => {
day: '',
fandom_id: [],
work_type_id: [],
rating: [],
},
{
keepTouched: true,
Expand Down
4 changes: 2 additions & 2 deletions src/components/circle/detail-page/EditGeneralInfoSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { uploadService } from '@/services/upload';
import {
EditGeneralInfoPayload,
editGeneralInfoPayload,
ratingEnum,
RATING_ENUM,
updateCirclePayload,
} from '@/types/circle';
import { prettifyError } from '@/utils/helper';
Expand Down Expand Up @@ -198,7 +198,7 @@ function EditGeneralInfoSection() {
errorMessage={formState.errors[field.name]?.message}
selectionMode="single"
>
{ratingEnum.map((id) => {
{RATING_ENUM.map((id) => {
return (
<SelectItem
key={id}
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/circle/useParseClientQueryToParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const CIRCLE_FILTER_CLIENT_PARAMS_INITIAL_VALUE: GetCircleQueryParamsClie
fandom_id: [],
work_type_id: [],
search: '',
rating: [],
event: '',
};

export const useParseCircleQueryToParams = () => {
Expand All @@ -21,7 +23,7 @@ export const useParseCircleQueryToParams = () => {
}

const isActive = Object.entries(params).some(([key, values]) => {
return key !== 'search' && values.length > 0;
return key !== 'search' && key !== 'event' && values.length > 0;
});
return {
filter: params,
Expand Down
23 changes: 19 additions & 4 deletions src/hooks/event/useGetEvent.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
import { eventService } from '@/services/event';
import { useQuery } from '@tanstack/react-query';
import { UndefinedInitialDataOptions, useQuery } from '@tanstack/react-query';
type QueryFnData = Awaited<ReturnType<typeof eventService.getEvents>>['data'];
type Options = Omit<UndefinedInitialDataOptions<QueryFnData>, 'queryKey'>;
type PickedOptions = Partial<
Pick<
Options,
| 'refetchOnMount'
| 'refetchOnReconnect'
| 'refetchOnWindowFocus'
| 'enabled'
| 'staleTime'
>
>;

export const useGetEvents = (
params: Parameters<typeof eventService.getEvents>[0],
) => {
type Params = Parameters<typeof eventService.getEvents>[0];

export const useGetEvents = (params: Params, options?: PickedOptions) => {
return useQuery({
queryKey: ['/v1/event', params],
queryFn: async () => (await eventService.getEvents(params)).data,
retry: 0,
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
...options,
});
};
64 changes: 63 additions & 1 deletion src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import MegaphoneIcon from '@/icons/MegaphoneIcon';
import { classNames } from '@/utils/classNames';
import TVIcon from '@/icons/TVIcon';
import { useSession } from '@/components/providers/SessionProvider';
import { useGetEvents } from '@/hooks/event/useGetEvent';
import { useRouter } from 'next/router';
import XMarkIcon from '@/icons/XMarkIcon';

const FilterDrawer = dynamic(() => import('@/components/circle/FilterDrawer'), {
ssr: false,
Expand Down Expand Up @@ -168,6 +171,63 @@ const JoinAsCircleCTA = () => {
);
};

const EventChipsFilter = () => {
const { data, error } = useGetEvents(
{ limit: 2, page: 1 },
{ staleTime: Infinity },
);
const router = useRouter();
const { filter } = useParseCircleQueryToParams();
if (!data || data.length === 0 || error) return null;
return (
<ul className="mb-4 mt-0 flex h-full w-full max-w-full items-center gap-2 overflow-x-auto overflow-y-hidden">
{data.map((ev) => {
const isSelected = router.query.event === ev.slug;
return (
<Link
className={classNames(
'whitespace-nowrap border-[1.5px] border-neutral-200 bg-slate-50 px-3 py-1 font-medium text-neutral-500 transition-all active:scale-90',

isSelected
? 'rounded-[50px] border-neutral-500 bg-primary font-semibold text-white'
: 'rounded-lg hover:bg-slate-200',
)}
key={ev.id}
href={{
query: {
...router.query,
event: ev.slug,
},
}}
shallow
>
<li> {ev.name}</li>
</Link>
);
})}
{!!router.query.event && (
<button
className="rounded-full p-0.5 transition-all delay-200 hover:bg-danger hover:text-white active:scale-90"
type="button"
onClick={() => {
if (!!filter.day) delete router.query.day;
delete router.query.event;
router.push(
{
query: router.query,
},
undefined,
{ shallow: true },
);
}}
>
<XMarkIcon width={16} height={16} />
</button>
)}
</ul>
);
};

export default function Home() {
const setOpen = useDrawerFilterStore((state) => state.setDrawerFilterIsOpen);
const { isActive } = useParseCircleQueryToParams();
Expand All @@ -179,7 +239,7 @@ export default function Home() {
<h1 className="text-xl font-bold">Discover Circles</h1>
<WarningDev />
</div>
<div className="mb-6 mt-2 flex w-full items-center gap-2">
<div className="mb-4 mt-2 flex w-full items-center gap-2">
<SearchInput />
<button
type="button"
Expand All @@ -195,6 +255,8 @@ export default function Home() {
</button>
<FilterDrawer />
</div>
<EventChipsFilter />

<CircleListGrid />
</EachPageLayout>
);
Expand Down
4 changes: 2 additions & 2 deletions src/pages/join.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Spin from '@/components/general/Spin';
import { useSession } from '@/components/providers/SessionProvider';
import { useOnboarding } from '@/hooks/circle/useOnboarding';
import MegaphoneIcon from '@/icons/MegaphoneIcon';
import { onboardingPayloadSchema, ratingEnum } from '@/types/circle';
import { onboardingPayloadSchema, RATING_ENUM } from '@/types/circle';
import { zodResolver } from '@hookform/resolvers/zod';
import { Button, Input, Select, SelectItem } from '@nextui-org/react';
import { useRouter } from 'next/router';
Expand Down Expand Up @@ -115,7 +115,7 @@ function JoinPage() {
<Select
color="primary"
variant="underlined"
items={ratingEnum.map((x) => ({
items={RATING_ENUM.map((x) => ({
id: x,
name: x,
}))}
Expand Down
31 changes: 25 additions & 6 deletions src/types/circle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
import { ACCEPTED_IMAGE_TYPES, FIVE_MB } from '@/constants/common';
import { eventEntity } from './event';

export const ratingEnum = ['GA', 'PG', 'M'] as const;
export const ratingEnumSchema = z.enum(ratingEnum, {
export const RATING_ENUM = ['GA', 'PG', 'M'] as const;
export const ratingEnumSchema = z.enum(RATING_ENUM, {
message: 'Rating is required',
});

Expand All @@ -31,6 +31,12 @@ export const circlesQueryParamsClient = z.object({
.or(z.literal(''))
.default('')
.optional(),
rating: z
.array(ratingEnumSchema)
.or(ratingEnumSchema.transform((x) => [x]))
.default([]),

event: trimmedString.optional(),
});

export type GetCircleQueryParamsClient = z.infer<
Expand Down Expand Up @@ -82,15 +88,28 @@ export const circleSchema = circleEntity.extend({
event: eventEntity.nullable(),
});

const circles = circleSchema.omit({ event: true });

export type CircleCard = z.infer<typeof circles>;
export type CircleCard = z.infer<typeof circlePaginationSchema>;

export const onboardCircleResponse = backendResponseSchema(circleEntity);
export const getOneCircleResponse = backendResponseSchema(
circleSchema.omit({ event_id: true }),
);
export const getCirclesResponse = backendResponsePagination(circles);

export const circlePaginationSchema = circleEntity
.omit({ description: true })
.extend({
block: circleBlockEntity.nullable(),
bookmarked: z.boolean(),
work_type: z.array(fandomWorkTypeBaseEntity),
fandom: z.array(fandomWorkTypeBaseEntity),
event: eventEntity
.omit({ description: true, ended_at: true, started_at: true })
.nullable(),
});

export const getCirclesResponse = backendResponsePagination(
circlePaginationSchema,
);

export const onboardingPayloadSchema = z.object({
name: z
Expand Down