@@ -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
@@ -128,6 +143,8 @@ const HomePage: React.FC = () => {
128143 }
129144 } ) ;
130145
146+ sponsorChannel = sponsorService . subscribeToSponsors ( ( ) => fetchData ( ) ) ;
147+
131148 // Subscribe to invitation changes for this spot
132149 invitationChannel = invitationService . subscribeToInvitations (
133150 spot . id ,
@@ -160,6 +177,9 @@ const HomePage: React.FC = () => {
160177 if ( invitationChannel ) {
161178 supabase . removeChannel ( invitationChannel ) ;
162179 }
180+ if ( sponsorChannel ) {
181+ supabase . removeChannel ( sponsorChannel ) ;
182+ }
163183 } ;
164184 } , [ fetchData , spot ?. id , notify ] ) ;
165185
@@ -631,6 +651,36 @@ const HomePage: React.FC = () => {
631651 ) ;
632652 }
633653
654+
655+ const handleAssignSponsor = async ( e : React . FormEvent ) => {
656+ e . preventDefault ( ) ;
657+ if ( ! spot || ! sponsorForm . sponsor_id || ! sponsorForm . amount_covered ) return ;
658+
659+ try {
660+ await sponsorService . sponsorSpot ( {
661+ spot_id : spot . id ,
662+ sponsor_id : sponsorForm . sponsor_id ,
663+ amount_covered : Number ( sponsorForm . amount_covered ) ,
664+ message : sponsorForm . message ,
665+ } ) ;
666+ setIsSponsorModalOpen ( false ) ;
667+ setSponsorForm ( { sponsor_id : '' , amount_covered : '' , message : '' } ) ;
668+ await fetchData ( ) ;
669+ } catch ( error : any ) {
670+ alert ( `Failed to assign sponsor: ${ error . message || 'Please try again.' } ` ) ;
671+ }
672+ } ;
673+
674+ const handleRemoveSponsor = async ( ) => {
675+ if ( ! spot ) return ;
676+ try {
677+ await sponsorService . removeSponsor ( spot . id ) ;
678+ await fetchData ( ) ;
679+ } catch ( error : any ) {
680+ alert ( `Failed to remove sponsor: ${ error . message || 'Please try again.' } ` ) ;
681+ }
682+ } ;
683+
634684 /* ----------------------------- UI ----------------------------- */
635685
636686 return (
@@ -696,9 +746,23 @@ const HomePage: React.FC = () => {
696746 day : 'numeric'
697747 } ) } at { spot . timing }
698748 </ p >
749+ { spotSponsor && (
750+ < 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" >
751+ < span > 🎉 This spot is sponsored by < strong > { spotSponsor . sponsor ?. name || 'A Bro' } </ strong > !</ span >
752+ < SponsorBadge size = "sm" showLabel = { false } count = { spotSponsor . sponsor ?. sponsor_count || 0 } />
753+ </ div >
754+ ) }
699755 </ div >
700756 { isAdmin && (
701757 < div className = "flex gap-2" >
758+ < Button size = "sm" variant = "secondary" onClick = { ( ) => setIsSponsorModalOpen ( true ) } >
759+ Add Sponsor
760+ </ Button >
761+ { spotSponsor && (
762+ < Button size = "sm" variant = "secondary" onClick = { handleRemoveSponsor } >
763+ Remove Sponsor
764+ </ Button >
765+ ) }
702766 < Button
703767 size = "sm"
704768 variant = "secondary"
@@ -905,6 +969,42 @@ const HomePage: React.FC = () => {
905969 </ >
906970 ) }
907971
972+
973+ < Modal
974+ isOpen = { isSponsorModalOpen }
975+ onClose = { ( ) => setIsSponsorModalOpen ( false ) }
976+ title = "Add Sponsor"
977+ >
978+ < form onSubmit = { handleAssignSponsor } className = "space-y-4" >
979+ < div >
980+ < label className = "text-sm text-zinc-400" > Sponsor</ label >
981+ < select
982+ value = { sponsorForm . sponsor_id }
983+ onChange = { ( e ) => setSponsorForm ( prev => ( { ...prev , sponsor_id : e . target . value } ) ) }
984+ className = "w-full mt-2 bg-zinc-900 border border-white/10 rounded-lg p-3"
985+ required
986+ >
987+ < option value = "" > Select member</ option >
988+ { allProfiles . map ( ( member ) => (
989+ < option key = { member . id } value = { member . id } > { member . name } (@{ member . username } )</ option >
990+ ) ) }
991+ </ select >
992+ </ div >
993+ < Input
994+ label = "Amount Covered"
995+ type = "number"
996+ value = { sponsorForm . amount_covered }
997+ onChange = { ( e ) => setSponsorForm ( prev => ( { ...prev , amount_covered : e . target . value } ) ) }
998+ />
999+ < Textarea
1000+ label = "Message"
1001+ value = { sponsorForm . message }
1002+ onChange = { ( e ) => setSponsorForm ( prev => ( { ...prev , message : e . target . value } ) ) }
1003+ />
1004+ < Button type = "submit" className = "w-full" > Assign Sponsor</ Button >
1005+ </ form >
1006+ </ Modal >
1007+
9081008 { /* CREATE SPOT MODAL */ }
9091009 < Modal
9101010 isOpen = { isCreateSpotModalOpen }
0 commit comments