Skip to content

Commit

Permalink
feat: home page completed
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonCrk committed Nov 21, 2023
1 parent f7c03c6 commit 16a71bb
Show file tree
Hide file tree
Showing 14 changed files with 462 additions and 70 deletions.
139 changes: 102 additions & 37 deletions src/components/EventCard.component.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,128 @@
import { FC } from 'react'

import { Link } from 'react-router-dom'

interface EventCardProps {
eventId: string
avatar: string
username: string
createdAt: string
eventName: string
participants: number
eventType: string
import { useQueryClient } from '@tanstack/react-query'

import {
EventItem,
StatusColorValues,
StatusValues,
VisibilityColorValues,
VisibilityValues,
} from '../models/event.model'

import { useAlerts } from '../hooks/useAlerts.hook'

import Avatar from './Avatar.component'
import EventOptions from './EventOptions.component'

import { datetimeFormat } from '../utils/datetimeFormat'

import { FaUsers, FaCircle } from 'react-icons/fa'
import { MdVisibility } from 'react-icons/md'

interface Props extends EventItem {
width: string
}

const EventCard: FC<EventCardProps> = ({
eventId,
avatar,
username,
const EventCard: FC<Props> = ({
id: eventId,
coordinator,
createdAt,
eventName,
participants,
eventType,
name,
numberParticipants,
width,
status,
visibility,
}) => {
const queryClient = useQueryClient()
const { showAlert } = useAlerts()

const handleCancelEvent = () => {
queryClient.invalidateQueries({ queryKey: ['home'] })
}

const handleDeleteEvent = (message: string) => {
queryClient.invalidateQueries({ queryKey: ['home'] })
showAlert({
message,
type: 'success',
timestamp: 3000,
})
}

return (
<Link
<article
className='card'
to={`/events/${eventId}`}
style={{
textDecoration: 'none',
width,
}}
>
<div className='card-body'>
<div className='d-flex align-items-center gap-2 mb-2'>
<img
src={avatar}
alt='Avatar'
className='img-fulid rounded-circle mr-3'
style={{
width: '70px',
height: '70px',
}}
/>
<header className='d-flex justify-content-between'>
<div className='d-flex align-items-center gap-2 mb-2'>
<Link to={`/${coordinator.account.id}/profile`}>
<Avatar src={coordinator.account.picture} size='50px' />
</Link>

<div className=''>
<p className='mb-0 fs-5'>{username}</p>
<p className='mb-0 text-secondary'>{createdAt}</p>
<div>
<Link
to={`/${coordinator.account.id}/profile`}
className='mb-0 fs-5 text-dark fw-semibold'
style={{ textDecoration: 'none' }}
>
{coordinator.username}
</Link>
<p className='mb-0 text-secondary' style={{ fontSize: '0.9rem' }}>
{datetimeFormat(createdAt, 'short', 'short')}
</p>
</div>
</div>
</div>
<p className='fs-4 fw-bold'>{eventName}</p>

<EventOptions
coordinatorId={coordinator.id}
eventId={eventId}
eventStatus={status}
onCancel={() => handleCancelEvent()}
onDelete={({ message }) => handleDeleteEvent(message)}
/>
</header>

<Link
to={`/events/${eventId}`}
className='fs-4 fw-bold text-dark'
style={{ textDecoration: 'none' }}
>
<p className='mb-2'>{name}</p>
</Link>

<div className='d-flex justify-content-between align-items-center'>
<p className='border border-primary p-2'>
Participant {participants}
</p>
<div className='badge bg-secondary d-flex align-items-center gap-2 fs-6 py-2'>
<FaUsers />
<span>Participants {numberParticipants}</span>
</div>

<p className='badge fs-6 bg-secondary'>{eventType}</p>
<div className='d-flex gap-2'>
<p
className={`badge py-2 d-flex align-items-center mb-0 fs-6 bg-${VisibilityColorValues[visibility]}`}
style={{ fontSize: '0.9rem' }}
>
<MdVisibility style={{ marginRight: 4, fontSize: '1rem' }} />
<span>{VisibilityValues[visibility]}</span>
</p>
<p
className={`badge py-2 d-flex align-items-center mb-0 bg-${StatusColorValues[status]}`}
style={{ fontSize: '0.9rem' }}
>
<FaCircle style={{ marginRight: 4 }} />
<span>{StatusValues[status]}</span>
</p>
</div>
</div>
</div>
</Link>
</article>
)
}

Expand Down
14 changes: 12 additions & 2 deletions src/components/EventOptions.component.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
import { FC } from 'react'

import { Link } from 'react-router-dom'

import { useMutation } from '@tanstack/react-query'

import { EventId, Status } from '../models/event.model'
import { UserId } from '../models/user.model'
import { MessageResponse } from '../models/response.model'

import { useAuthStore } from '../store/useAuthStorage'

import { cancelEvent, deleteEvent } from '../services/event.service'

import { SlOptionsVertical } from 'react-icons/sl'
import { Link } from 'react-router-dom'

interface Props {
coordinatorId: UserId
eventId: EventId
eventStatus: Status
onCancel: (data: MessageResponse) => void
onDelete: (data: MessageResponse) => void
}

const EventOptions: FC<Props> = ({
coordinatorId,
eventId,
eventStatus,
onCancel,
onDelete,
}) => {
const user = useAuthStore(state => state.user)

const { isPending: isPendingDeleteEvent, mutate: mutateDeleteEvent } =
useMutation({
mutationFn: deleteEvent,
Expand Down Expand Up @@ -53,6 +61,8 @@ const EventOptions: FC<Props> = ({
mutateCancelEvent(eventId)
}

if (user?.id !== coordinatorId) return null

return (
<div className='dropdown'>
<button
Expand All @@ -66,7 +76,7 @@ const EventOptions: FC<Props> = ({

<ul className='dropdown-menu dropdown-menu-end mt-1'>
<li>
<Link className='dropdown-item' to={`/events/${eventId}`}>
<Link className='dropdown-item' to={`/events/${eventId}/edit`}>
Edit
</Link>
</li>
Expand Down
13 changes: 9 additions & 4 deletions src/components/Select.component.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { FC, ReactNode } from 'react'
import { ChangeEvent, FC, ReactNode } from 'react'

import { UseFormRegister } from 'react-hook-form'

interface Props {
id: string
isLoading?: boolean
register: UseFormRegister<any>
name: string
Expand All @@ -11,9 +12,11 @@ interface Props {
options: () => ReactNode
defaultOption: string
label?: string
onChange?: (e: ChangeEvent<HTMLSelectElement>) => void
}

const Select: FC<Props> = ({
id,
name,
register,
errorMessage,
Expand All @@ -22,20 +25,22 @@ const Select: FC<Props> = ({
defaultOption,
label,
options,
onChange,
}) => {
return (
<div>
{label && (
<label className='form-label' style={{ marginBottom: 2 }}>
<label htmlFor={id} className='form-label' style={{ marginBottom: 2 }}>
{label}
</label>
)}
<select
id={id}
className={`form-select ${isError && 'is-invalid'}`}
disabled={isLoading}
{...register(name)}
{...register(name, onChange && { onChange })}
>
<option value={undefined}>{defaultOption}</option>
<option value={''}>{defaultOption}</option>
{!isLoading && options()}
</select>
{isError && <p className='text-danger mb-0'>{errorMessage}</p>}
Expand Down
16 changes: 16 additions & 0 deletions src/components/skeleton/AvatarSkeleton.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FC } from 'react'

interface Props {
size: string
}

const AvatarSkeleton: FC<Props> = ({ size }) => {
return (
<div
className='bg-secondary rounded-circle'
style={{ width: size, height: size }}
></div>
)
}

export default AvatarSkeleton
80 changes: 80 additions & 0 deletions src/components/skeleton/EventCardSkeleton.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { FC } from 'react'

import AvatarSkeleton from './AvatarSkeleton.component'

interface Props {
width: string
}

const EventCardSkeleton: FC<Props> = ({ width }) => {
return (
<article
className='card'
style={{
textDecoration: 'none',
width,
}}
>
<div className='card-body'>
<header className='d-flex align-items-center gap-2 mb-2'>
<AvatarSkeleton size='50px' />

<p className='placeholder-glow mb-0 d-flex flex-column gap-2'>
<span
className='placeholder placeholder-lg'
style={{ width: '200px', height: '23px' }}
></span>
<span className='placeholder' style={{ width: '100px' }}></span>
</p>
</header>

<p className='placeholder-glow d-flex flex-wrap gap-1 mb-2'>
<span
className='placeholder placeholder-lg col-4'
style={{ height: '35px' }}
></span>
<span
className='placeholder placeholder-lg col-7'
style={{ height: '35px' }}
></span>
<span
className='placeholder placeholder-lg col-4'
style={{ height: '35px' }}
></span>
</p>

<div className='d-flex justify-content-between align-items-center'>
<div className='badge bg-secondary d-flex align-items-center gap-2 py-2'>
<p className='placeholder-glow mb-0'>
<span
className='placeholder placeholder-lg'
style={{ width: '100px', height: '24px' }}
></span>
</p>
</div>

<div>
<div className='badge bg-secondary py-2 me-2'>
<p className='placeholder-glow mb-0'>
<span
className='placeholder placeholder-lg'
style={{ width: '100px', height: '24px' }}
></span>
</p>
</div>
<div className='badge py-2 bg-secondary'>
<p className='placeholder-glow mb-0'>
<span
className='placeholder placeholder-lg'
style={{ width: '100px', height: '24px' }}
></span>
</p>
</div>
</div>
</div>
</div>
</article>
)
}

export default EventCardSkeleton
8 changes: 2 additions & 6 deletions src/components/skeleton/EventDetailsSkeleton.component.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import type { FC } from 'react'

import Avatar from '../Avatar.component'
import TagSkeleton from './TagSkeleton.component'
import AvatarSkeleton from './AvatarSkeleton.component'

const EventDetailsSkeleton: FC = () => {
return (
<div>
<div className='d-flex gap-2 align-items-center mb-3'>
<Avatar
src={'https://plainbackground.com/plain1024/adb2b5.png'}
alt='loading'
size='60px'
/>
<AvatarSkeleton size='60px' />

<p className='placeholder-glow mb-0 d-flex flex-column gap-2'>
<span
Expand Down
Loading

0 comments on commit 16a71bb

Please sign in to comment.