@@ -954,6 +954,258 @@ function Details({
954954}
955955
956956
957+ function InstitutionProfile ( ) {
958+ const [ profile , setProfile ] = useState ( {
959+ institutionName : '' ,
960+ streetAddress : '' ,
961+ city : '' ,
962+ state : '' ,
963+ postalCode : '' ,
964+ country : '' ,
965+ defaultCurrency : 'USD' ,
966+ departmentName : '' ,
967+ costCenter : '' ,
968+ logo : ''
969+ } ) ;
970+ const [ status , setStatus ] = useState ( null ) ;
971+ const [ error , setError ] = useState ( null ) ;
972+ const baseDir = PLUGIN_BASE ;
973+
974+ useEffect ( ( ) => {
975+ let cancelled = false ;
976+ async function load ( ) {
977+ try {
978+ let text ;
979+ if ( window . cockpit && window . cockpit . file ) {
980+ text = await window . cockpit . file ( `${ baseDir } /institution.json` ) . read ( ) ;
981+ } else {
982+ const resp = await fetch ( 'institution.json' ) ;
983+ if ( ! resp . ok ) throw new Error ( 'Failed to load profile' ) ;
984+ text = await resp . text ( ) ;
985+ }
986+ if ( cancelled ) return ;
987+ const json = JSON . parse ( text ) ;
988+ setProfile ( p => ( { ...p , ...json } ) ) ;
989+ } catch ( e ) {
990+ console . error ( e ) ;
991+ }
992+ }
993+ load ( ) ;
994+ return ( ) => {
995+ cancelled = true ;
996+ } ;
997+ } , [ ] ) ;
998+
999+ function update ( field , value ) {
1000+ setProfile ( prev => ( { ...prev , [ field ] : value } ) ) ;
1001+ }
1002+
1003+ function handleLogo ( e ) {
1004+ const file = e . target . files && e . target . files [ 0 ] ;
1005+ if ( ! file ) return ;
1006+ const reader = new FileReader ( ) ;
1007+ reader . onload = ( ) => {
1008+ setProfile ( prev => ( { ...prev , logo : reader . result } ) ) ;
1009+ } ;
1010+ reader . readAsDataURL ( file ) ;
1011+ }
1012+
1013+ async function save ( ) {
1014+ try {
1015+ setStatus ( null ) ;
1016+ setError ( null ) ;
1017+ const text = JSON . stringify ( profile , null , 2 ) ;
1018+ if ( window . cockpit && window . cockpit . file ) {
1019+ await window . cockpit . file ( `${ baseDir } /institution.json` ) . replace ( text ) ;
1020+ } else {
1021+ await fetch ( 'institution.json' , {
1022+ method : 'PUT' ,
1023+ headers : { 'Content-Type' : 'application/json' } ,
1024+ body : text
1025+ } ) ;
1026+ }
1027+ setStatus ( 'Saved' ) ;
1028+ } catch ( e ) {
1029+ console . error ( e ) ;
1030+ setError ( 'Failed to save profile' ) ;
1031+ }
1032+ }
1033+
1034+ return React . createElement (
1035+ 'div' ,
1036+ { className : 'institution-profile' } ,
1037+ React . createElement ( 'h2' , null , 'Institution Profile' ) ,
1038+ React . createElement (
1039+ 'p' ,
1040+ null ,
1041+ "Enter your university's name, contact details, and other key information"
1042+ ) ,
1043+ React . createElement (
1044+ 'div' ,
1045+ null ,
1046+ React . createElement (
1047+ 'label' ,
1048+ null ,
1049+ 'Logo: ' ,
1050+ React . createElement ( 'input' , {
1051+ type : 'file' ,
1052+ accept : 'image/*' ,
1053+ onChange : handleLogo
1054+ } )
1055+ ) ,
1056+ profile . logo &&
1057+ React . createElement ( 'img' , {
1058+ src : profile . logo ,
1059+ alt : 'Logo' ,
1060+ className : 'institution-logo-preview'
1061+ } )
1062+ ) ,
1063+ React . createElement (
1064+ 'div' ,
1065+ null ,
1066+ React . createElement (
1067+ 'label' ,
1068+ null ,
1069+ 'Institution Name: ' ,
1070+ React . createElement ( 'input' , {
1071+ type : 'text' ,
1072+ value : profile . institutionName ,
1073+ onChange : e => update ( 'institutionName' , e . target . value )
1074+ } )
1075+ )
1076+ ) ,
1077+ React . createElement (
1078+ 'div' ,
1079+ null ,
1080+ React . createElement (
1081+ 'label' ,
1082+ null ,
1083+ 'Street Address: ' ,
1084+ React . createElement ( 'input' , {
1085+ type : 'text' ,
1086+ value : profile . streetAddress ,
1087+ onChange : e => update ( 'streetAddress' , e . target . value )
1088+ } )
1089+ )
1090+ ) ,
1091+ React . createElement (
1092+ 'div' ,
1093+ null ,
1094+ React . createElement (
1095+ 'label' ,
1096+ null ,
1097+ 'City: ' ,
1098+ React . createElement ( 'input' , {
1099+ type : 'text' ,
1100+ value : profile . city ,
1101+ onChange : e => update ( 'city' , e . target . value )
1102+ } )
1103+ )
1104+ ) ,
1105+ React . createElement (
1106+ 'div' ,
1107+ null ,
1108+ React . createElement (
1109+ 'label' ,
1110+ null ,
1111+ 'State/Province: ' ,
1112+ React . createElement ( 'input' , {
1113+ type : 'text' ,
1114+ value : profile . state ,
1115+ onChange : e => update ( 'state' , e . target . value )
1116+ } )
1117+ )
1118+ ) ,
1119+ React . createElement (
1120+ 'div' ,
1121+ null ,
1122+ React . createElement (
1123+ 'label' ,
1124+ null ,
1125+ 'Postal Code: ' ,
1126+ React . createElement ( 'input' , {
1127+ type : 'text' ,
1128+ value : profile . postalCode ,
1129+ onChange : e => update ( 'postalCode' , e . target . value )
1130+ } )
1131+ )
1132+ ) ,
1133+ React . createElement (
1134+ 'div' ,
1135+ null ,
1136+ React . createElement (
1137+ 'label' ,
1138+ null ,
1139+ 'Country: ' ,
1140+ React . createElement ( 'input' , {
1141+ type : 'text' ,
1142+ value : profile . country ,
1143+ onChange : e => update ( 'country' , e . target . value )
1144+ } )
1145+ )
1146+ ) ,
1147+ React . createElement (
1148+ 'div' ,
1149+ null ,
1150+ React . createElement (
1151+ 'label' ,
1152+ null ,
1153+ 'Default Currency: ' ,
1154+ React . createElement (
1155+ 'select' ,
1156+ {
1157+ value : profile . defaultCurrency ,
1158+ onChange : e => update ( 'defaultCurrency' , e . target . value )
1159+ } ,
1160+ [ 'USD' , 'EUR' , 'GBP' , 'CAD' , 'AUD' ] . map ( c =>
1161+ React . createElement ( 'option' , { key : c , value : c } , c )
1162+ )
1163+ )
1164+ )
1165+ ) ,
1166+ React . createElement (
1167+ 'div' ,
1168+ null ,
1169+ React . createElement (
1170+ 'label' ,
1171+ null ,
1172+ 'Recharge Unit/Department Name: ' ,
1173+ React . createElement ( 'input' , {
1174+ type : 'text' ,
1175+ value : profile . departmentName ,
1176+ onChange : e => update ( 'departmentName' , e . target . value )
1177+ } )
1178+ )
1179+ ) ,
1180+ React . createElement (
1181+ 'div' ,
1182+ null ,
1183+ React . createElement (
1184+ 'label' ,
1185+ null ,
1186+ 'Recharge Cost Center / Chart String: ' ,
1187+ React . createElement ( 'input' , {
1188+ type : 'text' ,
1189+ value : profile . costCenter ,
1190+ onChange : e => update ( 'costCenter' , e . target . value )
1191+ } )
1192+ )
1193+ ) ,
1194+ React . createElement (
1195+ 'div' ,
1196+ { style : { marginTop : '1em' } } ,
1197+ React . createElement ( 'button' , { onClick : save } , 'Save' ) ,
1198+ status && React . createElement ( 'span' , { style : { marginLeft : '0.5em' } } , status ) ,
1199+ error &&
1200+ React . createElement (
1201+ 'span' ,
1202+ { className : 'error' , style : { marginLeft : '0.5em' } } ,
1203+ error
1204+ )
1205+ )
1206+ ) ;
1207+ }
1208+
9571209function Rates ( { onRatesUpdated } ) {
9581210 const [ config , setConfig ] = useState ( null ) ;
9591211 const [ overrides , setOverrides ] = useState ( [ ] ) ;
@@ -1117,6 +1369,7 @@ function Rates({ onRatesUpdated }) {
11171369 return React . createElement (
11181370 'div' ,
11191371 null ,
1372+ React . createElement ( InstitutionProfile , null ) ,
11201373 React . createElement ( 'h2' , null , 'Rate Configuration' ) ,
11211374 React . createElement (
11221375 'div' ,
0 commit comments