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

feature/BQ-282 into [email protected] 🐒 activity card page #61

Open
wants to merge 30 commits into
base: [email protected]
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f364c06
feature/BQ-282 🌊 added activity page draft with mock
danax7 May 4, 2024
616148d
feature/BQ-282 🌊 month presentation fix
danax7 May 5, 2024
eaca7cb
feature/BQ-282 🌊 added static breadcrumbs, added two new variants for…
danax7 May 5, 2024
013b2c6
feature/BQ-282 🌊 bruh fix
danax7 May 6, 2024
90e6cf7
feature/BQ-282 🌊 deleted response log
danax7 May 6, 2024
fc5b41c
feature/BQ-282 🌊 some review fixes
danax7 May 19, 2024
e454c02
feature/BQ-282 🐒 Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΌΠ΅Π΄ΠΈΠ°
Melly5 May 23, 2024
fc83a45
feature/BQ-282 🐒 ΡƒΠ±Ρ€Π°Ρ‚ΡŒ ΠΈΠΊΠΎΠ½ΠΊΡƒ
Melly5 May 23, 2024
1f7377f
feature/BQ-282 🐒 add activity info
Melly5 May 23, 2024
506771c
feature/BQ-282 🐒 fix participant layout for desktop
Melly5 May 24, 2024
6157db2
feature/BQ-282 🐒 add carousel media for mobile activity
Melly5 May 24, 2024
29dbd7b
feature/BQ-282 🐒 add activity page mobile styles, add addresses sched…
Melly5 May 24, 2024
03e261c
Merge branch 'release/1.5.0' into feature/BQ-282
Melly5 May 28, 2024
c8db714
feature/BQ-282 🐒 fix after merge
Melly5 May 28, 2024
5a9aff3
feature/BQ-282 🐒 fix activities logic, add generated types
Melly5 May 28, 2024
751a4e2
feature/BQ-282 🐒 add new page for schedule info
Melly5 May 31, 2024
c88c5fd
feature/BQ-282 🐒 fix address schedule routing and styles
Melly5 May 31, 2024
b1560a0
feature/BQ-282 🐒 fix address schedule time info display
Melly5 May 31, 2024
a070aaf
feature/BQ-282 🐒 fix address schedule desktop styles
Melly5 May 31, 2024
165cbd5
feature/BQ-282 🐒 ffx schedule day calendar style
Melly5 Jun 7, 2024
ee10c1c
feature/BQ-282 🐒 add phone logic, time periods, remove cariusel dots …
Melly5 Jun 8, 2024
13121cb
feature/BQ-282 🐒 fix calendar naming
Melly5 Jun 8, 2024
98f9313
feature/BQ-282 🐒 add desktop app activity layout
Melly5 Jun 8, 2024
6454b31
feature/BQ-282 🐒 fix schedule employee number
Melly5 Jun 8, 2024
1221e33
feature/BQ-282 🐒 fix breadcrumbs and styles
Melly5 Jun 16, 2024
3c133f1
feature/BQ-282 🐒 fix styles, remove isMobile, move to layout
Melly5 Jun 16, 2024
5e5f4b6
feature/BQ-282 🐒 fix app activities logic
Melly5 Jun 29, 2024
1764226
feature/BQ-282 🐒 add metadata
Melly5 Jul 27, 2024
500eae2
feature/BQ-282 🐒 add /activities pages with metadata logic
Melly5 Jul 30, 2024
6ab4e5d
feature/BQ-282 🐒 rename translation
Melly5 Jul 30, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export const ActivitiesSection = async ({ cityId }: ActivitiesSectionProps) => {
<Typography tag='h2' variant='h1' className='text-2xl md:text-[32px]'>
<I18nText path='landing.activities.title' />
</Typography>

<Link href={ROUTES.APP.ACTIVITIES} className={buttonVariants({ variant: 'link' })}>
<Typography tag='p' variant='h6'>
<I18nText path='button.watchAll' />
Expand All @@ -59,7 +58,8 @@ export const ActivitiesSection = async ({ cityId }: ActivitiesSectionProps) => {
<div className='mt-16 flex flex-col items-center justify-center gap-8 md:grid md:grid-cols-2 md:justify-between lg:grid-cols-3'>
{getActivityPublicResponse.rows.map((activity) => (
<ActivityCard>
<ActivityCardImage src={activity.cover} alt={activity.name} />
{/* Π’Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠ΅ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ Π΄ΠΎ ΠΌΠ΅Ρ€ΠΆΠ° Ρ€Π΅Π»ΠΈΠ·Π° */}
<ActivityCardImage src={activity.media[0].url} alt={activity.name} />
<ActivityCardHeader>
<ActivityCardCategory>{activity.category}</ActivityCardCategory>
<ActivityCardName>{activity.name}</ActivityCardName>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const ActivitiesCategories = () => {
const { categories, onCategoryClick } = useActivitiesPage();

return (
<ScrollArea className='w-full space-y-3 whitespace-nowrap'>
<ScrollArea className='space-y-3 whitespace-nowrap'>
<Tabs defaultValue={searchParams.get('category') ?? ''}>
<TabsList className='flex w-full justify-start gap-1 bg-transparent p-0'>
<TabsTrigger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React from 'react';
import { Clock4Icon, UserRoundIcon } from 'lucide-react';
import Link from 'next/link';
import { useIntersectionObserver } from 'usehooks-ts';

import { I18nText } from '@/components/common';
Expand All @@ -13,8 +14,10 @@ import {
ActivityCardDivider,
ActivityCardHeader,
ActivityCardImage,
ActivityCardName
ActivityCardName,
Typography
} from '@/components/ui';
import { ROUTES } from '@/utils/constants';

import { useActivitiesPage } from '../../(contexts)/activitiesPage';

Expand All @@ -31,32 +34,51 @@ export const ActivityList = () => {

return (
<>
<div className='mt-6 flex flex-col items-center justify-center gap-8 md:grid md:grid-cols-2 md:justify-between lg:grid-cols-3'>
<div className='grid grid-cols-2 gap-x-2 gap-y-5 lgx:w-full md:grid-cols-3 md:gap-x-3 md:gap-y-8'>
{activities.map((activity) => (
<ActivityCard key={activity.id}>
<ActivityCardImage src={activity.cover} alt={activity.name} />
<ActivityCardHeader>
<ActivityCardCategory>{activity.category}</ActivityCardCategory>
<ActivityCardName>{activity.name}</ActivityCardName>
</ActivityCardHeader>
<ActivityCardDivider />
<ActivityCardContent>
<ActivityCardContentItem>
<UserRoundIcon className='size-6 stroke-muted-foreground' />
<I18nText
path='landing.activities.card.minimumAge'
values={{ age: activity.ageLimit[0] }}
/>
</ActivityCardContentItem>
<ActivityCardContentItem>
<Clock4Icon className='size-6 stroke-muted-foreground' />
<I18nText
path='landing.activities.card.duration'
values={{ duration: activity.duration }}
/>
</ActivityCardContentItem>
</ActivityCardContent>
</ActivityCard>
<Link href={`${ROUTES.APP.ACTIVITIES}/${activity.id}`}>
<ActivityCard key={activity.id}>
{/* Π’Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠ΅ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ Π΄ΠΎ ΠΌΠ΅Ρ€ΠΆΠ° Ρ€Π΅Π»ΠΈΠ·Π° */}
<ActivityCardImage
src={activity.media[0].url}
alt={activity.name}
className='2md:rounded-[8px]'
/>
<ActivityCardHeader>
<ActivityCardCategory>{activity.category}</ActivityCardCategory>
<ActivityCardName>{activity.name}</ActivityCardName>
</ActivityCardHeader>
<ActivityCardDivider />
<ActivityCardContent>
<ActivityCardContentItem>
<UserRoundIcon className='size-3 stroke-muted-foreground 2xs:size-5' />
<Typography
tag='p'
variant='body4'
className='text-muted-foreground 2xs:text-base'
>
<I18nText
path='landing.activities.card.minimumAge'
values={{ age: activity.ageLimit[0] }}
/>
</Typography>
</ActivityCardContentItem>
<ActivityCardContentItem>
<Clock4Icon className='size-3 stroke-muted-foreground 2xs:size-5' />
<Typography
tag='p'
variant='body4'
className='text-muted-foreground 2xs:text-base'
>
<I18nText
path='landing.activities.card.duration'
values={{ duration: activity.duration }}
/>
</Typography>
</ActivityCardContentItem>
</ActivityCardContent>
</ActivityCard>
</Link>
))}
</div>
<div ref={ref} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Clock4Icon, UserRoundIcon } from 'lucide-react';

import { I18nText } from '@/components/common';
import {
ActivityCardCategory,
ActivityCardContent,
ActivityCardContentItem,
ActivityCardDivider,
ActivityCardHeader,
ActivityCardHeaderTop,
ActivityCardName,
Typography
} from '@/components/ui';

export const ActivityInfo = ({ activity }: { activity: ActivityResponse }) => (
<div className='mt-3 max-w-[483px]'>
<ActivityCardHeader className='lg:gap-2'>
<ActivityCardHeaderTop className='mb-0 mt-0'>
<ActivityCardCategory>{activity.category}</ActivityCardCategory>
{/* <div className='flex gap-2 lg:gap-4'>
{activity.participants && (
<ActivityCardContentItem>
<UsersRoundIcon className='size-4 stroke-muted-foreground lg:size-5' />
<Typography tag='p' variant='body3' className='xsx:text-xs'>
{activity.participants}
</Typography>
</ActivityCardContentItem>
)}
{activity.likes && (
<ActivityCardContentItem>
<HeartIcon className='size-4 stroke-muted-foreground lg:size-5' />
<Typography tag='p' variant='body3' className='xsx:text-xs'>
{activity.likes}
</Typography>
</ActivityCardContentItem>
)}
</div> */}
</ActivityCardHeaderTop>
<ActivityCardName className='text-lg 2md:text-xl'>{activity.name}</ActivityCardName>
</ActivityCardHeader>
<ActivityCardContent className='mt-[11px]'>
<div className='flex gap-[20px]'>
{activity.ageLimit[0] && (
<ActivityCardContentItem>
<UserRoundIcon className='size-4 stroke-muted-foreground lg:size-5' />
<Typography tag='p' variant='body3' className='text-gray-two'>
<I18nText
path='landing.activities.card.minimumAge'
values={{ age: activity.ageLimit[0] }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Π° Ρ‡Ρ‚ΠΎ Π° Π·Π°Ρ‡Π΅ΠΌ Π½Π°ΠΌ массив ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½ΠΈΠΉ Ссли ΠΌΡ‹ Π² ΠΊΠΎΠ΄Π΅ Π²Π΅Π·Π΄Π΅ юзаСм Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π½ΡƒΠ»Π΅Π²ΠΎΠΉ индСкс. Π’Ρ€ΠΎΠ΄Π΅ Π½Π΅Ρ‚ кСйса Π³Π΄Π΅ нСсколько ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½ΠΈΠΉ? Или я Ρ„ΠΈΠ³Π½ΡŽ нСсу

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ageLimit": [
7,
13
],
ΠΌΡ‹ ΠΏΡ€ΠΈ создании активности ΠΌΠΎΠΆΠ΅ΠΌ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Π²Π΅Ρ€Ρ…Π½ΡŽΡŽ ΠΏΠ»Π°Π½ΠΊΡƒ возраста

/>
</Typography>
</ActivityCardContentItem>
)}
{activity.duration && (
<ActivityCardContentItem>
<Clock4Icon className='size-4 stroke-muted-foreground lg:size-5' />
<Typography tag='p' variant='body3' className='text-gray-two'>
<I18nText
path='landing.activities.card.duration'
values={{ duration: activity.duration }}
/>
</Typography>
</ActivityCardContentItem>
)}
</div>
</ActivityCardContent>
<ActivityCardDivider className='mt-4' />
</div>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use client';

import React from 'react';
import Image from 'next/image';

import { cn } from '@/lib/utils';
import { useI18n } from '@/utils/contexts';

import { CarouselMedia } from '../CarouselMedia/CarouselMedia';

interface ActivityMediaProps {
activity: ActivityResponse;
isMobile: boolean;
}

export const ActivityMedia = ({ activity, isMobile }: ActivityMediaProps) => {
const i18n = useI18n();
const activityCover = activity.media.find((media) => media.flag === 'AVATAR')!;
const [activeMedia, setActiveMedia] = React.useState(activityCover);

return (
<>
{isMobile && <CarouselMedia activity={activity} />}
{!isMobile && (
<div className='flex w-full flex-col gap-4 md:flex-row 2md:gap-6'>
<div className='relative max-h-[483px] w-full md:w-[65%] md:max-w-[483px]'>
{activeMedia.url && activeMedia.type === 'IMAGE' && (
<div className='aspect-square p-1/2'>
<Image
className='rounded-lg'
src={activeMedia.url}
fill
alt={i18n.formatMessage({ id: 'activity.image.alt' }, { name: activity.name })}
/>
</div>
)}
{activeMedia.type === 'VIDEO' && (
<div className='aspect-square'>
<video
autoPlay
muted
className='h-full max-h-[418px] w-full rounded-lg border border-border object-cover'
>
<source src={activeMedia.url} type='video/mp4' />
</video>
</div>
)}
</div>

<div className='flex w-full max-w-[240px] flex-grow grid-rows-4 gap-2 overflow-x-scroll md:grid md:h-full md:w-fit md:grid-cols-2 md:overflow-x-hidden 2md:gap-4'>
{activity.media.map((item, index) => (
<div className='relative' key={index}>
<div className='relative w-[112px] md:w-full'>
{item.url && item.type === 'IMAGE' && (
<div className='aspect-square h-full w-full'>
<Image
className={cn(
'w-full rounded-lg',
activeMedia.url === item.url && 'border-2 border-emerald-700'
)}
src={item.url}
fill
onClick={() => setActiveMedia(item)}
alt={i18n.formatMessage(
{ id: 'activity.image.alt' },
{ name: activity.name }
)}
/>
</div>
)}
{item.type === 'VIDEO' && (
<div className='size-[112px]'>
<video
autoPlay
muted
className={cn(
activeMedia.url === item.url && 'border-2 border-emerald-700',
'size-[112px] rounded-lg border border-border object-cover'
)}
onClick={() => setActiveMedia(item)}
>
<source src={item.url} type='video/mp4' />
</video>
</div>
)}
</div>
</div>
))}
</div>
</div>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { I18nText } from '@/components/common';
import { Typography } from '@/components/ui';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger
} from '@/components/ui/accordion';
import { getDevice } from '@/utils/helpers/server';

import { FreeTimeList } from '../FreeTimeList/FreeTimeList';

interface AddressItemProps {
schedule: Schedule;
}

export const AddressItem = ({ schedule }: AddressItemProps) => {
const device = getDevice();
const isMobile = device.type === 'mobile';

return (
<Accordion type='single' collapsible className='rounded-lg border border-border px-3'>
<AccordionItem value='item-1' className='border-none'>
<AccordionTrigger>
{schedule.address.street}, {schedule.address.house}
</AccordionTrigger>
<AccordionContent className='space-y-3'>
<div className='flex gap-2'>
<Typography variant='sub3' tag='h3'>
<I18nText path='field.details.label' />
</Typography>
<Typography variant='body5' tag='p'>
{schedule?.details}
</Typography>
</div>
<div className='flex flex-col gap-3 md:flex-row'>
<div className='w-1/3 min-w-60'>
<Typography variant='sub3' tag='h3' className='flex-1'>
<I18nText path='field.nearestFreeTime.label' />
</Typography>
</div>

<FreeTimeList schedule={schedule} isMobile={isMobile} />
</div>
{/* <div className='flex'>
<Typography variant='sub3' tag='h3' className='flex-1'>
<I18nText path='field.chooseAnotherTime.label' />
</Typography>
<ChevronRightIcon className='stroke-muted-foreground' />
</div>
<div className='flex'>
<Typography variant='sub3' tag='h3' className='flex-1'>
<I18nText path='field.goToSupportChat.label' />
</Typography>
<ChevronRightIcon className='stroke-muted-foreground' />
</div> */}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Π° Ρ‡Ρ‚ΠΎ ΠΏΠΎΡ‡Π΅ΠΌΡƒ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ это ΠΈΠΊΠΎΠ½ΠΊΠΈ ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π² mvp ΠΏΠΎΠΊΠ° Π½Π΅ ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°ΡŽΡ‚ΡΡ

</AccordionContent>
</AccordionItem>
</Accordion>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Autoplay from 'embla-carousel-autoplay';
import Image from 'next/image';

import { Carousel, CarouselContent, CarouselDots, CarouselItem } from '@/components/ui';
import { useI18n } from '@/utils/contexts';

const CAROUSEL_AUTO_PLAY_DELAY = 8000;

export const CarouselMedia = ({ activity }: { activity: ActivityResponse }) => {
const i18n = useI18n();

return (
<Carousel
className='relative w-full'
opts={{
loop: true
}}
plugins={[Autoplay({ delay: CAROUSEL_AUTO_PLAY_DELAY })]}
>
<CarouselContent>
{activity.media.map((item, index) => (
<CarouselItem key={index} className='aspect-square w-full'>
<div className='relative h-full w-full'>
<Image
src={item.url}
fill
alt={i18n.formatMessage({ id: 'activity.image.alt' }, { name: activity.name })}
priority={index === 0}
/>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselDots
className='absolute bottom-5 mx-auto mt-4 w-full text-center'
activeStyle='bg-white scale-100'
inactiveStyle='bg-transparent border border-white'
/>
</Carousel>
);
};
Loading