@@ -24,6 +24,7 @@ import { createMatchEngine } from "../features/dating/MatchEngine";
2424import { DatingFeed } from "../features/dating/DatingFeed" ;
2525import { ChatWindow , type ChatApiClient } from "../features/chat/ChatWindow" ;
2626import type { ChatMessage } from "../features/chat/chat.types" ;
27+ import { MapView } from "../features/map/MapView" ;
2728import placeholderA from "../assets/reddoor-placeholder-1.svg" ;
2829import placeholderB from "../assets/reddoor-placeholder-2.svg" ;
2930import placeholderC from "../assets/reddoor-placeholder-3.svg" ;
@@ -6508,9 +6509,15 @@ function SettingsPanel({
65086509 const [ adminCreateProfileDisplayName , setAdminCreateProfileDisplayName ] = useState < string > ( "" ) ;
65096510 const [ adminCreateProfileAge , setAdminCreateProfileAge ] = useState < string > ( "" ) ;
65106511 const [ adminCreateProfileBio , setAdminCreateProfileBio ] = useState < string > ( "" ) ;
6511- const [ adminCreateProfileLat , setAdminCreateProfileLat ] = useState < string > ( "" ) ;
6512- const [ adminCreateProfileLng , setAdminCreateProfileLng ] = useState < string > ( "" ) ;
6512+ const [ adminCreateMainPhotoFile , setAdminCreateMainPhotoFile ] = useState < File | null > ( null ) ;
6513+ const [ adminCreateGalleryPhotoFiles , setAdminCreateGalleryPhotoFiles ] = useState < ReadonlyArray < File > > ( [ ] ) ;
6514+ const [ adminCreateVideoFile , setAdminCreateVideoFile ] = useState < File | null > ( null ) ;
65136515 const [ adminCreatedUserId , setAdminCreatedUserId ] = useState < string | null > ( null ) ;
6516+ const [ adminPendingUserPlacement , setAdminPendingUserPlacement ] = useState < {
6517+ userId : string ;
6518+ displayName : string ;
6519+ position : { lat : number ; lng : number } ;
6520+ } | null > ( null ) ;
65146521
65156522 async function refreshAdmin ( ) : Promise < void > {
65166523 if ( session . role !== "admin" ) {
@@ -6678,8 +6685,51 @@ function SettingsPanel({
66786685 setAdminStatus ( "Email, password, and display name are required." ) ;
66796686 return ;
66806687 }
6688+
6689+ // Get admin's current location
6690+ if ( ! ( "geolocation" in navigator ) ) {
6691+ setAdminStatus ( "Geolocation not available." ) ;
6692+ return ;
6693+ }
6694+
66816695 setAdminBusy ( true ) ;
6682- setAdminStatus ( "" ) ;
6696+ setAdminStatus ( "Getting your location..." ) ;
6697+
6698+ try {
6699+ const position = await new Promise < GeolocationPosition > ( ( resolve , reject ) => {
6700+ navigator . geolocation . getCurrentPosition ( resolve , reject , {
6701+ enableHighAccuracy : true ,
6702+ timeout : 10000 ,
6703+ maximumAge : 30000
6704+ } ) ;
6705+ } ) ;
6706+
6707+ const adminLocation = {
6708+ lat : position . coords . latitude ,
6709+ lng : position . coords . longitude
6710+ } ;
6711+
6712+ // Create pending user state for placement
6713+ setAdminPendingUserPlacement ( {
6714+ userId : `pending-${ Date . now ( ) } ` ,
6715+ displayName : adminCreateProfileDisplayName . trim ( ) ,
6716+ position : adminLocation
6717+ } ) ;
6718+
6719+ setAdminStatus ( "Position the user on the map and confirm placement." ) ;
6720+ } catch ( e ) {
6721+ setAdminStatus ( "Failed to get your location. Please check location permissions." ) ;
6722+ } finally {
6723+ setAdminBusy ( false ) ;
6724+ }
6725+ }
6726+
6727+ async function adminConfirmUserPlacement ( ) : Promise < void > {
6728+ if ( ! adminPendingUserPlacement ) return ;
6729+
6730+ setAdminBusy ( true ) ;
6731+ setAdminStatus ( "Creating user and profile..." ) ;
6732+
66836733 try {
66846734 // Create the user
66856735 const userResult = await api . adminCreateUser ( session . sessionToken , {
@@ -6694,25 +6744,88 @@ function SettingsPanel({
66946744 const userId = userResult . user . id ;
66956745 setAdminCreatedUserId ( userId ) ;
66966746
6697- // Create the profile
6747+ // Upload photos if provided
6748+ let mainPhotoMediaId : string | undefined ;
6749+ let galleryMediaIds : string [ ] = [ ] ;
6750+
6751+ if ( adminCreateMainPhotoFile ) {
6752+ try {
6753+ const initiated = await api . initiateMediaUpload ( session . sessionToken , {
6754+ kind : "photo_main" ,
6755+ mimeType : adminCreateMainPhotoFile . type || "image/jpeg" ,
6756+ sizeBytes : adminCreateMainPhotoFile . size
6757+ } ) ;
6758+
6759+ const handledLocally = await uploadToLocalSignedUrl ( initiated . uploadUrl , adminCreateMainPhotoFile , adminCreateMainPhotoFile . type || "image/jpeg" ) ;
6760+ if ( ! handledLocally ) {
6761+ const uploadRes = await fetch ( initiated . uploadUrl , {
6762+ method : "PUT" ,
6763+ headers : { "content-type" : adminCreateMainPhotoFile . type || "image/jpeg" } ,
6764+ body : adminCreateMainPhotoFile
6765+ } ) ;
6766+ if ( ! uploadRes . ok ) throw new Error ( `Upload failed (${ uploadRes . status } )` ) ;
6767+ }
6768+
6769+ await api . completeMediaUpload ( session . sessionToken , initiated . mediaId ) ;
6770+ mainPhotoMediaId = initiated . mediaId ;
6771+ } catch ( e ) {
6772+ console . warn ( "Failed to upload main photo:" , e ) ;
6773+ }
6774+ }
6775+
6776+ // Upload gallery photos
6777+ for ( const file of adminCreateGalleryPhotoFiles ) {
6778+ try {
6779+ const initiated = await api . initiateMediaUpload ( session . sessionToken , {
6780+ kind : "photo_gallery" ,
6781+ mimeType : file . type || "image/jpeg" ,
6782+ sizeBytes : file . size
6783+ } ) ;
6784+
6785+ const handledLocally = await uploadToLocalSignedUrl ( initiated . uploadUrl , file , file . type || "image/jpeg" ) ;
6786+ if ( ! handledLocally ) {
6787+ const uploadRes = await fetch ( initiated . uploadUrl , {
6788+ method : "PUT" ,
6789+ headers : { "content-type" : file . type || "image/jpeg" } ,
6790+ body : file
6791+ } ) ;
6792+ if ( ! uploadRes . ok ) throw new Error ( `Upload failed (${ uploadRes . status } )` ) ;
6793+ }
6794+
6795+ await api . completeMediaUpload ( session . sessionToken , initiated . mediaId ) ;
6796+ galleryMediaIds . push ( initiated . mediaId ) ;
6797+ } catch ( e ) {
6798+ console . warn ( "Failed to upload gallery photo:" , e ) ;
6799+ }
6800+ }
6801+
6802+ // Create the profile with location
66986803 const age = parseInt ( adminCreateProfileAge . trim ( ) ) || 25 ;
6699- const lat = parseFloat ( adminCreateProfileLat . trim ( ) ) || undefined ;
6700- const lng = parseFloat ( adminCreateProfileLng . trim ( ) ) || undefined ;
67016804
67026805 const profileData : any = {
67036806 displayName : adminCreateProfileDisplayName . trim ( ) ,
67046807 age,
67056808 bio : adminCreateProfileBio . trim ( ) || ""
67066809 } ;
67076810
6708- if ( lat !== undefined && lng !== undefined ) {
6709- profileData . travelMode = {
6710- enabled : true ,
6711- lat,
6712- lng
6713- } ;
6811+ // Add stats if provided (we'll need to add these to the form)
6812+ // For now, just basic profile
6813+
6814+ if ( mainPhotoMediaId ) {
6815+ profileData . mainPhotoMediaId = mainPhotoMediaId ;
6816+ }
6817+
6818+ if ( galleryMediaIds . length > 0 ) {
6819+ profileData . galleryMediaIds = galleryMediaIds ;
67146820 }
67156821
6822+ // Set travel mode to the confirmed position
6823+ profileData . travelMode = {
6824+ enabled : true ,
6825+ lat : adminPendingUserPlacement . position . lat ,
6826+ lng : adminPendingUserPlacement . position . lng
6827+ } ;
6828+
67166829 await api . adminUpsertProfile ( session . sessionToken , userId , profileData ) ;
67176830
67186831 // Reset form
@@ -6725,8 +6838,10 @@ function SettingsPanel({
67256838 setAdminCreateProfileDisplayName ( "" ) ;
67266839 setAdminCreateProfileAge ( "" ) ;
67276840 setAdminCreateProfileBio ( "" ) ;
6728- setAdminCreateProfileLat ( "" ) ;
6729- setAdminCreateProfileLng ( "" ) ;
6841+ setAdminCreateMainPhotoFile ( null ) ;
6842+ setAdminCreateGalleryPhotoFiles ( [ ] ) ;
6843+ setAdminCreateVideoFile ( null ) ;
6844+ setAdminPendingUserPlacement ( null ) ;
67306845
67316846 await refreshAdmin ( ) ;
67326847 setAdminStatus ( `User and profile created successfully. User ID: ${ userId } ` ) ;
@@ -6739,6 +6854,20 @@ function SettingsPanel({
67396854 }
67406855 }
67416856
6857+ function adminCancelUserPlacement ( ) : void {
6858+ setAdminPendingUserPlacement ( null ) ;
6859+ setAdminStatus ( "User creation cancelled." ) ;
6860+ }
6861+
6862+ function adminUpdatePendingUserPosition ( position : { lat : number ; lng : number } ) : void {
6863+ if ( adminPendingUserPlacement ) {
6864+ setAdminPendingUserPlacement ( {
6865+ ...adminPendingUserPlacement ,
6866+ position
6867+ } ) ;
6868+ }
6869+ }
6870+
67426871 return (
67436872 < div style = { { display : "grid" , gap : 12 } } >
67446873 < div style = { cardStyle ( ) } >
@@ -6912,41 +7041,18 @@ function SettingsPanel({
69127041 placeholder = "John Doe"
69137042 />
69147043 </ label >
6915- < div style = { { display : "grid" , gridTemplateColumns : "1fr 1fr" , gap : 8 } } >
6916- < label style = { { display : "grid" , gap : 4 } } >
6917- < span style = { { fontSize : 13 , color : "#b9bec9" } } > Age</ span >
6918- < input
6919- type = "number"
6920- value = { adminCreateProfileAge }
6921- onChange = { ( e ) => setAdminCreateProfileAge ( e . target . value ) }
6922- style = { { padding : 8 , border : "1px solid #444" , borderRadius : 4 , background : "#2a2d32" , color : "#fff" } }
6923- placeholder = "25"
6924- min = "18"
6925- max = "120"
6926- />
6927- </ label >
6928- < div style = { { display : "grid" , gap : 4 } } >
6929- < span style = { { fontSize : 13 , color : "#b9bec9" } } > Location (optional)</ span >
6930- < div style = { { display : "grid" , gridTemplateColumns : "1fr 1fr" , gap : 4 } } >
6931- < input
6932- type = "number"
6933- value = { adminCreateProfileLat }
6934- onChange = { ( e ) => setAdminCreateProfileLat ( e . target . value ) }
6935- style = { { padding : 8 , border : "1px solid #444" , borderRadius : 4 , background : "#2a2d32" , color : "#fff" } }
6936- placeholder = "Lat"
6937- step = "0.000001"
6938- />
6939- < input
6940- type = "number"
6941- value = { adminCreateProfileLng }
6942- onChange = { ( e ) => setAdminCreateProfileLng ( e . target . value ) }
6943- style = { { padding : 8 , border : "1px solid #444" , borderRadius : 4 , background : "#2a2d32" , color : "#fff" } }
6944- placeholder = "Lng"
6945- step = "0.000001"
6946- />
6947- </ div >
6948- </ div >
6949- </ div >
7044+ < label style = { { display : "grid" , gap : 4 } } >
7045+ < span style = { { fontSize : 13 , color : "#b9bec9" } } > Age</ span >
7046+ < input
7047+ type = "number"
7048+ value = { adminCreateProfileAge }
7049+ onChange = { ( e ) => setAdminCreateProfileAge ( e . target . value ) }
7050+ style = { { padding : 8 , border : "1px solid #444" , borderRadius : 4 , background : "#2a2d32" , color : "#fff" } }
7051+ placeholder = "25"
7052+ min = "18"
7053+ max = "120"
7054+ />
7055+ </ label >
69507056 < label style = { { display : "grid" , gap : 4 } } >
69517057 < span style = { { fontSize : 13 , color : "#b9bec9" } } > Bio (optional)</ span >
69527058 < textarea
@@ -6957,16 +7063,88 @@ function SettingsPanel({
69577063 maxLength = { 280 }
69587064 />
69597065 </ label >
7066+ < label style = { { display : "grid" , gap : 4 } } >
7067+ < span style = { { fontSize : 13 , color : "#b9bec9" } } > Main Photo</ span >
7068+ < input
7069+ type = "file"
7070+ accept = "image/*"
7071+ onChange = { ( e ) => setAdminCreateMainPhotoFile ( e . target . files ?. [ 0 ] || null ) }
7072+ style = { { padding : 8 , border : "1px solid #444" , borderRadius : 4 , background : "#2a2d32" , color : "#fff" } }
7073+ />
7074+ </ label >
7075+ < label style = { { display : "grid" , gap : 4 } } >
7076+ < span style = { { fontSize : 13 , color : "#b9bec9" } } > Gallery Photos</ span >
7077+ < input
7078+ type = "file"
7079+ accept = "image/*"
7080+ multiple
7081+ onChange = { ( e ) => setAdminCreateGalleryPhotoFiles ( Array . from ( e . target . files || [ ] ) ) }
7082+ style = { { padding : 8 , border : "1px solid #444" , borderRadius : 4 , background : "#2a2d32" , color : "#fff" } }
7083+ />
7084+ </ label >
7085+ < label style = { { display : "grid" , gap : 4 } } >
7086+ < span style = { { fontSize : 13 , color : "#b9bec9" } } > Video (optional)</ span >
7087+ < input
7088+ type = "file"
7089+ accept = "video/*"
7090+ onChange = { ( e ) => setAdminCreateVideoFile ( e . target . files ?. [ 0 ] || null ) }
7091+ style = { { padding : 8 , border : "1px solid #444" , borderRadius : 4 , background : "#2a2d32" , color : "#fff" } }
7092+ />
7093+ </ label >
69607094 < button
69617095 type = "button"
69627096 style = { buttonSecondary ( adminBusy ) }
69637097 disabled = { adminBusy }
69647098 onClick = { ( ) => void adminCreateUserAndProfile ( ) }
69657099 >
6966- CREATE USER & PROFILE
7100+ CREATE USER & POSITION ON MAP
69677101 </ button >
69687102 </ div >
69697103 </ div >
7104+
7105+ { adminPendingUserPlacement && (
7106+ < div style = { { display : "grid" , gap : 6 } } >
7107+ < div style = { { fontSize : 15 , fontWeight : 700 } } > POSITION USER ON MAP</ div >
7108+ < div style = { { color : "#b9bec9" , fontSize : 13 } } >
7109+ Drag the marker to position the user, then confirm placement.
7110+ </ div >
7111+ < div style = { { height : 400 , border : "1px solid #444" , borderRadius : 4 , overflow : "hidden" } } >
7112+ < MapView
7113+ initialView = { {
7114+ center : adminPendingUserPlacement . position ,
7115+ zoom : 15
7116+ } }
7117+ markers = { [ {
7118+ id : adminPendingUserPlacement . userId ,
7119+ position : adminPendingUserPlacement . position ,
7120+ color : "#C00000" ,
7121+ draggable : true ,
7122+ onDragEnd : adminUpdatePendingUserPosition
7123+ } ] }
7124+ visible = { true }
7125+ onMapClick = { ( ) => { } }
7126+ />
7127+ </ div >
7128+ < div style = { { display : "grid" , gridTemplateColumns : "auto auto" , gap : 8 } } >
7129+ < button
7130+ type = "button"
7131+ style = { buttonPrimary ( adminBusy ) }
7132+ disabled = { adminBusy }
7133+ onClick = { ( ) => void adminConfirmUserPlacement ( ) }
7134+ >
7135+ { adminBusy ? "CREATING..." : "CONFIRM PLACEMENT & CREATE USER" }
7136+ </ button >
7137+ < button
7138+ type = "button"
7139+ style = { buttonSecondary ( adminBusy ) }
7140+ disabled = { adminBusy }
7141+ onClick = { ( ) => void adminCancelUserPlacement ( ) }
7142+ >
7143+ CANCEL
7144+ </ button >
7145+ </ div >
7146+ </ div >
7147+ ) }
69707148 </ div >
69717149 </ div >
69727150 ) : null }
0 commit comments