Compare commits
9 Commits
cd22418786
...
62ce98c80e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62ce98c80e | ||
|
|
fe3f2b0894 | ||
|
|
79f08a1208 | ||
|
|
2c73ee4d7e | ||
|
|
392dd6f0d2 | ||
|
|
1f1192fb62 | ||
|
|
31c6ae3f5c | ||
|
|
678c55a1e2 | ||
|
|
d61e518697 |
@@ -317,6 +317,7 @@ def upgrade() -> None:
|
|||||||
sa.Column('gift_id', sa.UUID(), nullable=False),
|
sa.Column('gift_id', sa.UUID(), nullable=False),
|
||||||
sa.Column('reserved_at', sa.DateTime(timezone=True), nullable=True),
|
sa.Column('reserved_at', sa.DateTime(timezone=True), nullable=True),
|
||||||
sa.Column('notes', sa.String(), nullable=True),
|
sa.Column('notes', sa.String(), nullable=True),
|
||||||
|
sa.Column('quantity', sa.Integer(), nullable=False, server_default='1'),
|
||||||
sa.ForeignKeyConstraint(['gift_id'], ['gift_items.id'], ondelete="CASCADE"),
|
sa.ForeignKeyConstraint(['gift_id'], ['gift_items.id'], ondelete="CASCADE"),
|
||||||
sa.ForeignKeyConstraint(['guest_id'], ['guests.id'], ondelete="CASCADE"),
|
sa.ForeignKeyConstraint(['guest_id'], ['guests.id'], ondelete="CASCADE"),
|
||||||
sa.PrimaryKeyConstraint('guest_id', 'gift_id')
|
sa.PrimaryKeyConstraint('guest_id', 'gift_id')
|
||||||
|
|||||||
@@ -1048,3 +1048,21 @@ def read_gift_purchases_by_guest(
|
|||||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||||
|
|
||||||
return gift_purchase_crud.get_gift_reservations_by_guest(db, guest_id=guest_id)
|
return gift_purchase_crud.get_gift_reservations_by_guest(db, guest_id=guest_id)
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/purchases/guest/all",
|
||||||
|
response_model=List[GiftPurchase],
|
||||||
|
operation_id="read_guests_gift_purchases"
|
||||||
|
)
|
||||||
|
def read_all_guest_gift_reservations(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Retrieve all guest gift reservations.
|
||||||
|
"""
|
||||||
|
reservations = gift_purchase_crud.get_all_guest_gift_reservations(db=db)
|
||||||
|
|
||||||
|
if not reservations:
|
||||||
|
reservations = []
|
||||||
|
return reservations
|
||||||
|
|||||||
@@ -2,15 +2,13 @@ from datetime import datetime, timezone
|
|||||||
from typing import List, Optional, Dict, Any, Union
|
from typing import List, Optional, Dict, Any, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from sqlalchemy import asc, desc
|
from sqlalchemy import asc, desc, select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session, joinedload
|
||||||
|
|
||||||
from app.crud.base import CRUDBase
|
from app.crud.base import CRUDBase
|
||||||
from app.models import gift as gift_models
|
|
||||||
from app.models.gift import GiftItem, GiftCategory, GiftPurchase, GiftStatus, EventGiftCategory
|
|
||||||
from app.models.guest import Guest
|
|
||||||
from app.models.event import Event
|
from app.models.event import Event
|
||||||
from app.schemas import gifts as gift_schemas
|
from app.models.gift import GiftItem, GiftCategory, GiftPurchase, GiftStatus, EventGiftCategory
|
||||||
|
from app.models.guest import Guest, GuestGifts
|
||||||
from app.schemas.gifts import (
|
from app.schemas.gifts import (
|
||||||
GiftItemCreate, GiftItemUpdate,
|
GiftItemCreate, GiftItemUpdate,
|
||||||
GiftCategoryCreate, GiftCategoryUpdate,
|
GiftCategoryCreate, GiftCategoryUpdate,
|
||||||
@@ -94,13 +92,14 @@ class CRUDGiftItem(CRUDBase[GiftItem, GiftItemCreate, GiftItemUpdate]):
|
|||||||
gift: GiftItem = self.get(db, gift_id)
|
gift: GiftItem = self.get(db, gift_id)
|
||||||
if gift and gift.quantity_received < gift.quantity_requested:
|
if gift and gift.quantity_received < gift.quantity_requested:
|
||||||
# Add to the association table using the SQLAlchemy Core Table directly
|
# Add to the association table using the SQLAlchemy Core Table directly
|
||||||
from app.models.guest import guest_gifts
|
from app.models.guest import GuestGifts
|
||||||
|
|
||||||
stmt = guest_gifts.insert().values(
|
stmt = GuestGifts.insert().values(
|
||||||
gift_id=gift_id,
|
gift_id=gift_id,
|
||||||
guest_id=guest_id,
|
guest_id=guest_id,
|
||||||
reserved_at=datetime.now(timezone.utc),
|
reserved_at=datetime.now(timezone.utc),
|
||||||
notes=notes
|
notes=notes,
|
||||||
|
quantity=quantity
|
||||||
)
|
)
|
||||||
db.execute(stmt)
|
db.execute(stmt)
|
||||||
|
|
||||||
@@ -151,6 +150,7 @@ class CRUDGiftItem(CRUDBase[GiftItem, GiftItemCreate, GiftItemUpdate]):
|
|||||||
|
|
||||||
return gift # Always return the gift object, even if no changes were made
|
return gift # Always return the gift object, even if no changes were made
|
||||||
|
|
||||||
|
|
||||||
class CRUDEventGiftCategory:
|
class CRUDEventGiftCategory:
|
||||||
def create(self, db: Session, *, obj_in: EventGiftCategoryCreate) -> EventGiftCategory:
|
def create(self, db: Session, *, obj_in: EventGiftCategoryCreate) -> EventGiftCategory:
|
||||||
"""Create a new event-category association"""
|
"""Create a new event-category association"""
|
||||||
@@ -335,13 +335,10 @@ class CRUDGiftPurchase(CRUDBase[GiftPurchase, GiftPurchaseCreate, GiftPurchaseUp
|
|||||||
result = []
|
result = []
|
||||||
for gift in guest.gifts:
|
for gift in guest.gifts:
|
||||||
# Access the association data through the relationship metadata
|
# Access the association data through the relationship metadata
|
||||||
from sqlalchemy import select
|
|
||||||
from app.models.guest import guest_gifts
|
|
||||||
|
|
||||||
# Get the reservation data
|
# Get the reservation data
|
||||||
stmt = select(guest_gifts).where(
|
stmt = select(GuestGifts).where(
|
||||||
(guest_gifts.c.guest_id == guest_id) &
|
(GuestGifts.c.guest_id == guest_id) &
|
||||||
(guest_gifts.c.gift_id == gift.id)
|
(GuestGifts.c.gift_id == gift.id)
|
||||||
)
|
)
|
||||||
reservation = db.execute(stmt).first()
|
reservation = db.execute(stmt).first()
|
||||||
|
|
||||||
@@ -359,6 +356,37 @@ class CRUDGiftPurchase(CRUDBase[GiftPurchase, GiftPurchaseCreate, GiftPurchaseUp
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def get_all_guest_gift_reservations(self, db: Session) -> List[GiftPurchase]:
|
||||||
|
"""Retrieve all guest gift reservations and convert them into GiftPurchase-like objects for API compatibility."""
|
||||||
|
|
||||||
|
stmt = select(Guest).options(joinedload(Guest.gifts))
|
||||||
|
guests = db.scalars(stmt).all()
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for guest in guests:
|
||||||
|
# Fetch all gifts and their reservation details per guest at once to avoid N+1 issues
|
||||||
|
for gift in guest.gifts:
|
||||||
|
reservation_stmt = select(GuestGifts).where(
|
||||||
|
(GuestGifts.c.guest_id == guest.id) &
|
||||||
|
(GuestGifts.c.gift_id == gift.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
reservation = db.execute(reservation_stmt).first()
|
||||||
|
|
||||||
|
if reservation:
|
||||||
|
purchase = GiftPurchase(
|
||||||
|
id=UUID('00000000-0000-0000-0000-000000000000'), # Placeholder UUID
|
||||||
|
gift_id=gift.id,
|
||||||
|
guest_id=guest.id,
|
||||||
|
quantity=1, # Default
|
||||||
|
purchased_at=reservation.reserved_at,
|
||||||
|
notes=reservation.notes
|
||||||
|
)
|
||||||
|
result.append(purchase)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
# Create CRUD instances
|
# Create CRUD instances
|
||||||
gift_item_crud = CRUDGiftItem(GiftItem)
|
gift_item_crud = CRUDGiftItem(GiftItem)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from .event_theme import EventTheme
|
|||||||
from .event_media import EventMedia, MediaType, MediaPurpose
|
from .event_media import EventMedia, MediaType, MediaPurpose
|
||||||
|
|
||||||
# Import guest and RSVP models
|
# Import guest and RSVP models
|
||||||
from .guest import Guest, GuestStatus, guest_gifts
|
from .guest import Guest, GuestStatus, GuestGifts
|
||||||
from .rsvp import RSVP, RSVPStatus
|
from .rsvp import RSVP, RSVPStatus
|
||||||
|
|
||||||
# Import gift-related models
|
# Import gift-related models
|
||||||
|
|||||||
@@ -82,11 +82,12 @@ class Guest(Base, UUIDMixin, TimestampMixin):
|
|||||||
|
|
||||||
|
|
||||||
# Association table for guest gifts (many-to-many relationship)
|
# Association table for guest gifts (many-to-many relationship)
|
||||||
guest_gifts = Table(
|
GuestGifts = Table(
|
||||||
'guest_gifts',
|
'guest_gifts',
|
||||||
Base.metadata,
|
Base.metadata,
|
||||||
Column('guest_id', UUID(as_uuid=True), ForeignKey('guests.id'), primary_key=True),
|
Column('guest_id', UUID(as_uuid=True), ForeignKey('guests.id'), primary_key=True),
|
||||||
Column('gift_id', UUID(as_uuid=True), ForeignKey('gift_items.id'), primary_key=True),
|
Column('gift_id', UUID(as_uuid=True), ForeignKey('gift_items.id'), primary_key=True),
|
||||||
Column('reserved_at', DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)),
|
Column('reserved_at', DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)),
|
||||||
Column('notes', String),
|
Column('notes', String),
|
||||||
|
Column('quantity', Integer, default=1),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import Link from "next/link";
|
|||||||
import { Loader2Icon, PaletteIcon } from "lucide-react";
|
import { Loader2Icon, PaletteIcon } from "lucide-react";
|
||||||
import { useEventThemes } from "@/context/event-theme-context";
|
import { useEventThemes } from "@/context/event-theme-context";
|
||||||
import { getServerFileUrl } from "@/lib/utils";
|
import { getServerFileUrl } from "@/lib/utils";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
export default function EventThemesPage() {
|
export default function EventThemesPage() {
|
||||||
// const { data: themes, isLoading } = useQuery({
|
// const { data: themes, isLoading } = useQuery({
|
||||||
@@ -24,8 +25,11 @@ export default function EventThemesPage() {
|
|||||||
// queryFn: () => listEventThemes().then(res => res.data),
|
// queryFn: () => listEventThemes().then(res => res.data),
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const { themes, isLoadingThemes } = useEventThemes();
|
const { themes, refetchThemes, isLoadingThemes } = useEventThemes();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refetchThemes();
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className="max-w-7xl mx-auto py-6 px-4">
|
<div className="max-w-7xl mx-auto py-6 px-4">
|
||||||
<div className="flex items-center justify-between mb-8">
|
<div className="flex items-center justify-between mb-8">
|
||||||
|
|||||||
@@ -6,16 +6,15 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {
|
import {
|
||||||
ChevronRight,
|
|
||||||
Loader2,
|
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
|
ChevronRight,
|
||||||
|
Edit,
|
||||||
|
ExternalLink,
|
||||||
|
Loader2,
|
||||||
|
MoreHorizontal,
|
||||||
Plus,
|
Plus,
|
||||||
Search,
|
Search,
|
||||||
Filter,
|
|
||||||
Edit,
|
|
||||||
Trash,
|
Trash,
|
||||||
MoreHorizontal,
|
|
||||||
Settings,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEvents } from "@/context/event-context";
|
import { useEvents } from "@/context/event-context";
|
||||||
import { useGifts } from "@/context/gift-context";
|
import { useGifts } from "@/context/gift-context";
|
||||||
@@ -45,9 +44,14 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { GiftStatus, GiftPriority } from "@/client/types.gen";
|
import { GiftPriority, GiftStatus } from "@/client/types.gen";
|
||||||
import { CategoryModal } from "@/components/gifts/category-modal";
|
import { CategoryModal } from "@/components/gifts/category-modal";
|
||||||
import { GiftModal } from "@/components/gifts/gift-modal";
|
import { GiftModal } from "@/components/gifts/gift-modal";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
|
||||||
export default function GiftRegistryPage() {
|
export default function GiftRegistryPage() {
|
||||||
const { slug } = useParams<{ slug: string }>();
|
const { slug } = useParams<{ slug: string }>();
|
||||||
@@ -57,6 +61,7 @@ export default function GiftRegistryPage() {
|
|||||||
items,
|
items,
|
||||||
isLoadingCategories,
|
isLoadingCategories,
|
||||||
isLoadingItems,
|
isLoadingItems,
|
||||||
|
purchases,
|
||||||
error,
|
error,
|
||||||
refetchCategories,
|
refetchCategories,
|
||||||
refetchItems,
|
refetchItems,
|
||||||
@@ -387,19 +392,70 @@ export default function GiftRegistryPage() {
|
|||||||
const category = categories?.find(
|
const category = categories?.find(
|
||||||
(c) => c.id === item.category_id,
|
(c) => c.id === item.category_id,
|
||||||
);
|
);
|
||||||
|
const canShowReservations = item.status
|
||||||
|
? (
|
||||||
|
[
|
||||||
|
GiftStatus.RESERVED,
|
||||||
|
GiftStatus.RECEIVED,
|
||||||
|
] as GiftStatus[]
|
||||||
|
).includes(item.status)
|
||||||
|
: false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={item.id}>
|
<React.Fragment key={item.id}>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{category?.name || "Uncategorized"}
|
{category?.name || "Uncategorized"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="font-medium">
|
|
||||||
|
<TableCell className="flex items-center gap-2 font-medium">
|
||||||
|
{/* Purchase URL clickable icon */}
|
||||||
|
{item.purchase_url ? (
|
||||||
|
<Link
|
||||||
|
href={item.purchase_url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<ExternalLink className="w-4 h-4 text-blue-500 hover:text-blue-600" />
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<ExternalLink className="w-4 h-4 text-gray-300 cursor-not-allowed" />
|
||||||
|
)}
|
||||||
{item.name}
|
{item.name}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>{item.quantity_requested || 1}</TableCell>
|
<TableCell>{item.quantity_requested || 1}</TableCell>
|
||||||
<TableCell>{item.formatted_price || "-"}</TableCell>
|
<TableCell>{item.formatted_price || "-"}</TableCell>
|
||||||
<TableCell>{getPriorityBadge(item.priority)}</TableCell>
|
<TableCell>{getPriorityBadge(item.priority)}</TableCell>
|
||||||
<TableCell>{getStatusBadge(item.status)}</TableCell>
|
|
||||||
|
<TableCell>
|
||||||
|
{canShowReservations ? (
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<div className="cursor-pointer">
|
||||||
|
{getStatusBadge(item.status)}
|
||||||
|
</div>
|
||||||
|
</PopoverTrigger>
|
||||||
|
{/*<PopoverContent className="text-sm">*/}
|
||||||
|
{/* {item.reservations &&*/}
|
||||||
|
{/* item.reservations.length > 0 ? (*/}
|
||||||
|
{/* <ul className="list-disc">*/}
|
||||||
|
{/* {item.reservations.map((res) => (*/}
|
||||||
|
{/* <li key={res.guest_id}>*/}
|
||||||
|
{/* {res.guest_name}: {res.quantity}*/}
|
||||||
|
{/* </li>*/}
|
||||||
|
{/* ))}*/}
|
||||||
|
{/* </ul>*/}
|
||||||
|
{/* ) : (*/}
|
||||||
|
{/* <p>No reservations available.</p>*/}
|
||||||
|
{/* )}*/}
|
||||||
|
{/*</PopoverContent>*/}
|
||||||
|
</Popover>
|
||||||
|
) : (
|
||||||
|
getStatusBadge(item.status)
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
@@ -428,68 +484,18 @@ export default function GiftRegistryPage() {
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
|
{/* Separate row for description */}
|
||||||
|
{/*{item.description && (*/}
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell
|
<TableCell
|
||||||
colSpan={7}
|
colSpan={7}
|
||||||
className="py-1 px-4 border-t-0 bg-gray-50 dark:bg-gray-800/50 text-sm text-gray-500 dark:text-gray-400"
|
className="py-1 px-4 bg-gray-50 dark:bg-gray-800/50 text-sm text-gray-500 dark:text-gray-400 h-8"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-1">
|
{item.description}
|
||||||
{/* Description - show if available */}
|
|
||||||
{item.description && <p>{item.description}</p>}
|
|
||||||
|
|
||||||
{/* Status-specific information */}
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{item.status === GiftStatus.AVAILABLE && (
|
|
||||||
<>
|
|
||||||
{item.store_name && (
|
|
||||||
<span>Store: {item.store_name}</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{item.purchase_url && (
|
|
||||||
<>
|
|
||||||
{item.store_name && <span>•</span>}
|
|
||||||
<a
|
|
||||||
href={item.purchase_url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-600 hover:underline"
|
|
||||||
>
|
|
||||||
Shop Link
|
|
||||||
</a>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!item.store_name &&
|
|
||||||
!item.purchase_url &&
|
|
||||||
item.brand && (
|
|
||||||
<span>Brand: {item.brand}</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!item.store_name &&
|
|
||||||
!item.purchase_url &&
|
|
||||||
!item.brand && (
|
|
||||||
<span className={"italic"}>
|
|
||||||
No purchase information available
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{item.status === GiftStatus.RESERVED && (
|
|
||||||
<span>Reserved</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{item.status === GiftStatus.PURCHASED && (
|
|
||||||
<span>Purchased</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{item.status === GiftStatus.RECEIVED && (
|
|
||||||
<span>Received</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
{/*)}*/}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ import {
|
|||||||
import { useEventThemes } from "@/context/event-theme-context";
|
import { useEventThemes } from "@/context/event-theme-context";
|
||||||
import { getServerFileUrl } from "@/lib/utils";
|
import { getServerFileUrl } from "@/lib/utils";
|
||||||
import GuestsList from "@/components/guests/guests-list";
|
import GuestsList from "@/components/guests/guests-list";
|
||||||
|
import { useGuests } from "@/context/guest-context";
|
||||||
|
|
||||||
export default function EventDetailPage() {
|
export default function EventDetailPage() {
|
||||||
const { slug } = useParams<{ slug: string }>();
|
const { slug } = useParams<{ slug: string }>();
|
||||||
const { event, fetchEventBySlug, isLoadingEvent, eventError } = useEvents();
|
const { event, fetchEventBySlug, isLoadingEvent, eventError } = useEvents();
|
||||||
const { themes } = useEventThemes();
|
const { themes } = useEventThemes();
|
||||||
|
const { refetchGuests } = useGuests();
|
||||||
const currentTheme =
|
const currentTheme =
|
||||||
event?.theme_id && themes
|
event?.theme_id && themes
|
||||||
? themes.find((theme) => theme.id === event.theme_id)
|
? themes.find((theme) => theme.id === event.theme_id)
|
||||||
@@ -44,6 +46,10 @@ export default function EventDetailPage() {
|
|||||||
fetchEventBySlug(slug);
|
fetchEventBySlug(slug);
|
||||||
}, [slug, fetchEventBySlug]);
|
}, [slug, fetchEventBySlug]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refetchGuests();
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (isLoadingEvent) {
|
if (isLoadingEvent) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-[50vh]">
|
<div className="flex items-center justify-center min-h-[50vh]">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useRouter, usePathname } from "next/navigation";
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import Navbar from "@/components/layout/navbar";
|
import Navbar from "@/components/layout/navbar";
|
||||||
import Breadcrumbs from "@/components/layout/breadcrumb";
|
import Breadcrumbs from "@/components/layout/breadcrumb";
|
||||||
|
import { useEvents } from "@/context/event-context";
|
||||||
|
|
||||||
export default function MainLayout({
|
export default function MainLayout({
|
||||||
children,
|
children,
|
||||||
@@ -12,12 +13,18 @@ export default function MainLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const { isAuthenticated, isLoading } = useAuth();
|
const { isAuthenticated, isLoading } = useAuth();
|
||||||
|
const { refetchUpcomingEvents, refetchPublicEvents, refetchUserEvents } =
|
||||||
|
useEvents();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLoading && !isAuthenticated) {
|
if (!isLoading && !isAuthenticated) {
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
|
} else {
|
||||||
|
refetchUpcomingEvents();
|
||||||
|
refetchPublicEvents();
|
||||||
|
refetchUserEvents();
|
||||||
}
|
}
|
||||||
}, [isAuthenticated, isLoading, router]);
|
}, [isAuthenticated, isLoading, router]);
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import {
|
|||||||
readGiftPurchase,
|
readGiftPurchase,
|
||||||
readGiftPurchasesByGift,
|
readGiftPurchasesByGift,
|
||||||
readGiftPurchasesByGuest,
|
readGiftPurchasesByGuest,
|
||||||
|
readGuestsGiftPurchases,
|
||||||
createEvent,
|
createEvent,
|
||||||
getUserEvents,
|
getUserEvents,
|
||||||
getUpcomingEvents,
|
getUpcomingEvents,
|
||||||
@@ -165,6 +166,7 @@ import type {
|
|||||||
ReadGiftPurchaseData,
|
ReadGiftPurchaseData,
|
||||||
ReadGiftPurchasesByGiftData,
|
ReadGiftPurchasesByGiftData,
|
||||||
ReadGiftPurchasesByGuestData,
|
ReadGiftPurchasesByGuestData,
|
||||||
|
ReadGuestsGiftPurchasesData,
|
||||||
CreateEventData,
|
CreateEventData,
|
||||||
CreateEventError,
|
CreateEventError,
|
||||||
CreateEventResponse,
|
CreateEventResponse,
|
||||||
@@ -1421,6 +1423,27 @@ export const readGiftPurchasesByGuestOptions = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const readGuestsGiftPurchasesQueryKey = (
|
||||||
|
options?: Options<ReadGuestsGiftPurchasesData>,
|
||||||
|
) => createQueryKey("readGuestsGiftPurchases", options);
|
||||||
|
|
||||||
|
export const readGuestsGiftPurchasesOptions = (
|
||||||
|
options?: Options<ReadGuestsGiftPurchasesData>,
|
||||||
|
) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: async ({ queryKey, signal }) => {
|
||||||
|
const { data } = await readGuestsGiftPurchases({
|
||||||
|
...options,
|
||||||
|
...queryKey[0],
|
||||||
|
signal,
|
||||||
|
throwOnError: true,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
queryKey: readGuestsGiftPurchasesQueryKey(options),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const createEventQueryKey = (options: Options<CreateEventData>) =>
|
export const createEventQueryKey = (options: Options<CreateEventData>) =>
|
||||||
createQueryKey("createEvent", options);
|
createQueryKey("createEvent", options);
|
||||||
|
|
||||||
|
|||||||
@@ -2715,6 +2715,16 @@ export const PresignedUrlResponseSchema = {
|
|||||||
|
|
||||||
export const RSVPSchemaSchema = {
|
export const RSVPSchemaSchema = {
|
||||||
properties: {
|
properties: {
|
||||||
|
event_id: {
|
||||||
|
type: "string",
|
||||||
|
format: "uuid",
|
||||||
|
title: "Event Id",
|
||||||
|
},
|
||||||
|
guest_id: {
|
||||||
|
type: "string",
|
||||||
|
format: "uuid",
|
||||||
|
title: "Guest Id",
|
||||||
|
},
|
||||||
status: {
|
status: {
|
||||||
$ref: "#/components/schemas/RSVPStatus",
|
$ref: "#/components/schemas/RSVPStatus",
|
||||||
},
|
},
|
||||||
@@ -2771,12 +2781,30 @@ export const RSVPSchemaSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
type: "object",
|
type: "object",
|
||||||
required: ["status", "id", "response_date", "created_at", "updated_at"],
|
required: [
|
||||||
|
"event_id",
|
||||||
|
"guest_id",
|
||||||
|
"status",
|
||||||
|
"id",
|
||||||
|
"response_date",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
],
|
||||||
title: "RSVPSchema",
|
title: "RSVPSchema",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const RSVPSchemaCreateSchema = {
|
export const RSVPSchemaCreateSchema = {
|
||||||
properties: {
|
properties: {
|
||||||
|
event_id: {
|
||||||
|
type: "string",
|
||||||
|
format: "uuid",
|
||||||
|
title: "Event Id",
|
||||||
|
},
|
||||||
|
guest_id: {
|
||||||
|
type: "string",
|
||||||
|
format: "uuid",
|
||||||
|
title: "Guest Id",
|
||||||
|
},
|
||||||
status: {
|
status: {
|
||||||
$ref: "#/components/schemas/RSVPStatus",
|
$ref: "#/components/schemas/RSVPStatus",
|
||||||
},
|
},
|
||||||
@@ -2811,19 +2839,9 @@ export const RSVPSchemaCreateSchema = {
|
|||||||
additional_info: {
|
additional_info: {
|
||||||
title: "Additional Info",
|
title: "Additional Info",
|
||||||
},
|
},
|
||||||
event_id: {
|
|
||||||
type: "string",
|
|
||||||
format: "uuid",
|
|
||||||
title: "Event Id",
|
|
||||||
},
|
|
||||||
guest_id: {
|
|
||||||
type: "string",
|
|
||||||
format: "uuid",
|
|
||||||
title: "Guest Id",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
type: "object",
|
type: "object",
|
||||||
required: ["status", "event_id", "guest_id"],
|
required: ["event_id", "guest_id", "status"],
|
||||||
title: "RSVPSchemaCreate",
|
title: "RSVPSchemaCreate",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ import type {
|
|||||||
ReadGiftPurchasesByGuestData,
|
ReadGiftPurchasesByGuestData,
|
||||||
ReadGiftPurchasesByGuestResponse,
|
ReadGiftPurchasesByGuestResponse,
|
||||||
ReadGiftPurchasesByGuestError,
|
ReadGiftPurchasesByGuestError,
|
||||||
|
ReadGuestsGiftPurchasesData,
|
||||||
|
ReadGuestsGiftPurchasesResponse,
|
||||||
CreateEventData,
|
CreateEventData,
|
||||||
CreateEventResponse,
|
CreateEventResponse,
|
||||||
CreateEventError,
|
CreateEventError,
|
||||||
@@ -1153,6 +1155,23 @@ export const readGiftPurchasesByGuest = <ThrowOnError extends boolean = false>(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read All Guest Gift Reservations
|
||||||
|
* Retrieve all guest gift reservations.
|
||||||
|
*/
|
||||||
|
export const readGuestsGiftPurchases = <ThrowOnError extends boolean = false>(
|
||||||
|
options?: Options<ReadGuestsGiftPurchasesData, ThrowOnError>,
|
||||||
|
) => {
|
||||||
|
return (options?.client ?? _heyApiClient).get<
|
||||||
|
ReadGuestsGiftPurchasesResponse,
|
||||||
|
unknown,
|
||||||
|
ThrowOnError
|
||||||
|
>({
|
||||||
|
url: "/api/v1/events/gifts/purchases/guest/all",
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create Event
|
* Create Event
|
||||||
* Create a new event.
|
* Create a new event.
|
||||||
|
|||||||
@@ -467,6 +467,8 @@ export type PresignedUrlResponse = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type RsvpSchema = {
|
export type RsvpSchema = {
|
||||||
|
event_id: string;
|
||||||
|
guest_id: string;
|
||||||
status: RsvpStatus;
|
status: RsvpStatus;
|
||||||
number_of_guests?: number;
|
number_of_guests?: number;
|
||||||
response_message?: string | null;
|
response_message?: string | null;
|
||||||
@@ -479,13 +481,13 @@ export type RsvpSchema = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type RsvpSchemaCreate = {
|
export type RsvpSchemaCreate = {
|
||||||
|
event_id: string;
|
||||||
|
guest_id: string;
|
||||||
status: RsvpStatus;
|
status: RsvpStatus;
|
||||||
number_of_guests?: number;
|
number_of_guests?: number;
|
||||||
response_message?: string | null;
|
response_message?: string | null;
|
||||||
dietary_requirements?: string | null;
|
dietary_requirements?: string | null;
|
||||||
additional_info?: unknown;
|
additional_info?: unknown;
|
||||||
event_id: string;
|
|
||||||
guest_id: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RsvpSchemaUpdate = {
|
export type RsvpSchemaUpdate = {
|
||||||
@@ -1874,6 +1876,23 @@ export type ReadGiftPurchasesByGuestResponses = {
|
|||||||
export type ReadGiftPurchasesByGuestResponse =
|
export type ReadGiftPurchasesByGuestResponse =
|
||||||
ReadGiftPurchasesByGuestResponses[keyof ReadGiftPurchasesByGuestResponses];
|
ReadGiftPurchasesByGuestResponses[keyof ReadGiftPurchasesByGuestResponses];
|
||||||
|
|
||||||
|
export type ReadGuestsGiftPurchasesData = {
|
||||||
|
body?: never;
|
||||||
|
path?: never;
|
||||||
|
query?: never;
|
||||||
|
url: "/api/v1/events/gifts/purchases/guest/all";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReadGuestsGiftPurchasesResponses = {
|
||||||
|
/**
|
||||||
|
* Successful Response
|
||||||
|
*/
|
||||||
|
200: Array<GiftPurchase>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReadGuestsGiftPurchasesResponse =
|
||||||
|
ReadGuestsGiftPurchasesResponses[keyof ReadGuestsGiftPurchasesResponses];
|
||||||
|
|
||||||
export type CreateEventData = {
|
export type CreateEventData = {
|
||||||
body: EventCreate;
|
body: EventCreate;
|
||||||
path?: never;
|
path?: never;
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import {
|
|||||||
import { useGuests } from "@/context/guest-context";
|
import { useGuests } from "@/context/guest-context";
|
||||||
import {
|
import {
|
||||||
EventResponse,
|
EventResponse,
|
||||||
EventThemeResponse,
|
|
||||||
GuestCreate,
|
GuestCreate,
|
||||||
GuestRead,
|
GuestRead,
|
||||||
GuestStatus,
|
GuestStatus,
|
||||||
@@ -66,6 +65,12 @@ import {
|
|||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { useAuth } from "@/context/auth-context";
|
import { useAuth } from "@/context/auth-context";
|
||||||
import { generateInviteLink } from "@/lib/utils";
|
import { generateInviteLink } from "@/lib/utils";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { StickyNote, Utensils } from "lucide-react";
|
||||||
|
|
||||||
// Helper to generate a random invitation code
|
// Helper to generate a random invitation code
|
||||||
const generateInvitationCode = (fullName: string): string => {
|
const generateInvitationCode = (fullName: string): string => {
|
||||||
@@ -522,10 +527,13 @@ const GuestListTable = ({ event }: GuestListTableProps) => {
|
|||||||
<TableHead>Phone</TableHead>
|
<TableHead>Phone</TableHead>
|
||||||
<TableHead>Invitation Code</TableHead>
|
<TableHead>Invitation Code</TableHead>
|
||||||
<TableHead>Status</TableHead>
|
<TableHead>Status</TableHead>
|
||||||
<TableHead>Additional Guests</TableHead>
|
<TableHead>Add. Guests</TableHead>
|
||||||
|
<TableHead>Diet Restr.</TableHead>
|
||||||
|
<TableHead>Notes</TableHead>
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
<TableHead className="text-right">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{isLoadingGuests ? (
|
{isLoadingGuests ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@@ -567,6 +575,54 @@ const GuestListTable = ({ event }: GuestListTableProps) => {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{getStatusBadge(guest.status)}</TableCell>
|
<TableCell>{getStatusBadge(guest.status)}</TableCell>
|
||||||
<TableCell>{guest.actual_additional_guests || 0}</TableCell>
|
<TableCell>{guest.actual_additional_guests || 0}</TableCell>
|
||||||
|
|
||||||
|
{/* Dietary Restrictions Column */}
|
||||||
|
<TableCell>
|
||||||
|
{guest.dietary_restrictions &&
|
||||||
|
guest.dietary_restrictions.length > 0 ? (
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-6 w-6"
|
||||||
|
>
|
||||||
|
<Utensils className="h-4 w-4 text-green-600 cursor-pointer" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="max-w-xs">
|
||||||
|
<p className="text-sm">
|
||||||
|
{guest.dietary_restrictions}
|
||||||
|
</p>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
) : (
|
||||||
|
"-"
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
{/* Notes Column */}
|
||||||
|
<TableCell>
|
||||||
|
{guest.notes && guest.notes.length > 0 ? (
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-6 w-6"
|
||||||
|
>
|
||||||
|
<StickyNote className="h-4 w-4 text-blue-600 cursor-pointer" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="max-w-xs">
|
||||||
|
<p className="text-sm">{guest.notes}</p>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
) : (
|
||||||
|
"-"
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { useSearchParams, useParams } from "next/navigation";
|
|||||||
import { Loader2, Plus, Minus } from "lucide-react";
|
import { Loader2, Plus, Minus } from "lucide-react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
import { useRSVPs } from "@/context/rsvp-context";
|
||||||
|
|
||||||
interface RSVPProps {
|
interface RSVPProps {
|
||||||
eventId: string;
|
eventId: string;
|
||||||
@@ -32,6 +33,8 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
|
|||||||
findGuestByInvitationCode,
|
findGuestByInvitationCode,
|
||||||
submitGuestRsvp,
|
submitGuestRsvp,
|
||||||
} = useGuests();
|
} = useGuests();
|
||||||
|
|
||||||
|
const { rsvps } = useRSVPs();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const { slug } = useParams<{ slug: string }>();
|
const { slug } = useParams<{ slug: string }>();
|
||||||
const { event, fetchEventBySlug, isLoadingEvent } = useEvents();
|
const { event, fetchEventBySlug, isLoadingEvent } = useEvents();
|
||||||
@@ -45,6 +48,9 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
|
|||||||
const [isProcessing, setIsProcessing] = useState(false);
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [success, setSuccess] = useState<boolean>(false);
|
const [success, setSuccess] = useState<boolean>(false);
|
||||||
|
const [currentGuest, setCurrentGuest] = useState<any | null>(null);
|
||||||
|
const [currentRSVP, setCurrentRSVP] = useState<any | null>(null);
|
||||||
|
|
||||||
const { setTheme } = useTheme();
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -58,6 +64,21 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
|
|||||||
}
|
}
|
||||||
}, [slug, fetchEventBySlug]);
|
}, [slug, fetchEventBySlug]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (rsvps && currentGuest) {
|
||||||
|
setCurrentRSVP(rsvps.find((rsvp) => rsvp.guest_id === currentGuest?.id));
|
||||||
|
}
|
||||||
|
}, [rsvps, currentGuest]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentRSVP) {
|
||||||
|
setStatus(currentRSVP.status);
|
||||||
|
setNumberOfGuests(currentRSVP.number_of_guests);
|
||||||
|
setResponseMessage(currentRSVP.response_message);
|
||||||
|
setDietaryRequirements(currentRSVP.dietary_requirements);
|
||||||
|
}
|
||||||
|
}, [currentRSVP]);
|
||||||
|
|
||||||
// Find the theme for this event
|
// Find the theme for this event
|
||||||
const eventTheme =
|
const eventTheme =
|
||||||
event && themes?.find((theme) => theme.id === event.theme_id);
|
event && themes?.find((theme) => theme.id === event.theme_id);
|
||||||
@@ -114,7 +135,7 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
|
|||||||
// Find the guest with matching invitation code
|
// Find the guest with matching invitation code
|
||||||
if (guests) {
|
if (guests) {
|
||||||
const matchingGuest = findGuestByInvitationCode(invitationCode);
|
const matchingGuest = findGuestByInvitationCode(invitationCode);
|
||||||
|
setCurrentGuest(matchingGuest);
|
||||||
if (matchingGuest) {
|
if (matchingGuest) {
|
||||||
console.log("matchingGuest ", matchingGuest);
|
console.log("matchingGuest ", matchingGuest);
|
||||||
setGuestId(matchingGuest.id);
|
setGuestId(matchingGuest.id);
|
||||||
@@ -248,13 +269,12 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
|
|||||||
if (success) {
|
if (success) {
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="w-full rounded-lg border shadow-sm p-8 text-center"
|
className="w-full rounded-lg shadow-sm p-8 text-center"
|
||||||
initial={{ opacity: 0, scale: 0.9 }}
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.background,
|
backgroundColor: colors.background,
|
||||||
borderColor: colors.primary,
|
|
||||||
color: colors.text,
|
color: colors.text,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -298,13 +318,12 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="w-full rounded-lg border shadow-sm"
|
className="w-full rounded-lg shadow-sm"
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
animate="visible"
|
animate="visible"
|
||||||
variants={formVariants}
|
variants={formVariants}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.background,
|
backgroundColor: colors.background,
|
||||||
borderColor: colors.backgroundDark,
|
|
||||||
color: colors.text,
|
color: colors.text,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -466,12 +485,15 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
|
|||||||
<motion.div className="flex justify-end mt-6" variants={itemVariants}>
|
<motion.div className="flex justify-end mt-6" variants={itemVariants}>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full md:w-auto"
|
className="w-full md:w-auto text-white transition-colors duration-300"
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
style={{
|
style={{ backgroundColor: colors.accent }}
|
||||||
backgroundColor: colors.accent,
|
onMouseEnter={(e) =>
|
||||||
color: "white",
|
(e.currentTarget.style.backgroundColor = colors.accent2)
|
||||||
}}
|
}
|
||||||
|
onMouseLeave={(e) =>
|
||||||
|
(e.currentTarget.style.backgroundColor = colors.accent)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{isProcessing ? (
|
{isProcessing ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ interface EventsContextState {
|
|||||||
userEvents: PaginatedResponseEventResponse | undefined;
|
userEvents: PaginatedResponseEventResponse | undefined;
|
||||||
upcomingEvents: PaginatedResponseEventResponse | undefined;
|
upcomingEvents: PaginatedResponseEventResponse | undefined;
|
||||||
publicEvents: PaginatedResponseEventResponse | undefined;
|
publicEvents: PaginatedResponseEventResponse | undefined;
|
||||||
|
|
||||||
|
refetchUpcomingEvents: () => Promise<any>;
|
||||||
|
refetchPublicEvents: () => Promise<any>;
|
||||||
|
refetchUserEvents: () => Promise<any>;
|
||||||
|
|
||||||
isLoadingUserEvents: boolean;
|
isLoadingUserEvents: boolean;
|
||||||
isLoadingUpcomingEvents: boolean;
|
isLoadingUpcomingEvents: boolean;
|
||||||
isLoadingPublicEvents: boolean;
|
isLoadingPublicEvents: boolean;
|
||||||
@@ -67,6 +72,11 @@ const defaultEventsState: EventsContextState = {
|
|||||||
userEvents: undefined,
|
userEvents: undefined,
|
||||||
upcomingEvents: undefined,
|
upcomingEvents: undefined,
|
||||||
publicEvents: undefined,
|
publicEvents: undefined,
|
||||||
|
|
||||||
|
refetchUpcomingEvents: async () => undefined,
|
||||||
|
refetchPublicEvents: async () => undefined,
|
||||||
|
refetchUserEvents: async () => undefined,
|
||||||
|
|
||||||
isLoadingUserEvents: false,
|
isLoadingUserEvents: false,
|
||||||
isLoadingUpcomingEvents: false,
|
isLoadingUpcomingEvents: false,
|
||||||
isLoadingPublicEvents: false,
|
isLoadingPublicEvents: false,
|
||||||
@@ -148,6 +158,7 @@ export const EventsProvider: React.FC<EventsProviderProps> = ({ children }) => {
|
|||||||
data: userEvents,
|
data: userEvents,
|
||||||
isLoading: isLoadingUserEvents,
|
isLoading: isLoadingUserEvents,
|
||||||
error: userEventsError,
|
error: userEventsError,
|
||||||
|
refetch: refetchUserEvents,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
...getUserEventsOptions(paginationParams),
|
...getUserEventsOptions(paginationParams),
|
||||||
});
|
});
|
||||||
@@ -157,6 +168,7 @@ export const EventsProvider: React.FC<EventsProviderProps> = ({ children }) => {
|
|||||||
data: upcomingEvents,
|
data: upcomingEvents,
|
||||||
isLoading: isLoadingUpcomingEvents,
|
isLoading: isLoadingUpcomingEvents,
|
||||||
error: upcomingEventsError,
|
error: upcomingEventsError,
|
||||||
|
refetch: refetchUpcomingEvents,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
...getUpcomingEventsOptions(paginationParams),
|
...getUpcomingEventsOptions(paginationParams),
|
||||||
});
|
});
|
||||||
@@ -166,6 +178,7 @@ export const EventsProvider: React.FC<EventsProviderProps> = ({ children }) => {
|
|||||||
data: publicEvents,
|
data: publicEvents,
|
||||||
isLoading: isLoadingPublicEvents,
|
isLoading: isLoadingPublicEvents,
|
||||||
error: publicEventsError,
|
error: publicEventsError,
|
||||||
|
refetch: refetchPublicEvents,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
...getPublicEventsOptions(paginationParams),
|
...getPublicEventsOptions(paginationParams),
|
||||||
});
|
});
|
||||||
@@ -255,6 +268,11 @@ export const EventsProvider: React.FC<EventsProviderProps> = ({ children }) => {
|
|||||||
userEvents,
|
userEvents,
|
||||||
upcomingEvents,
|
upcomingEvents,
|
||||||
publicEvents,
|
publicEvents,
|
||||||
|
|
||||||
|
refetchPublicEvents,
|
||||||
|
refetchUpcomingEvents,
|
||||||
|
refetchUserEvents,
|
||||||
|
|
||||||
isLoadingUserEvents,
|
isLoadingUserEvents,
|
||||||
isLoadingUpcomingEvents,
|
isLoadingUpcomingEvents,
|
||||||
isLoadingPublicEvents,
|
isLoadingPublicEvents,
|
||||||
|
|||||||
Reference in New Issue
Block a user