@@ -10,21 +10,23 @@ import {
1010 InvitationStatus ,
1111 PaymentStatus ,
1212 UserRole ,
13+ SpotSponsor ,
1314} from "../types" ;
1415import Card from "../components/common/Card" ;
1516import Button from "../components/common/Button" ;
1617import Modal from "../components/common/Modal" ;
1718import Input from "../components/common/Input" ;
1819import GlowButton from "../components/common/GlowButton" ;
1920import Textarea from "../components/common/Textarea" ;
20- import { spotService , invitationService , paymentService , notificationService , profileService } from "../services/database" ;
21+ import { spotService , invitationService , paymentService , notificationService , profileService , sponsorService } from "../services/database" ;
2122import { supabase } from "../services/supabase" ;
2223import { checkDatabaseSetup , getSetupInstructions } from "../services/dbCheck" ;
2324import { useNotifications } from "../contexts/NotificationsContext" ;
2425import { format } from "date-fns" ;
2526import ShinyText from "../components/common/ShinyText" ;
2627import GradientText from "../components/common/GradientText" ;
2728import StarBorder from "../components/common/StarBorder" ;
29+ import SponsorBadge from "../components/common/SponsorBadge" ;
2830
2931declare const google : any ;
3032
@@ -47,6 +49,10 @@ const HomePage: React.FC = () => {
4749 feedback : '' ,
4850 } ) ;
4951 const [ dbSetupError , setDbSetupError ] = useState < string | null > ( null ) ;
52+ const [ spotSponsor , setSpotSponsor ] = useState < SpotSponsor | null > ( null ) ;
53+ const [ allProfiles , setAllProfiles ] = useState < any [ ] > ( [ ] ) ;
54+ const [ isSponsorModalOpen , setIsSponsorModalOpen ] = useState ( false ) ;
55+ const [ sponsorForm , setSponsorForm ] = useState ( { sponsor_id : '' , amount_covered : '' , message : '' } ) ;
5056
5157 const [ newSpotData , setNewSpotData ] = useState ( {
5258 location : "" ,
@@ -85,11 +91,19 @@ const HomePage: React.FC = () => {
8591 setSpot ( spotData ) ;
8692
8793 if ( spotData ) {
88- const inv = await invitationService . getInvitations ( spotData . id ) ;
94+ const [ inv , sponsor ] = await Promise . all ( [
95+ invitationService . getInvitations ( spotData . id ) ,
96+ sponsorService . getSponsor ( spotData . id ) ,
97+ ] ) ;
8998 setInvitations ( inv ) ;
99+ setSpotSponsor ( sponsor ) ;
90100 } else {
91101 setInvitations ( [ ] ) ;
102+ setSpotSponsor ( null ) ;
92103 }
104+
105+ const profiles = await profileService . getAllProfiles ( ) ;
106+ setAllProfiles ( profiles || [ ] ) ;
93107 } catch ( err : any ) {
94108 console . error ( "Error fetching data:" , err ) ;
95109 if ( err . message ?. includes ( 'does not exist' ) || err . message ?. includes ( 'relation' ) ) {
@@ -109,6 +123,7 @@ const HomePage: React.FC = () => {
109123 // Set up real-time subscriptions
110124 let spotChannel : any = null ;
111125 let invitationChannel : any = null ;
126+ let sponsorChannel : any = null ;
112127
113128 if ( spot ) {
114129 // Subscribe to spot changes
@@ -127,6 +142,8 @@ const HomePage: React.FC = () => {
127142 }
128143 } ) ;
129144
145+ sponsorChannel = sponsorService . subscribeToSponsors ( ( ) => fetchData ( ) ) ;
146+
130147 // Subscribe to invitation changes for this spot
131148 invitationChannel = invitationService . subscribeToInvitations (
132149 spot . id ,
@@ -158,6 +175,9 @@ const HomePage: React.FC = () => {
158175 if ( invitationChannel ) {
159176 supabase . removeChannel ( invitationChannel ) ;
160177 }
178+ if ( sponsorChannel ) {
179+ supabase . removeChannel ( sponsorChannel ) ;
180+ }
161181 } ;
162182 } , [ fetchData , spot ?. id , notify ] ) ;
163183
@@ -621,6 +641,36 @@ const HomePage: React.FC = () => {
621641 ) ;
622642 }
623643
644+
645+ const handleAssignSponsor = async ( e : React . FormEvent ) => {
646+ e . preventDefault ( ) ;
647+ if ( ! spot || ! sponsorForm . sponsor_id || ! sponsorForm . amount_covered ) return ;
648+
649+ try {
650+ await sponsorService . sponsorSpot ( {
651+ spot_id : spot . id ,
652+ sponsor_id : sponsorForm . sponsor_id ,
653+ amount_covered : Number ( sponsorForm . amount_covered ) ,
654+ message : sponsorForm . message ,
655+ } ) ;
656+ setIsSponsorModalOpen ( false ) ;
657+ setSponsorForm ( { sponsor_id : '' , amount_covered : '' , message : '' } ) ;
658+ await fetchData ( ) ;
659+ } catch ( error : any ) {
660+ alert ( `Failed to assign sponsor: ${ error . message || 'Please try again.' } ` ) ;
661+ }
662+ } ;
663+
664+ const handleRemoveSponsor = async ( ) => {
665+ if ( ! spot ) return ;
666+ try {
667+ await sponsorService . removeSponsor ( spot . id ) ;
668+ await fetchData ( ) ;
669+ } catch ( error : any ) {
670+ alert ( `Failed to remove sponsor: ${ error . message || 'Please try again.' } ` ) ;
671+ }
672+ } ;
673+
624674 /* ----------------------------- UI ----------------------------- */
625675
626676 return (
@@ -686,9 +736,23 @@ const HomePage: React.FC = () => {
686736 day : 'numeric'
687737 } ) } at { spot . timing }
688738 </ p >
739+ { spotSponsor && (
740+ < div className = "mt-3 inline-flex items-center gap-2 px-3 py-2 rounded-xl bg-yellow-500/10 border border-yellow-500/30" >
741+ < span > 🎉 This spot is sponsored by < strong > { spotSponsor . sponsor ?. name || 'A Bro' } </ strong > !</ span >
742+ < SponsorBadge size = "sm" showLabel = { false } count = { spotSponsor . sponsor ?. sponsor_count || 0 } />
743+ </ div >
744+ ) }
689745 </ div >
690746 { isAdmin && (
691747 < div className = "flex gap-2" >
748+ < Button size = "sm" variant = "secondary" onClick = { ( ) => setIsSponsorModalOpen ( true ) } >
749+ Add Sponsor
750+ </ Button >
751+ { spotSponsor && (
752+ < Button size = "sm" variant = "secondary" onClick = { handleRemoveSponsor } >
753+ Remove Sponsor
754+ </ Button >
755+ ) }
692756 < Button
693757 size = "sm"
694758 variant = "secondary"
@@ -895,6 +959,42 @@ const HomePage: React.FC = () => {
895959 </ >
896960 ) }
897961
962+
963+ < Modal
964+ isOpen = { isSponsorModalOpen }
965+ onClose = { ( ) => setIsSponsorModalOpen ( false ) }
966+ title = "Add Sponsor"
967+ >
968+ < form onSubmit = { handleAssignSponsor } className = "space-y-4" >
969+ < div >
970+ < label className = "text-sm text-zinc-400" > Sponsor</ label >
971+ < select
972+ value = { sponsorForm . sponsor_id }
973+ onChange = { ( e ) => setSponsorForm ( prev => ( { ...prev , sponsor_id : e . target . value } ) ) }
974+ className = "w-full mt-2 bg-zinc-900 border border-white/10 rounded-lg p-3"
975+ required
976+ >
977+ < option value = "" > Select member</ option >
978+ { allProfiles . map ( ( member ) => (
979+ < option key = { member . id } value = { member . id } > { member . name } (@{ member . username } )</ option >
980+ ) ) }
981+ </ select >
982+ </ div >
983+ < Input
984+ label = "Amount Covered"
985+ type = "number"
986+ value = { sponsorForm . amount_covered }
987+ onChange = { ( e ) => setSponsorForm ( prev => ( { ...prev , amount_covered : e . target . value } ) ) }
988+ />
989+ < Textarea
990+ label = "Message"
991+ value = { sponsorForm . message }
992+ onChange = { ( e ) => setSponsorForm ( prev => ( { ...prev , message : e . target . value } ) ) }
993+ />
994+ < Button type = "submit" className = "w-full" > Assign Sponsor</ Button >
995+ </ form >
996+ </ Modal >
997+
898998 { /* CREATE SPOT MODAL */ }
899999 < Modal
9001000 isOpen = { isCreateSpotModalOpen }
0 commit comments