Refactor org app structure and portal interactions
This commit is contained in:
parent
cdfc8dda80
commit
e0e6121a5c
@ -1,3 +1,4 @@
|
|||||||
|
PREP(buildPortalPayload);
|
||||||
PREP(handleUIEvents);
|
PREP(handleUIEvents);
|
||||||
PREP(initOrgClass);
|
PREP(initOrgClass);
|
||||||
PREP(openUI);
|
PREP(openUI);
|
||||||
|
|||||||
@ -18,6 +18,32 @@ if (isNil QGVAR(OrgClass)) then { call FUNC(initOrgClass); };
|
|||||||
GVAR(OrgClass) call ["sync", [_data, _jip]];
|
GVAR(OrgClass) call ["sync", [_data, _jip]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseCreateOrg), {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _control = uiNamespace getVariable [QGVAR(PendingBrowserControl), controlNull];
|
||||||
|
uiNamespace setVariable [QGVAR(PendingBrowserControl), controlNull];
|
||||||
|
|
||||||
|
private _success = _payload getOrDefault ["success", false];
|
||||||
|
if (!_success) exitWith {
|
||||||
|
if (_control isNotEqualTo controlNull) then {
|
||||||
|
private _json = toJSON (createHashMapFromArray [
|
||||||
|
["message", _payload getOrDefault ["message", "Organization registration failed."]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
_control ctrlWebBrowserAction ["ExecJS", format ["OrgUIBridge.receiveCreateFailure(%1)", _json]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _orgData = _payload getOrDefault ["org", createHashMap];
|
||||||
|
GVAR(OrgClass) call ["sync", [_orgData, true]];
|
||||||
|
|
||||||
|
if (_control isNotEqualTo controlNull) then {
|
||||||
|
private _json = toJSON (call FUNC(buildPortalPayload));
|
||||||
|
_control ctrlWebBrowserAction ["ExecJS", format ["OrgUIBridge.receiveCreateSuccess(%1)", _json]];
|
||||||
|
};
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
EGVAR(actor,ActorClass) get "isLoaded";
|
EGVAR(actor,ActorClass) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
129
arma/client/addons/org/functions/fnc_buildPortalPayload.sqf
Normal file
129
arma/client/addons/org/functions/fnc_buildPortalPayload.sqf
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Builds the web portal payload from the synced org class.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Portal payload <HASHMAP>
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_client_org_fnc_buildPortalPayload;
|
||||||
|
*
|
||||||
|
* Public: No
|
||||||
|
*/
|
||||||
|
|
||||||
|
private _orgData = GVAR(OrgClass) get "org";
|
||||||
|
|
||||||
|
private _name = _orgData getOrDefault ["name", "Unknown Organization"];
|
||||||
|
private _id = _orgData getOrDefault ["id", ""];
|
||||||
|
private _ownerUid = _orgData getOrDefault ["owner", ""];
|
||||||
|
private _funds = _orgData getOrDefault ["funds", 0];
|
||||||
|
private _reputation = _orgData getOrDefault ["reputation", 0];
|
||||||
|
private _assetsRaw = _orgData getOrDefault ["assets", createHashMap];
|
||||||
|
private _membersRaw = _orgData getOrDefault ["members", createHashMap];
|
||||||
|
private _fleetRaw = _orgData getOrDefault ["fleet", createHashMap];
|
||||||
|
private _headquarters = _orgData getOrDefault ["headquarters", "ArmA Verse"];
|
||||||
|
private _type = _orgData getOrDefault ["type", "Organization"];
|
||||||
|
private _status = _orgData getOrDefault ["status", "Operational"];
|
||||||
|
private _isDefaultOrg = (_orgData getOrDefault ["default", false])
|
||||||
|
|| {toLower _id isEqualTo "default"}
|
||||||
|
|| {toLower _ownerUid isEqualTo "server"};
|
||||||
|
|
||||||
|
private _playerName = name player;
|
||||||
|
private _playerUid = getPlayerUID player;
|
||||||
|
private _playerVar = vehicleVarName player;
|
||||||
|
private _sessionRole = "Member";
|
||||||
|
private _sessionIsCeo = _isDefaultOrg && {_playerVar isEqualTo "ceo"};
|
||||||
|
private _ownerName = ["", "Server"] select (toLower _ownerUid isEqualTo "server");
|
||||||
|
|
||||||
|
private _membersList = [];
|
||||||
|
{
|
||||||
|
private _memberData = _y;
|
||||||
|
private _memberName = _memberData getOrDefault ["name", "Unknown"];
|
||||||
|
private _memberUid = _memberData getOrDefault ["uid", ""];
|
||||||
|
|
||||||
|
if (_memberUid isEqualTo _ownerUid && {_ownerName isEqualTo ""}) then { _ownerName = _memberName; };
|
||||||
|
if (_memberUid isEqualTo _playerUid) then { _sessionRole = "Member"; };
|
||||||
|
|
||||||
|
_membersList pushBack (createHashMapFromArray [["name", _memberName]]);
|
||||||
|
} forEach _membersRaw;
|
||||||
|
|
||||||
|
if (_ownerName isEqualTo "" && { _ownerUid isEqualTo _playerUid }) then { _ownerName = _playerName; };
|
||||||
|
if (_ownerName isEqualTo "" && { _ownerUid isNotEqualTo "" }) then { _ownerName = "Unknown Owner"; };
|
||||||
|
if (_ownerUid isEqualTo _playerUid) then { _sessionRole = "Leader"; };
|
||||||
|
|
||||||
|
private _assetsList = [];
|
||||||
|
{
|
||||||
|
private _assetData = _y;
|
||||||
|
_assetsList pushBack (createHashMapFromArray [
|
||||||
|
["name", _assetData getOrDefault ["name", "Unknown Asset"]],
|
||||||
|
["type", _assetData getOrDefault ["type", "items"]],
|
||||||
|
["quantity", str (_assetData getOrDefault ["quantity", 0])]
|
||||||
|
]);
|
||||||
|
} forEach _assetsRaw;
|
||||||
|
|
||||||
|
private _fleetList = [];
|
||||||
|
{
|
||||||
|
private _vehicleData = _y;
|
||||||
|
_fleetList pushBack (createHashMapFromArray [
|
||||||
|
["name", _vehicleData getOrDefault ["name", "Unknown Vehicle"]],
|
||||||
|
["type", _vehicleData getOrDefault ["type", "other"]],
|
||||||
|
["status", _vehicleData getOrDefault ["status", "Unknown"]],
|
||||||
|
["damage", _vehicleData getOrDefault ["damage", "0%"]]
|
||||||
|
]);
|
||||||
|
} forEach _fleetRaw;
|
||||||
|
|
||||||
|
private _roadmap = [
|
||||||
|
createHashMapFromArray [
|
||||||
|
["name", "Contracts Board"],
|
||||||
|
["status", "Planned"],
|
||||||
|
["detail", "Track payouts, assignments, and claim approvals."]
|
||||||
|
],
|
||||||
|
createHashMapFromArray [
|
||||||
|
["name", "Diplomacy"],
|
||||||
|
["status", "Future Review"],
|
||||||
|
["detail", "Possible future module pending a full design and scope review."]
|
||||||
|
],
|
||||||
|
createHashMapFromArray [
|
||||||
|
["name", "Logistics Queue"],
|
||||||
|
["status", "Future Review"],
|
||||||
|
["detail", "Possible future module pending a full design and scope review."]
|
||||||
|
],
|
||||||
|
createHashMapFromArray [
|
||||||
|
["name", "Permissions"],
|
||||||
|
["status", "Future Review"],
|
||||||
|
["detail", "Possible future module pending a full design and scope review."]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
createHashMapFromArray [
|
||||||
|
["session", createHashMapFromArray [
|
||||||
|
["actorName", _playerName],
|
||||||
|
["actorUid", _playerUid],
|
||||||
|
["role", _sessionRole],
|
||||||
|
["ceo", _sessionIsCeo]
|
||||||
|
]],
|
||||||
|
["portalData", createHashMapFromArray [
|
||||||
|
["org", createHashMapFromArray [
|
||||||
|
["name", _name],
|
||||||
|
["tag", _id],
|
||||||
|
["type", _type],
|
||||||
|
["status", _status],
|
||||||
|
["headquarters", _headquarters],
|
||||||
|
["owner", _ownerName],
|
||||||
|
["ownerUid", _ownerUid],
|
||||||
|
["isDefault", _isDefaultOrg]
|
||||||
|
]],
|
||||||
|
["funds", _funds],
|
||||||
|
["reputation", _reputation],
|
||||||
|
["members", _membersList],
|
||||||
|
["fleet", _fleetList],
|
||||||
|
["assets", _assetsList],
|
||||||
|
["activity", []],
|
||||||
|
["roadmap", _roadmap]
|
||||||
|
]]
|
||||||
|
]
|
||||||
@ -30,159 +30,35 @@ private _fnc_execBridge = {
|
|||||||
_control ctrlWebBrowserAction ["ExecJS", format ["OrgUIBridge.%1(%2)", _fnName, _json]];
|
_control ctrlWebBrowserAction ["ExecJS", format ["OrgUIBridge.%1(%2)", _fnName, _json]];
|
||||||
};
|
};
|
||||||
|
|
||||||
private _fnc_buildPortalPayload = {
|
|
||||||
private _orgData = GVAR(OrgClass) get "org";
|
|
||||||
|
|
||||||
private _name = _orgData getOrDefault ["name", "Unknown Organization"];
|
|
||||||
private _id = _orgData getOrDefault ["id", ""];
|
|
||||||
private _ownerUid = _orgData getOrDefault ["owner", ""];
|
|
||||||
private _funds = _orgData getOrDefault ["funds", 0];
|
|
||||||
private _reputation = _orgData getOrDefault ["reputation", 0];
|
|
||||||
private _assetsRaw = _orgData getOrDefault ["assets", createHashMap];
|
|
||||||
private _membersRaw = _orgData getOrDefault ["members", createHashMap];
|
|
||||||
private _fleetRaw = _orgData getOrDefault ["fleet", createHashMap];
|
|
||||||
private _headquarters = _orgData getOrDefault ["headquarters", "ArmA Verse"];
|
|
||||||
private _type = _orgData getOrDefault ["type", "Organization"];
|
|
||||||
private _status = _orgData getOrDefault ["status", "Operational"];
|
|
||||||
private _isDefaultOrg = (_orgData getOrDefault ["default", false])
|
|
||||||
|| {toLower _id isEqualTo "default"}
|
|
||||||
|| {toLower _ownerUid isEqualTo "server"};
|
|
||||||
|
|
||||||
private _playerName = name player;
|
|
||||||
private _playerUid = getPlayerUID player;
|
|
||||||
private _playerVar = vehicleVarName player;
|
|
||||||
private _sessionRole = "Member";
|
|
||||||
private _sessionIsCeo = _isDefaultOrg && {_playerVar isEqualTo "ceo"};
|
|
||||||
private _ownerName = ["", "Server"] select (toLower _ownerUid isEqualTo "server");
|
|
||||||
|
|
||||||
private _membersList = [];
|
|
||||||
{
|
|
||||||
private _memberData = _y;
|
|
||||||
private _memberName = _memberData getOrDefault ["name", "Unknown"];
|
|
||||||
private _memberUid = _memberData getOrDefault ["uid", ""];
|
|
||||||
|
|
||||||
if (_memberUid isEqualTo _ownerUid && {_ownerName isEqualTo ""}) then {
|
|
||||||
_ownerName = _memberName;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_memberUid isEqualTo _playerUid) then {
|
|
||||||
_sessionRole = "Member";
|
|
||||||
};
|
|
||||||
|
|
||||||
_membersList pushBack (createHashMapFromArray [
|
|
||||||
["name", _memberName]
|
|
||||||
]);
|
|
||||||
} forEach _membersRaw;
|
|
||||||
|
|
||||||
if (_ownerName isEqualTo "" && {_ownerUid isEqualTo _playerUid}) then {
|
|
||||||
_ownerName = _playerName;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_ownerName isEqualTo "" && {_ownerUid isNotEqualTo ""}) then {
|
|
||||||
_ownerName = "Unknown Owner";
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_ownerUid isEqualTo _playerUid) then {
|
|
||||||
_sessionRole = "Leader";
|
|
||||||
};
|
|
||||||
|
|
||||||
private _assetsList = [];
|
|
||||||
{
|
|
||||||
private _assetData = _y;
|
|
||||||
_assetsList pushBack (createHashMapFromArray [
|
|
||||||
["name", _assetData getOrDefault ["name", "Unknown Asset"]],
|
|
||||||
["type", _assetData getOrDefault ["type", "items"]],
|
|
||||||
["quantity", str (_assetData getOrDefault ["quantity", 0])]
|
|
||||||
]);
|
|
||||||
} forEach _assetsRaw;
|
|
||||||
|
|
||||||
private _fleetList = [];
|
|
||||||
{
|
|
||||||
private _vehicleData = _y;
|
|
||||||
_fleetList pushBack (createHashMapFromArray [
|
|
||||||
["name", _vehicleData getOrDefault ["name", "Unknown Vehicle"]],
|
|
||||||
["type", _vehicleData getOrDefault ["type", "other"]],
|
|
||||||
["status", _vehicleData getOrDefault ["status", "Unknown"]],
|
|
||||||
["damage", _vehicleData getOrDefault ["damage", "0%"]]
|
|
||||||
]);
|
|
||||||
} forEach _fleetRaw;
|
|
||||||
|
|
||||||
private _roadmap = [
|
|
||||||
createHashMapFromArray [
|
|
||||||
["name", "Contracts Board"],
|
|
||||||
["status", "Planned"],
|
|
||||||
["detail", "Track payouts, assignments, and claim approvals."]
|
|
||||||
],
|
|
||||||
createHashMapFromArray [
|
|
||||||
["name", "Diplomacy"],
|
|
||||||
["status", "Future Review"],
|
|
||||||
["detail", "Possible future module pending a full design and scope review."]
|
|
||||||
],
|
|
||||||
createHashMapFromArray [
|
|
||||||
["name", "Logistics Queue"],
|
|
||||||
["status", "Future Review"],
|
|
||||||
["detail", "Possible future module pending a full design and scope review."]
|
|
||||||
],
|
|
||||||
createHashMapFromArray [
|
|
||||||
["name", "Permissions"],
|
|
||||||
["status", "Future Review"],
|
|
||||||
["detail", "Possible future module pending a full design and scope review."]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
createHashMapFromArray [
|
|
||||||
["session", createHashMapFromArray [
|
|
||||||
["actorName", _playerName],
|
|
||||||
["actorUid", _playerUid],
|
|
||||||
["role", _sessionRole],
|
|
||||||
["ceo", _sessionIsCeo]
|
|
||||||
]],
|
|
||||||
["portalData", createHashMapFromArray [
|
|
||||||
["org", createHashMapFromArray [
|
|
||||||
["name", _name],
|
|
||||||
["tag", _id],
|
|
||||||
["type", _type],
|
|
||||||
["status", _status],
|
|
||||||
["headquarters", _headquarters],
|
|
||||||
["owner", _ownerName],
|
|
||||||
["ownerUid", _ownerUid],
|
|
||||||
["isDefault", _isDefaultOrg]
|
|
||||||
]],
|
|
||||||
["funds", _funds],
|
|
||||||
["reputation", _reputation],
|
|
||||||
["members", _membersList],
|
|
||||||
["fleet", _fleetList],
|
|
||||||
["assets", _assetsList],
|
|
||||||
["activity", []],
|
|
||||||
["roadmap", _roadmap]
|
|
||||||
]]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
diag_log format ["[FORGE:Client:Org] Handling UI event: %1 with data: %2", _event, _data];
|
diag_log format ["[FORGE:Client:Org] Handling UI event: %1 with data: %2", _event, _data];
|
||||||
|
|
||||||
switch (_event) do {
|
switch (_event) do {
|
||||||
case "org::close": { closeDialog 1; };
|
case "org::close": { closeDialog 1; };
|
||||||
case "org::login::request": {
|
case "org::login::request": {
|
||||||
private _email = toLower (_data getOrDefault ["email", ""]);
|
|
||||||
private _password = _data getOrDefault ["password", ""];
|
|
||||||
private _orgData = GVAR(OrgClass) get "org";
|
private _orgData = GVAR(OrgClass) get "org";
|
||||||
private _orgId = _orgData getOrDefault ["id", ""];
|
private _orgId = _orgData getOrDefault ["id", ""];
|
||||||
private _orgName = _orgData getOrDefault ["name", ""];
|
private _orgName = _orgData getOrDefault ["name", ""];
|
||||||
|
|
||||||
if (_email isEqualTo "" || {_password isEqualTo ""}) exitWith {
|
|
||||||
[_control, "receiveLoginFailure", createHashMapFromArray [
|
|
||||||
["message", "Enter both email and password."]
|
|
||||||
]] call _fnc_execBridge;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_orgId isEqualTo "" && {_orgName isEqualTo ""}) exitWith {
|
if (_orgId isEqualTo "" && {_orgName isEqualTo ""}) exitWith {
|
||||||
[_control, "receiveLoginFailure", createHashMapFromArray [
|
[_control, "receiveLoginFailure", createHashMapFromArray [
|
||||||
["message", "No organization data is available for this player."]
|
["message", "No organization data is available for this player."]
|
||||||
]] call _fnc_execBridge;
|
]] call _fnc_execBridge;
|
||||||
};
|
};
|
||||||
|
|
||||||
[_control, "receiveLoginSuccess", call _fnc_buildPortalPayload] call _fnc_execBridge;
|
private _payload = call FUNC(buildPortalPayload);
|
||||||
|
[_control, "receiveLoginSuccess", _payload] call _fnc_execBridge;
|
||||||
|
};
|
||||||
|
case "org::create::request": {
|
||||||
|
private _orgName = _data getOrDefault ["orgName", ""];
|
||||||
|
|
||||||
|
if (_orgName isEqualTo "") exitWith {
|
||||||
|
[_control, "receiveCreateFailure", createHashMapFromArray [
|
||||||
|
["message", "Enter an organization name."]
|
||||||
|
]] call _fnc_execBridge;
|
||||||
|
};
|
||||||
|
|
||||||
|
uiNamespace setVariable [QGVAR(PendingBrowserControl), _control];
|
||||||
|
[SRPC(org,requestCreateOrg), [getPlayerUID player, _orgName]] call CFUNC(serverEvent);
|
||||||
};
|
};
|
||||||
case "org::ready": {
|
case "org::ready": {
|
||||||
[_control, "receive", createHashMapFromArray [
|
[_control, "receive", createHashMapFromArray [
|
||||||
|
|||||||
@ -30,6 +30,17 @@
|
|||||||
store.failLogin("Arma login bridge is unavailable.");
|
store.failLogin("Arma login bridge is unavailable.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestCreateOrg(registration) {
|
||||||
|
store.startCreate();
|
||||||
|
|
||||||
|
const sent = sendEvent("org::create::request", registration);
|
||||||
|
if (sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.failCreate("Arma registration bridge is unavailable.");
|
||||||
|
}
|
||||||
|
|
||||||
function receive(eventOrPayload, data = {}) {
|
function receive(eventOrPayload, data = {}) {
|
||||||
const event =
|
const event =
|
||||||
typeof eventOrPayload === "object" && eventOrPayload !== null
|
typeof eventOrPayload === "object" && eventOrPayload !== null
|
||||||
@ -49,18 +60,33 @@
|
|||||||
store.failLogin(payloadData.message || "Authentication failed.");
|
store.failLogin(payloadData.message || "Authentication failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event === "org::create::success") {
|
||||||
|
store.completeCreate(payloadData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event === "org::create::failure") {
|
||||||
|
store.failCreate(
|
||||||
|
payloadData.message || "Organization registration failed.",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RegistryApp.bridge = {
|
RegistryApp.bridge = {
|
||||||
requestLogin,
|
requestLogin,
|
||||||
|
requestCreateOrg,
|
||||||
receive,
|
receive,
|
||||||
sendEvent,
|
sendEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
window.OrgUIBridge = {
|
window.OrgUIBridge = {
|
||||||
requestLogin,
|
requestLogin,
|
||||||
|
requestCreateOrg,
|
||||||
receive,
|
receive,
|
||||||
receiveLoginSuccess: (data) => receive("org::login::success", data),
|
receiveLoginSuccess: (data) => receive("org::login::success", data),
|
||||||
receiveLoginFailure: (data) => receive("org::login::failure", data),
|
receiveLoginFailure: (data) => receive("org::login::failure", data),
|
||||||
|
receiveCreateSuccess: (data) => receive("org::create::success", data),
|
||||||
|
receiveCreateFailure: (data) => receive("org::create::failure", data),
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
107
arma/client/addons/org/ui/_site/components/AppShell.js
Normal file
107
arma/client/addons/org/ui/_site/components/AppShell.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
(function () {
|
||||||
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const { h } = RegistryApp.runtime;
|
||||||
|
const store = RegistryApp.store;
|
||||||
|
|
||||||
|
RegistryApp.components = RegistryApp.components || {};
|
||||||
|
|
||||||
|
RegistryApp.components.App = function App() {
|
||||||
|
const Navbar = window.SharedUI.componentFns.Navbar;
|
||||||
|
const Header = window.SharedUI.componentFns.Header;
|
||||||
|
const Footer = window.SharedUI.componentFns.Footer;
|
||||||
|
const HomeView = RegistryApp.componentFns.HomeView;
|
||||||
|
const RegistrationView = RegistryApp.componentFns.RegistrationView;
|
||||||
|
const PortalApp =
|
||||||
|
window.OrgPortal && window.OrgPortal.components
|
||||||
|
? window.OrgPortal.components.App
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const view = store.getView();
|
||||||
|
const viewLabel =
|
||||||
|
view === "create"
|
||||||
|
? "Organization Registration"
|
||||||
|
: view === "portal"
|
||||||
|
? "Organization Portal"
|
||||||
|
: "Entry Hub";
|
||||||
|
const actionLabel = view === "portal" ? "Sign Out" : "Close";
|
||||||
|
const footerSections = [
|
||||||
|
{
|
||||||
|
title: "Registry Resources",
|
||||||
|
items: [
|
||||||
|
"Registration Guidelines",
|
||||||
|
"Tax & Fee Schedule",
|
||||||
|
"Legal Compliance",
|
||||||
|
"Trademark Database",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Bureau Support",
|
||||||
|
items: [
|
||||||
|
"Office: Sector 7 Admin Block",
|
||||||
|
"Hours: 0800 - 1600 (GST)",
|
||||||
|
"Helpdesk: 555-01-REGISTRY",
|
||||||
|
"support@org-bureau.gov",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function closeRegistry() {
|
||||||
|
if (
|
||||||
|
typeof A3API !== "undefined" &&
|
||||||
|
typeof A3API.SendAlert === "function"
|
||||||
|
) {
|
||||||
|
A3API.SendAlert(
|
||||||
|
JSON.stringify({
|
||||||
|
event: "org::close",
|
||||||
|
data: {},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setView("home");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (view === "portal" && PortalApp) {
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
Navbar({
|
||||||
|
title: "Global Organization Network",
|
||||||
|
viewLabel,
|
||||||
|
actionLabel,
|
||||||
|
onAction: closeRegistry,
|
||||||
|
}),
|
||||||
|
PortalApp(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mainContent;
|
||||||
|
if (view === "home") {
|
||||||
|
mainContent = HomeView();
|
||||||
|
} else if (view === "create") {
|
||||||
|
mainContent = RegistrationView();
|
||||||
|
}
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"main",
|
||||||
|
null,
|
||||||
|
Navbar({
|
||||||
|
title: "Global Organization Network",
|
||||||
|
viewLabel,
|
||||||
|
actionLabel,
|
||||||
|
onAction: closeRegistry,
|
||||||
|
}),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "container" },
|
||||||
|
Header({
|
||||||
|
title: "Global Organization Network",
|
||||||
|
onTitleClick: () => store.setView("home"),
|
||||||
|
}),
|
||||||
|
mainContent,
|
||||||
|
),
|
||||||
|
Footer({ sections: footerSections }),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -1,40 +1,29 @@
|
|||||||
(function () {
|
(function () {
|
||||||
|
const SharedUI = (window.SharedUI = window.SharedUI || {});
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
const { h } = RegistryApp.runtime;
|
const { h } = RegistryApp.runtime;
|
||||||
|
|
||||||
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
SharedUI.componentFns = SharedUI.componentFns || {};
|
||||||
|
|
||||||
RegistryApp.componentFns.Footer = function Footer() {
|
SharedUI.componentFns.Footer = function Footer({ sections = [] }) {
|
||||||
return h(
|
return h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "footer" },
|
{ className: "footer" },
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "wrapper" },
|
{ className: "wrapper" },
|
||||||
h(
|
...sections.map((section) =>
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h3", null, "Registry Resources"),
|
|
||||||
h(
|
h(
|
||||||
"ul",
|
"div",
|
||||||
{ style: { listStyleType: "none", padding: 0 } },
|
null,
|
||||||
h("li", null, "Registration Guidelines"),
|
h("h3", null, section.title),
|
||||||
h("li", null, "Tax & Fee Schedule"),
|
h(
|
||||||
h("li", null, "Legal Compliance"),
|
"ul",
|
||||||
h("li", null, "Trademark Database"),
|
{ style: { listStyleType: "none", padding: 0 } },
|
||||||
),
|
...(section.items || []).map((item) =>
|
||||||
),
|
h("li", null, item),
|
||||||
h(
|
),
|
||||||
"div",
|
),
|
||||||
null,
|
|
||||||
h("h3", null, "Bureau Support"),
|
|
||||||
h(
|
|
||||||
"ul",
|
|
||||||
{ style: { listStyleType: "none", padding: 0 } },
|
|
||||||
h("li", null, "Office: Sector 7 Admin Block"),
|
|
||||||
h("li", null, "Hours: 0800 - 1600 (GST)"),
|
|
||||||
h("li", null, "Helpdesk: 555-01-REGISTRY"),
|
|
||||||
h("li", null, "support@org-bureau.gov"),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,84 +0,0 @@
|
|||||||
.split-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 2rem;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-panel {
|
|
||||||
text-align: left;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.75rem;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: var(--bg-app);
|
|
||||||
color: var(--text-main);
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 1rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
transition: border-color 0.2s;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--primary);
|
|
||||||
box-shadow: 0 0 0 2px rgb(59 130 246 / 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-actions {
|
|
||||||
margin-top: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-link {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-feedback {
|
|
||||||
padding: 0.85rem 1rem;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
font-size: 0.92rem;
|
|
||||||
|
|
||||||
&.is-error {
|
|
||||||
background: #fef2f2;
|
|
||||||
border: 1px solid #fecaca;
|
|
||||||
color: #991b1b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
|
||||||
.split-container {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +1,27 @@
|
|||||||
(function () {
|
(function () {
|
||||||
|
const SharedUI = (window.SharedUI = window.SharedUI || {});
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
const { h } = RegistryApp.runtime;
|
const { h } = RegistryApp.runtime;
|
||||||
const store = RegistryApp.store;
|
|
||||||
|
|
||||||
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
SharedUI.componentFns = SharedUI.componentFns || {};
|
||||||
|
|
||||||
RegistryApp.componentFns.Header = function Header({ title }) {
|
SharedUI.componentFns.Header = function Header({
|
||||||
|
title,
|
||||||
|
subtitle = "Organization Registration & Management Portal",
|
||||||
|
onTitleClick = null,
|
||||||
|
}) {
|
||||||
return h(
|
return h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "header" },
|
{ className: "header" },
|
||||||
h(
|
h(
|
||||||
"h1",
|
"h1",
|
||||||
{
|
{
|
||||||
style: { cursor: "pointer" },
|
style: { cursor: onTitleClick ? "pointer" : "default" },
|
||||||
onClick: () => store.setView("home"),
|
onClick: onTitleClick,
|
||||||
},
|
},
|
||||||
title,
|
title,
|
||||||
),
|
),
|
||||||
h("p", null, "Organization Registration & Management Portal"),
|
h("p", null, subtitle),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
35
arma/client/addons/org/ui/_site/components/hero.js
Normal file
35
arma/client/addons/org/ui/_site/components/hero.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedUI = (window.SharedUI = window.SharedUI || {});
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h } = OrgPortal.runtime;
|
||||||
|
|
||||||
|
SharedUI.componentFns = SharedUI.componentFns || {};
|
||||||
|
|
||||||
|
SharedUI.componentFns.Hero = function Hero({
|
||||||
|
className = "",
|
||||||
|
kicker = "",
|
||||||
|
title = "",
|
||||||
|
subtitle = "",
|
||||||
|
meta = "",
|
||||||
|
}) {
|
||||||
|
const finalClassName = [
|
||||||
|
"card org-panel org-span-12 org-page-header",
|
||||||
|
className,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"section",
|
||||||
|
{ className: finalClassName },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-page-heading" },
|
||||||
|
h("span", { className: "org-page-kicker" }, kicker),
|
||||||
|
h("h1", { className: "org-page-title" }, title),
|
||||||
|
h("p", { className: "org-page-subtitle" }, subtitle),
|
||||||
|
h("span", { className: "org-page-meta" }, meta),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -1,12 +0,0 @@
|
|||||||
.content {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 2rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
|
||||||
.content {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
|
||||||
const { h } = RegistryApp.runtime;
|
|
||||||
const store = RegistryApp.store;
|
|
||||||
|
|
||||||
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
|
||||||
|
|
||||||
RegistryApp.componentFns.HomeView = function HomeView() {
|
|
||||||
return h(
|
|
||||||
"div",
|
|
||||||
{ className: "content" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "card" },
|
|
||||||
h("h2", null, "Create Organization"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
null,
|
|
||||||
"Establish your Task Force, PMC, or Milsim unit with the Global Organization Network. Receive your official unit designator and TO&E authorization instantly.",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{ onClick: () => store.setView("create") },
|
|
||||||
"Register",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "card" },
|
|
||||||
h("h2", null, "Organization Portal"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
null,
|
|
||||||
"Access your unit dashboard to modify rosters, adjust active deployments, and submit after-action reports through the secure field uplink.",
|
|
||||||
),
|
|
||||||
h("button", { onClick: () => store.setView("login") }, "Login"),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
|
||||||
const { h } = RegistryApp.runtime;
|
|
||||||
const store = RegistryApp.store;
|
|
||||||
|
|
||||||
RegistryApp.components = RegistryApp.components || {};
|
|
||||||
|
|
||||||
RegistryApp.components.App = function App() {
|
|
||||||
const Navbar = RegistryApp.componentFns.Navbar;
|
|
||||||
const Header = RegistryApp.componentFns.Header;
|
|
||||||
const HomeView = RegistryApp.componentFns.HomeView;
|
|
||||||
const LoginForm = RegistryApp.componentFns.LoginForm;
|
|
||||||
const CreateOrgForm = RegistryApp.componentFns.CreateOrgForm;
|
|
||||||
const Footer = RegistryApp.componentFns.Footer;
|
|
||||||
const PortalApp =
|
|
||||||
window.OrgPortal && window.OrgPortal.components
|
|
||||||
? window.OrgPortal.components.App
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const view = store.getView();
|
|
||||||
|
|
||||||
if (view === "portal" && PortalApp) {
|
|
||||||
return h("div", null, Navbar(), PortalApp());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mainContent;
|
|
||||||
if (view === "home") {
|
|
||||||
mainContent = HomeView();
|
|
||||||
} else if (view === "login") {
|
|
||||||
mainContent = LoginForm();
|
|
||||||
} else if (view === "create") {
|
|
||||||
mainContent = CreateOrgForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
return h(
|
|
||||||
"main",
|
|
||||||
null,
|
|
||||||
Navbar(),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "container" },
|
|
||||||
Header({ title: "Global Organization Network" }),
|
|
||||||
mainContent,
|
|
||||||
),
|
|
||||||
Footer(),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
|
||||||
const { h } = RegistryApp.runtime;
|
|
||||||
const store = RegistryApp.store;
|
|
||||||
|
|
||||||
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
|
||||||
|
|
||||||
RegistryApp.componentFns.LoginForm = function LoginForm() {
|
|
||||||
const bridge = RegistryApp.bridge;
|
|
||||||
const isAuthenticating = store.getIsAuthenticating();
|
|
||||||
const loginError = store.getLoginError();
|
|
||||||
|
|
||||||
const handleLogin = () => {
|
|
||||||
const data = {
|
|
||||||
email: String(
|
|
||||||
document.getElementById("org-login-email")?.value || "",
|
|
||||||
),
|
|
||||||
password: String(
|
|
||||||
document.getElementById("org-login-password")?.value || "",
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!bridge) {
|
|
||||||
store.failLogin("Login bridge is not available.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bridge.requestLogin(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
return h(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
className: "card",
|
|
||||||
style: { maxWidth: "400px", margin: "0 auto" },
|
|
||||||
},
|
|
||||||
h("h2", null, "Organization Login"),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "app-form" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("label", null, "Email"),
|
|
||||||
h("input", {
|
|
||||||
id: "org-login-email",
|
|
||||||
type: "text",
|
|
||||||
placeholder: "admin@spearnet.mil",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("label", null, "Password"),
|
|
||||||
h("input", {
|
|
||||||
id: "org-login-password",
|
|
||||||
type: "password",
|
|
||||||
placeholder: "********",
|
|
||||||
disabled: isAuthenticating,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
loginError
|
|
||||||
? h(
|
|
||||||
"div",
|
|
||||||
{ className: "form-feedback is-error" },
|
|
||||||
loginError,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "form-actions" },
|
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
style: { width: "100%" },
|
|
||||||
onClick: handleLogin,
|
|
||||||
disabled: isAuthenticating,
|
|
||||||
},
|
|
||||||
isAuthenticating
|
|
||||||
? "Authenticating..."
|
|
||||||
: "Access Authenticator",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
className: "cancel-link",
|
|
||||||
onClick: () => store.setView("home"),
|
|
||||||
},
|
|
||||||
"Cancel / Return to Main",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
190
arma/client/addons/org/ui/_site/components/modal.js
Normal file
190
arma/client/addons/org/ui/_site/components/modal.js
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedUI = (window.SharedUI = window.SharedUI || {});
|
||||||
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const { h, ensureScopedStyle } = RegistryApp.runtime;
|
||||||
|
const scopeAttr = "data-ui-modal";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const modalCss = `
|
||||||
|
${scopeSelector} {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgb(15 23 42 / 0.38);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-card {
|
||||||
|
width: min(100%, 30rem);
|
||||||
|
margin-bottom: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-title {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
font-size: 1.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-close {
|
||||||
|
width: 2.25rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
padding: 0;
|
||||||
|
background: var(--bg-surface);
|
||||||
|
color: var(--text-main);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
box-shadow: none;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-close:hover {
|
||||||
|
background: var(--bg-surface-hover);
|
||||||
|
color: var(--text-main);
|
||||||
|
box-shadow: none;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-form label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-form input,
|
||||||
|
${scopeSelector} .app-modal-form select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--bg-app);
|
||||||
|
color: var(--text-main);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-form input:focus,
|
||||||
|
${scopeSelector} .app-modal-form select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 2px rgb(71 85 105 / 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-form input:disabled,
|
||||||
|
${scopeSelector} .app-modal-form select:disabled {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-actions button + button,
|
||||||
|
${scopeSelector} .app-modal-danger-actions button + button {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-danger {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #fff1f2;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-danger p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-danger-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .app-modal-head,
|
||||||
|
${scopeSelector} .app-modal-danger {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
SharedUI.componentFns = SharedUI.componentFns || {};
|
||||||
|
|
||||||
|
SharedUI.componentFns.Modal = function Modal({
|
||||||
|
title = "",
|
||||||
|
body = null,
|
||||||
|
onClose = null,
|
||||||
|
}) {
|
||||||
|
ensureScopedStyle("shared-modal", modalCss);
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className: "app-modal-backdrop",
|
||||||
|
[scopeAttr]: "",
|
||||||
|
onClick: (e) => {
|
||||||
|
if (e.target === e.currentTarget && onClose) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "card app-modal-card" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "app-modal-head" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h("h2", { className: "app-modal-title" }, title),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "app-modal-close",
|
||||||
|
onClick: onClose,
|
||||||
|
"aria-label": "Close dialog",
|
||||||
|
},
|
||||||
|
"x",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -1,79 +0,0 @@
|
|||||||
.app-navbar {
|
|
||||||
background: var(--bg-surface);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-navbar-inner {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
max-width: 1200px;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 1rem 2rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-navbar-brand {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-navbar-kicker {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-navbar-title {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--primary-hover);
|
|
||||||
letter-spacing: -0.025em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-navbar-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-navbar-view {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-close-btn {
|
|
||||||
background: transparent;
|
|
||||||
color: var(--text-muted);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--bg-surface-hover);
|
|
||||||
color: var(--primary-hover);
|
|
||||||
border-color: var(--primary);
|
|
||||||
transform: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
|
||||||
.app-navbar-inner {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-navbar-actions {
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,54 +1,113 @@
|
|||||||
(function () {
|
(function () {
|
||||||
|
const SharedUI = (window.SharedUI = window.SharedUI || {});
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
const { h } = RegistryApp.runtime;
|
const { h, ensureScopedStyle } = RegistryApp.runtime;
|
||||||
const store = RegistryApp.store;
|
const scopeAttr = "data-ui-navbar";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const navbarCss = `
|
||||||
|
${scopeSelector} {
|
||||||
|
background: var(--bg-surface);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
${scopeSelector} .app-navbar-inner {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 1200px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
function closeRegistry() {
|
${scopeSelector} .app-navbar-brand {
|
||||||
if (
|
display: flex;
|
||||||
typeof A3API !== "undefined" &&
|
flex-direction: column;
|
||||||
typeof A3API.SendAlert === "function"
|
gap: 0.125rem;
|
||||||
) {
|
}
|
||||||
A3API.SendAlert(
|
|
||||||
JSON.stringify({
|
|
||||||
event: "org::close",
|
|
||||||
data: {},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
store.setView("home");
|
${scopeSelector} .app-navbar-kicker {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-navbar-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-navbar-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-navbar-view {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-close-btn {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-muted);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-close-btn:hover {
|
||||||
|
background: var(--bg-surface-hover);
|
||||||
|
color: var(--primary-hover);
|
||||||
|
border-color: var(--primary);
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .app-navbar-inner {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
RegistryApp.componentFns.Navbar = function Navbar() {
|
${scopeSelector} .app-navbar-actions {
|
||||||
const view = store.getView();
|
align-items: flex-start;
|
||||||
const viewLabel =
|
}
|
||||||
view === "login"
|
}
|
||||||
? "Organization Login"
|
`;
|
||||||
: view === "create"
|
|
||||||
? "Organization Registration"
|
SharedUI.componentFns = SharedUI.componentFns || {};
|
||||||
: view === "portal"
|
|
||||||
? "Organization Portal"
|
SharedUI.componentFns.Navbar = function Navbar({
|
||||||
: "Entry Hub";
|
kicker = "ORBIS",
|
||||||
const actionLabel = view === "portal" ? "Sign Out" : "Close";
|
title = "",
|
||||||
|
viewLabel = "",
|
||||||
|
actionLabel = "",
|
||||||
|
onAction = null,
|
||||||
|
}) {
|
||||||
|
ensureScopedStyle("shared-navbar", navbarCss);
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
"nav",
|
"nav",
|
||||||
{ className: "app-navbar" },
|
{ className: "app-navbar", [scopeAttr]: "" },
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "app-navbar-inner" },
|
{ className: "app-navbar-inner" },
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "app-navbar-brand" },
|
{ className: "app-navbar-brand" },
|
||||||
h("span", { className: "app-navbar-kicker" }, "ORBIS"),
|
h("span", { className: "app-navbar-kicker" }, kicker),
|
||||||
h(
|
h("span", { className: "app-navbar-title" }, title),
|
||||||
"span",
|
|
||||||
{ className: "app-navbar-title" },
|
|
||||||
"Global Organization Network",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
@ -59,7 +118,7 @@
|
|||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
className: "app-close-btn",
|
className: "app-close-btn",
|
||||||
onClick: closeRegistry,
|
onClick: onAction,
|
||||||
},
|
},
|
||||||
actionLabel,
|
actionLabel,
|
||||||
),
|
),
|
||||||
|
|||||||
83
arma/client/addons/org/ui/_site/components/panelCard.js
Normal file
83
arma/client/addons/org/ui/_site/components/panelCard.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedUI = (window.SharedUI = window.SharedUI || {});
|
||||||
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const { h, ensureScopedStyle } = RegistryApp.runtime;
|
||||||
|
const scopeAttr = "data-ui-panel-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const panelCardCss = `
|
||||||
|
${scopeSelector} .org-panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-eyebrow {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-panel-title {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
font-size: 1.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-panel-subtitle {
|
||||||
|
margin: 0.35rem 0 0;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-panel-head {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
SharedUI.componentFns = SharedUI.componentFns || {};
|
||||||
|
|
||||||
|
SharedUI.componentFns.PanelCard = function PanelCard({
|
||||||
|
className = "",
|
||||||
|
eyebrow = "",
|
||||||
|
title = "",
|
||||||
|
subtitle = "",
|
||||||
|
headerExtras = null,
|
||||||
|
body = null,
|
||||||
|
rootProps = {},
|
||||||
|
}) {
|
||||||
|
const finalClassName = ["card org-panel", className]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ");
|
||||||
|
ensureScopedStyle("shared-panel-card", panelCardCss);
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"section",
|
||||||
|
{ className: finalClassName, [scopeAttr]: "", ...rootProps },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-panel-head" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
eyebrow
|
||||||
|
? h("div", { className: "org-eyebrow" }, eyebrow)
|
||||||
|
: null,
|
||||||
|
h("h2", { className: "org-panel-title" }, title),
|
||||||
|
subtitle
|
||||||
|
? h("p", { className: "org-panel-subtitle" }, subtitle)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
headerExtras,
|
||||||
|
),
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const { portalData } = OrgPortal.data;
|
||||||
|
const scopeAttr = "data-ui-activity-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const activityCardCss = `
|
||||||
|
${scopeSelector} .org-activity-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
gap: 0.85rem;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #94a3b8 #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-activity-row {
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-left: 3px solid #94a3b8;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-activity-row:nth-child(even) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);
|
||||||
|
border-color: rgb(148 163 184 / 0.45);
|
||||||
|
border-left-color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-activity-row p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-activity-time {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.ActivityCard = function ActivityCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
ensureScopedStyle("portal-activity-card", activityCardCss);
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-scroll-panel org-span-6",
|
||||||
|
title: "Command Feed",
|
||||||
|
subtitle: "Recent organization-level actions and updates.",
|
||||||
|
rootProps: { [scopeAttr]: "" },
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-activity-list" },
|
||||||
|
...portalData.activity.map((item) =>
|
||||||
|
h(
|
||||||
|
"article",
|
||||||
|
{ className: "org-activity-row" },
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "org-activity-time" },
|
||||||
|
item.time,
|
||||||
|
),
|
||||||
|
h("p", null, item.text),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const { portalData } = OrgPortal.data;
|
||||||
|
const actions = OrgPortal.actions;
|
||||||
|
const scopeAttr = "data-ui-assets-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const assetsCardCss = `
|
||||||
|
${scopeSelector} .org-simple-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
gap: 0.85rem;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #94a3b8 #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-row:nth-child(even) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);
|
||||||
|
border-color: rgb(148 163 184 / 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-name {
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-simple-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.AssetsCard = function AssetsCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
const SimpleStat = OrgPortal.componentFns.SimpleStat;
|
||||||
|
ensureScopedStyle("portal-assets-card", assetsCardCss);
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-scroll-panel org-span-7",
|
||||||
|
title: "Assets",
|
||||||
|
subtitle: "Inventory supplies and equipment with quantity totals.",
|
||||||
|
rootProps: { [scopeAttr]: "" },
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-simple-list" },
|
||||||
|
...portalData.assets.map((asset) =>
|
||||||
|
h(
|
||||||
|
"article",
|
||||||
|
{ className: "org-simple-row" },
|
||||||
|
h(
|
||||||
|
"strong",
|
||||||
|
{ className: "org-simple-name" },
|
||||||
|
asset.name,
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-simple-meta" },
|
||||||
|
SimpleStat(
|
||||||
|
"Type",
|
||||||
|
actions.formatAssetType(asset.type),
|
||||||
|
),
|
||||||
|
SimpleStat("Quantity", asset.quantity),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const permissions = OrgPortal.permissions;
|
||||||
|
const actions = OrgPortal.actions;
|
||||||
|
const scopeAttr = "data-ui-danger-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const dangerCardCss = `
|
||||||
|
${scopeSelector} {
|
||||||
|
border-color: #fecaca;
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #fff7f7 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-danger-copy {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-danger-copy strong,
|
||||||
|
${scopeSelector} .org-danger-copy p {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-danger-copy p {
|
||||||
|
margin: 0.4rem 0 0;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.DangerCard = function DangerCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
ensureScopedStyle("portal-danger-card", dangerCardCss);
|
||||||
|
|
||||||
|
if (!permissions.canDisbandOrg()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-span-12 org-danger-panel",
|
||||||
|
title: "Organization Controls",
|
||||||
|
subtitle:
|
||||||
|
"Leader-only actions for membership and permanent organization removal.",
|
||||||
|
rootProps: { [scopeAttr]: "" },
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-danger-copy" },
|
||||||
|
h("strong", null, "Disband organization"),
|
||||||
|
h(
|
||||||
|
"p",
|
||||||
|
null,
|
||||||
|
"This removes the organization and revokes access to the portal for all members.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-danger-btn",
|
||||||
|
onClick: () => actions.openModal("disband"),
|
||||||
|
},
|
||||||
|
"Disband Organization",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
101
arma/client/addons/org/ui/_site/components/portal/fleetCard.js
Normal file
101
arma/client/addons/org/ui/_site/components/portal/fleetCard.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const { portalData } = OrgPortal.data;
|
||||||
|
const actions = OrgPortal.actions;
|
||||||
|
const scopeAttr = "data-ui-fleet-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const fleetCardCss = `
|
||||||
|
${scopeSelector} .org-simple-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
gap: 0.85rem;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #94a3b8 #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} {
|
||||||
|
min-height: 32.5rem;
|
||||||
|
max-height: 32.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-row:nth-child(even) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);
|
||||||
|
border-color: rgb(148 163 184 / 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-name {
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-simple-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.FleetCard = function FleetCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
const SimpleStat = OrgPortal.componentFns.SimpleStat;
|
||||||
|
ensureScopedStyle("portal-fleet-card", fleetCardCss);
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-scroll-panel org-span-7",
|
||||||
|
title: "Fleet",
|
||||||
|
subtitle:
|
||||||
|
"Individual vehicles with type, status, and overall damage.",
|
||||||
|
rootProps: { [scopeAttr]: "" },
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-simple-list" },
|
||||||
|
...portalData.fleet.map((unit) =>
|
||||||
|
h(
|
||||||
|
"article",
|
||||||
|
{ className: "org-simple-row" },
|
||||||
|
h(
|
||||||
|
"strong",
|
||||||
|
{ className: "org-simple-name" },
|
||||||
|
unit.name,
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-simple-meta" },
|
||||||
|
SimpleStat(
|
||||||
|
"Type",
|
||||||
|
actions.formatVehicleType(unit.type),
|
||||||
|
),
|
||||||
|
SimpleStat("Status", unit.status),
|
||||||
|
SimpleStat("Damage", unit.damage),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
105
arma/client/addons/org/ui/_site/components/portal/futureCard.js
Normal file
105
arma/client/addons/org/ui/_site/components/portal/futureCard.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const { portalData } = OrgPortal.data;
|
||||||
|
const scopeAttr = "data-ui-future-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const futureCardCss = `
|
||||||
|
${scopeSelector} .org-roadmap-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #94a3b8 #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-roadmap-card {
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.7rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-roadmap-card:nth-child(4n + 2),
|
||||||
|
${scopeSelector} .org-roadmap-card:nth-child(4n + 3) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);
|
||||||
|
border-color: rgb(100 116 139 / 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-roadmap-card p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-list-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.2rem 0.55rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-roadmap-card:nth-child(4n + 2) .org-list-tag,
|
||||||
|
${scopeSelector} .org-roadmap-card:nth-child(4n + 3) .org-list-tag {
|
||||||
|
background: #cbd5e1;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-roadmap-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-roadmap-card:nth-child(4n + 3) {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-roadmap-card:nth-child(4n + 3) .org-list-tag {
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.FutureCard = function FutureCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
ensureScopedStyle("portal-future-card", futureCardCss);
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-scroll-panel org-span-6",
|
||||||
|
title: "Expansion Slots",
|
||||||
|
subtitle:
|
||||||
|
"Potential modules are tagged by status such as Planned, In Design, In Review, and Future Review.",
|
||||||
|
rootProps: { [scopeAttr]: "" },
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-roadmap-grid" },
|
||||||
|
...portalData.roadmap.map((item) =>
|
||||||
|
h(
|
||||||
|
"article",
|
||||||
|
{ className: "org-roadmap-card" },
|
||||||
|
h("span", { className: "org-list-tag" }, item.status),
|
||||||
|
h("strong", null, item.name),
|
||||||
|
h("p", null, item.detail),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -1,42 +1,83 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
const { h } = OrgPortal.runtime;
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
const store = OrgPortal.store;
|
const store = OrgPortal.store;
|
||||||
const permissions = OrgPortal.permissions;
|
const permissions = OrgPortal.permissions;
|
||||||
const actions = OrgPortal.actions;
|
const actions = OrgPortal.actions;
|
||||||
|
const scopeAttr = "data-ui-members-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const membersCardCss = `
|
||||||
|
${scopeSelector} .org-name-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
gap: 0.85rem;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #94a3b8 #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-row:nth-child(even) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);
|
||||||
|
border-color: rgb(148 163 184 / 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-row button {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-name-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-row button {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
OrgPortal.componentFns.MembersCard = function MembersCard() {
|
OrgPortal.componentFns.MembersCard = function MembersCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
const members = store.getMembers();
|
const members = store.getMembers();
|
||||||
const allowMemberManagement = permissions.canManageMembers();
|
const allowMemberManagement = permissions.canManageMembers();
|
||||||
|
ensureScopedStyle("portal-members-card", membersCardCss);
|
||||||
|
|
||||||
return h(
|
return PanelCard({
|
||||||
"section",
|
className: "org-scroll-panel org-span-5",
|
||||||
{ className: "card org-panel org-scroll-panel org-span-5" },
|
title: "Members",
|
||||||
h(
|
subtitle:
|
||||||
"div",
|
"Current roster listing. The organization owner cannot be removed.",
|
||||||
{ className: "org-panel-head" },
|
rootProps: { [scopeAttr]: "" },
|
||||||
h(
|
body: h(
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h2", { className: "org-panel-title" }, "Members"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
{ className: "org-panel-subtitle" },
|
|
||||||
"Current roster listing with member removal controls.",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-name-list" },
|
{ className: "org-name-list" },
|
||||||
...members.map((member) =>
|
...members.map((member) => {
|
||||||
h(
|
const canRemoveMember =
|
||||||
|
allowMemberManagement &&
|
||||||
|
!actions.isOwnerMember(member.name);
|
||||||
|
|
||||||
|
return h(
|
||||||
"article",
|
"article",
|
||||||
{ className: "org-name-row" },
|
{ className: "org-name-row" },
|
||||||
h("strong", null, member.name),
|
h("strong", null, member.name),
|
||||||
allowMemberManagement
|
canRemoveMember
|
||||||
? h(
|
? h(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
@ -67,9 +108,9 @@
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
);
|
||||||
),
|
}),
|
||||||
),
|
),
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const scopeAttr = "data-ui-metric-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const metricCardCss = `
|
||||||
|
${scopeSelector} {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.45rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector}:nth-child(4n + 2),
|
||||||
|
${scopeSelector}:nth-child(4n + 3) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(226 232 240) 100%);
|
||||||
|
border-color: rgb(100 116 139 / 0.35);
|
||||||
|
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-metric-label {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-metric-value {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector}:nth-child(4n + 2) .org-metric-value,
|
||||||
|
${scopeSelector}:nth-child(4n + 3) .org-metric-value {
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-metric-note {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector}:nth-child(4n + 3) {
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
||||||
|
border-color: var(--border);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector}:nth-child(4n + 3) .org-metric-value {
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.MetricCard = function MetricCard(
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
note,
|
||||||
|
) {
|
||||||
|
ensureScopedStyle("portal-metric-card", metricCardCss);
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-metric-card", [scopeAttr]: "" },
|
||||||
|
h("span", { className: "org-metric-label" }, label),
|
||||||
|
h("strong", { className: "org-metric-value" }, value),
|
||||||
|
h("span", { className: "org-metric-note" }, note),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -8,6 +8,7 @@
|
|||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
OrgPortal.componentFns.ModalLayer = function ModalLayer() {
|
OrgPortal.componentFns.ModalLayer = function ModalLayer() {
|
||||||
|
const Modal = window.SharedUI.componentFns.Modal;
|
||||||
const modal = store.getModal();
|
const modal = store.getModal();
|
||||||
if (!modal) {
|
if (!modal) {
|
||||||
return null;
|
return null;
|
||||||
@ -24,7 +25,7 @@
|
|||||||
title = "Run Payroll";
|
title = "Run Payroll";
|
||||||
body = h(
|
body = h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-modal-form" },
|
{ className: "app-modal-form" },
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
null,
|
null,
|
||||||
@ -39,7 +40,7 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-modal-actions" },
|
{ className: "app-modal-actions" },
|
||||||
h(
|
h(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
@ -75,7 +76,7 @@
|
|||||||
title = "Send Funds";
|
title = "Send Funds";
|
||||||
body = h(
|
body = h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-modal-form" },
|
{ className: "app-modal-form" },
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
null,
|
null,
|
||||||
@ -104,7 +105,7 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-modal-actions" },
|
{ className: "app-modal-actions" },
|
||||||
h(
|
h(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
@ -146,7 +147,7 @@
|
|||||||
title = "Assign Credit Line";
|
title = "Assign Credit Line";
|
||||||
body = h(
|
body = h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-modal-form" },
|
{ className: "app-modal-form" },
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
null,
|
null,
|
||||||
@ -172,7 +173,7 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-modal-actions" },
|
{ className: "app-modal-actions" },
|
||||||
h(
|
h(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
@ -214,7 +215,7 @@
|
|||||||
title = "Disband Organization";
|
title = "Disband Organization";
|
||||||
body = h(
|
body = h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-danger-confirm" },
|
{ className: "app-modal-danger" },
|
||||||
h(
|
h(
|
||||||
"p",
|
"p",
|
||||||
null,
|
null,
|
||||||
@ -224,7 +225,7 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-danger-actions" },
|
{ className: "app-modal-danger-actions" },
|
||||||
h(
|
h(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
@ -247,40 +248,10 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return h(
|
return Modal({
|
||||||
"div",
|
title,
|
||||||
{
|
body,
|
||||||
className: "org-modal-backdrop",
|
onClose: () => actions.closeModal(),
|
||||||
onClick: (e) => {
|
});
|
||||||
if (e.target === e.currentTarget) {
|
|
||||||
actions.closeModal();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "card org-modal-card" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-modal-head" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h2", { className: "org-panel-title" }, title),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
className: "org-modal-close",
|
|
||||||
onClick: () => actions.closeModal(),
|
|
||||||
"aria-label": "Close dialog",
|
|
||||||
},
|
|
||||||
"x",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
@ -1,35 +1,90 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
const { h } = OrgPortal.runtime;
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
const { portalData } = OrgPortal.data;
|
const { portalData } = OrgPortal.data;
|
||||||
const store = OrgPortal.store;
|
const store = OrgPortal.store;
|
||||||
const actions = OrgPortal.actions;
|
const actions = OrgPortal.actions;
|
||||||
|
const scopeAttr = "data-ui-overview-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const overviewCardCss = `
|
||||||
|
${scopeSelector} .org-hero-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.3fr 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-summary {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-meta-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-meta-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-meta-item:nth-child(even) {
|
||||||
|
background: linear-gradient(180deg, rgb(241 245 249) 0%, rgb(226 232 240) 100%);
|
||||||
|
border-color: rgb(148 163 184 / 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-meta-label {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-meta-value {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-metric-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-hero-grid,
|
||||||
|
${scopeSelector} .org-meta-row,
|
||||||
|
${scopeSelector} .org-metric-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
OrgPortal.componentFns.OverviewCard = function OverviewCard() {
|
OrgPortal.componentFns.OverviewCard = function OverviewCard() {
|
||||||
const MetricCard = OrgPortal.componentFns.MetricCard;
|
const MetricCard = OrgPortal.componentFns.MetricCard;
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
const readiness = actions.getAssetReadiness();
|
const readiness = actions.getAssetReadiness();
|
||||||
const headquarters = portalData.org.headquarters || "ArmA Verse";
|
const headquarters = portalData.org.headquarters || "ArmA Verse";
|
||||||
|
ensureScopedStyle("portal-overview-card", overviewCardCss);
|
||||||
|
|
||||||
return h(
|
return PanelCard({
|
||||||
"section",
|
className: "org-span-12",
|
||||||
{ className: "card org-panel org-span-12" },
|
eyebrow: portalData.org.tag,
|
||||||
h(
|
title: "Organization Overview",
|
||||||
"div",
|
rootProps: { [scopeAttr]: "" },
|
||||||
{ className: "org-panel-head" },
|
body: h(
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("div", { className: "org-eyebrow" }, portalData.org.tag),
|
|
||||||
h(
|
|
||||||
"h2",
|
|
||||||
{ className: "org-panel-title" },
|
|
||||||
"Organization Overview",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-hero-grid" },
|
{ className: "org-hero-grid" },
|
||||||
h(
|
h(
|
||||||
@ -115,6 +170,6 @@
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const scopeAttr = "data-ui-simple-stat";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const simpleStatCss = `
|
||||||
|
${scopeSelector} {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.2rem;
|
||||||
|
min-width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-label {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-value {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.SimpleStat = function SimpleStat(label, value) {
|
||||||
|
ensureScopedStyle("portal-simple-stat", simpleStatCss);
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-simple-stat", [scopeAttr]: "" },
|
||||||
|
h("span", { className: "org-simple-label" }, label),
|
||||||
|
h("strong", { className: "org-simple-value" }, value),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -0,0 +1,430 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle, createSignal } = OrgPortal.runtime;
|
||||||
|
const { portalData } = OrgPortal.data;
|
||||||
|
const store = OrgPortal.store;
|
||||||
|
const permissions = OrgPortal.permissions;
|
||||||
|
const actions = OrgPortal.actions;
|
||||||
|
const scopeAttr = "data-ui-treasury-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const [getTreasuryTab, setTreasuryTab] = createSignal("overview");
|
||||||
|
const [getTreasuryMenuOpen, setTreasuryMenuOpen] = createSignal(false);
|
||||||
|
const treasuryCardCss = `
|
||||||
|
${scopeSelector} .org-treasury-menu {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-btn {
|
||||||
|
width: 2.75rem;
|
||||||
|
height: 2.75rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: #f8fafc;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-btn:hover {
|
||||||
|
color: var(--primary-hover);
|
||||||
|
border-color: rgb(148 163 184 / 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-btn svg {
|
||||||
|
width: 1.1rem;
|
||||||
|
height: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 0.6rem);
|
||||||
|
right: 0;
|
||||||
|
min-width: 10.5rem;
|
||||||
|
padding: 0.45rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 12px 28px rgb(15 23 42 / 0.12);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-option + .org-menu-option {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-option {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-main);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-option:hover {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: rgb(148 163 184 / 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-option.is-active {
|
||||||
|
background: rgb(226 232 240 / 0.7);
|
||||||
|
color: var(--primary-hover);
|
||||||
|
border-color: rgb(148 163 184 / 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-finance-meta {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-finance-meta > div {
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-meta-label {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-action-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-action-grid button + button {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-action-grid button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-access-note {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-summary {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-summary strong {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-summary span:last-child {
|
||||||
|
font-size: 0.92rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-lines-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-line-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-line-row:nth-child(even) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);
|
||||||
|
border-color: rgb(148 163 184 / 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-line-member {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-line-label {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-line-empty {
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-finance-meta {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-line-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.TreasuryCard = function TreasuryCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
const creditLines = store.getCreditLines();
|
||||||
|
const allowTreasuryActions = permissions.canManageTreasury();
|
||||||
|
const activeTab = getTreasuryTab();
|
||||||
|
const isMenuOpen = getTreasuryMenuOpen();
|
||||||
|
const activeCreditLabel =
|
||||||
|
creditLines.length === 1
|
||||||
|
? "1 active credit line"
|
||||||
|
: `${creditLines.length} active credit lines`;
|
||||||
|
ensureScopedStyle("portal-treasury-card", treasuryCardCss);
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-span-5",
|
||||||
|
title: "Treasury",
|
||||||
|
subtitle: "Organization funds, reputation, and member payouts.",
|
||||||
|
headerExtras: h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-treasury-menu" },
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-menu-btn",
|
||||||
|
title: "Treasury views",
|
||||||
|
"aria-label": "Treasury views",
|
||||||
|
onClick: () => setTreasuryMenuOpen((open) => !open),
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "currentColor",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
"aria-hidden": "true",
|
||||||
|
},
|
||||||
|
h("line", { x1: "4", y1: "7", x2: "20", y2: "7" }),
|
||||||
|
h("line", { x1: "4", y1: "12", x2: "20", y2: "12" }),
|
||||||
|
h("line", { x1: "4", y1: "17", x2: "20", y2: "17" }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
isMenuOpen
|
||||||
|
? h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-menu-dropdown" },
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className:
|
||||||
|
activeTab === "overview"
|
||||||
|
? "org-menu-option is-active"
|
||||||
|
: "org-menu-option",
|
||||||
|
onClick: () => {
|
||||||
|
setTreasuryTab("overview");
|
||||||
|
setTreasuryMenuOpen(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Overview",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className:
|
||||||
|
activeTab === "credit"
|
||||||
|
? "org-menu-option is-active"
|
||||||
|
: "org-menu-option",
|
||||||
|
onClick: () => {
|
||||||
|
setTreasuryTab("credit");
|
||||||
|
setTreasuryMenuOpen(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Credit Lines",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
rootProps: { [scopeAttr]: "" },
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
activeTab === "credit"
|
||||||
|
? creditLines.length > 0
|
||||||
|
? h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-credit-lines-list" },
|
||||||
|
...creditLines.map((line) =>
|
||||||
|
h(
|
||||||
|
"article",
|
||||||
|
{ className: "org-credit-line-row" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-credit-line-member",
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-credit-line-label",
|
||||||
|
},
|
||||||
|
"Member",
|
||||||
|
),
|
||||||
|
h("strong", null, line.member),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-credit-line-member",
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-credit-line-label",
|
||||||
|
},
|
||||||
|
"Amount",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"strong",
|
||||||
|
null,
|
||||||
|
actions.formatCurrency(
|
||||||
|
line.amount,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-credit-line-empty" },
|
||||||
|
"No active credit lines.",
|
||||||
|
)
|
||||||
|
: h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-finance-meta" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "org-meta-label" },
|
||||||
|
"Funds",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"strong",
|
||||||
|
null,
|
||||||
|
actions.formatCurrency(store.getFunds()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "org-meta-label" },
|
||||||
|
"Reputation",
|
||||||
|
),
|
||||||
|
h("strong", null, `${portalData.reputation}`),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
allowTreasuryActions
|
||||||
|
? h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-action-grid" },
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
onClick: () =>
|
||||||
|
actions.openModal("payroll"),
|
||||||
|
},
|
||||||
|
"Run Payroll",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-secondary-btn",
|
||||||
|
onClick: () =>
|
||||||
|
actions.openModal("transfer"),
|
||||||
|
},
|
||||||
|
"Send Funds",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-secondary-btn",
|
||||||
|
onClick: () =>
|
||||||
|
actions.openModal("credit"),
|
||||||
|
},
|
||||||
|
"Credit Line",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: h(
|
||||||
|
"p",
|
||||||
|
{ className: "org-access-note" },
|
||||||
|
"Only the organization leader or CEO can manage treasury actions.",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-credit-summary" },
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "org-meta-label" },
|
||||||
|
"Credit Line Status",
|
||||||
|
),
|
||||||
|
h("strong", null, activeCreditLabel),
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
null,
|
||||||
|
creditLines.length > 0
|
||||||
|
? "Open the Credit Lines tab to review assigned members and amounts."
|
||||||
|
: "Assign a credit line to create the first approved member limit.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -6,55 +6,41 @@
|
|||||||
<title>ORBIS - Global Organization Network</title>
|
<title>ORBIS - Global Organization Network</title>
|
||||||
<script>
|
<script>
|
||||||
const addonRoot = "forge\\forge_client\\addons\\org\\ui\\_site\\";
|
const addonRoot = "forge\\forge_client\\addons\\org\\ui\\_site\\";
|
||||||
const styleFiles = [
|
const styleFiles = ["base.css", "controls.css", "hero.css"];
|
||||||
"base.css",
|
|
||||||
"components\\navbar.css",
|
|
||||||
"components\\homeView.css",
|
|
||||||
"components\\forms.css",
|
|
||||||
"portal\\components\\controls.css",
|
|
||||||
"portal\\components\\layout.css",
|
|
||||||
"portal\\components\\portalHeader.css",
|
|
||||||
"portal\\components\\overviewCard.css",
|
|
||||||
"portal\\components\\metricCard.css",
|
|
||||||
"portal\\components\\simpleList.css",
|
|
||||||
"portal\\components\\simpleStat.css",
|
|
||||||
"portal\\components\\treasuryCard.css",
|
|
||||||
"portal\\components\\activityCard.css",
|
|
||||||
"portal\\components\\futureCard.css",
|
|
||||||
"portal\\components\\dangerCard.css",
|
|
||||||
"portal\\components\\modalLayer.css",
|
|
||||||
];
|
|
||||||
const scriptFiles = [
|
const scriptFiles = [
|
||||||
"runtime.js",
|
"runtime.js",
|
||||||
|
"logic\\registryStore.js",
|
||||||
|
"logic\\portalStore.js",
|
||||||
|
"logic\\portalPermissions.js",
|
||||||
|
"logic\\portalActions.js",
|
||||||
"state.js",
|
"state.js",
|
||||||
"bridge.js",
|
"bridge.js",
|
||||||
"portal\\runtime.js",
|
|
||||||
"portal\\data.js",
|
"portal\\data.js",
|
||||||
"portal\\store.js",
|
"portal\\store.js",
|
||||||
"portal\\permissions.js",
|
"portal\\permissions.js",
|
||||||
"portal\\actions.js",
|
"portal\\actions.js",
|
||||||
"portal\\components\\metricCard.js",
|
|
||||||
"portal\\components\\simpleStat.js",
|
|
||||||
"portal\\components\\portalHeader.js",
|
|
||||||
"portal\\components\\overviewCard.js",
|
|
||||||
"portal\\components\\fleetCard.js",
|
|
||||||
"portal\\components\\treasuryCard.js",
|
|
||||||
"portal\\components\\assetsCard.js",
|
|
||||||
"portal\\components\\membersCard.js",
|
|
||||||
"portal\\components\\activityCard.js",
|
|
||||||
"portal\\components\\futureCard.js",
|
|
||||||
"portal\\components\\dangerCard.js",
|
|
||||||
"portal\\components\\modalLayer.js",
|
|
||||||
"portal\\components\\disbandedView.js",
|
|
||||||
"portal\\components\\footer.js",
|
|
||||||
"portal\\components\\index.js",
|
|
||||||
"components\\navbar.js",
|
"components\\navbar.js",
|
||||||
"components\\header.js",
|
"components\\header.js",
|
||||||
"components\\loginForm.js",
|
"components\\hero.js",
|
||||||
"components\\createOrgForm.js",
|
|
||||||
"components\\homeView.js",
|
|
||||||
"components\\footer.js",
|
"components\\footer.js",
|
||||||
"components\\index.js",
|
"components\\modal.js",
|
||||||
|
"components\\panelCard.js",
|
||||||
|
"components\\portal\\metricCard.js",
|
||||||
|
"components\\portal\\simpleStat.js",
|
||||||
|
"components\\portal\\overviewCard.js",
|
||||||
|
"components\\portal\\fleetCard.js",
|
||||||
|
"components\\portal\\treasuryCard.js",
|
||||||
|
"components\\portal\\assetsCard.js",
|
||||||
|
"components\\portal\\membersCard.js",
|
||||||
|
"components\\portal\\activityCard.js",
|
||||||
|
"components\\portal\\futureCard.js",
|
||||||
|
"components\\portal\\dangerCard.js",
|
||||||
|
"components\\portal\\modalLayer.js",
|
||||||
|
"views\\DisbandedView.js",
|
||||||
|
"views\\PortalView.js",
|
||||||
|
"views\\RegistrationView.js",
|
||||||
|
"views\\HomeView.js",
|
||||||
|
"components\\AppShell.js",
|
||||||
"bootstrap.js",
|
"bootstrap.js",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
318
arma/client/addons/org/ui/_site/logic/portalActions.js
Normal file
318
arma/client/addons/org/ui/_site/logic/portalActions.js
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedLogic = (window.SharedLogic = window.SharedLogic || {});
|
||||||
|
|
||||||
|
SharedLogic.createPortalActions = function createPortalActions({
|
||||||
|
portalData,
|
||||||
|
store,
|
||||||
|
permissions,
|
||||||
|
registryStore,
|
||||||
|
}) {
|
||||||
|
class OrgPortalActions {
|
||||||
|
constructor() {
|
||||||
|
this.treasuryNoticeTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatCurrency(value) {
|
||||||
|
return "$" + value.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
formatVehicleType(type) {
|
||||||
|
if (!type) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.charAt(0).toUpperCase() + type.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatAssetType(type) {
|
||||||
|
if (!type) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.charAt(0).toUpperCase() + type.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDisplayName(value) {
|
||||||
|
if (!value) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value)
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/)
|
||||||
|
.map((part) => {
|
||||||
|
if (!part) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
part.charAt(0).toUpperCase() +
|
||||||
|
part.slice(1).toLowerCase()
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
getAssetReadiness() {
|
||||||
|
if (portalData.fleet.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = portalData.fleet.reduce(
|
||||||
|
(sum, unit) => sum + (100 - parseInt(unit.damage, 10)),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
return Math.round(total / portalData.fleet.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
showTreasuryNotice(type, text) {
|
||||||
|
store.setTreasuryNotice({ type, text });
|
||||||
|
|
||||||
|
if (this.treasuryNoticeTimer) {
|
||||||
|
clearTimeout(this.treasuryNoticeTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.treasuryNoticeTimer = setTimeout(() => {
|
||||||
|
store.setTreasuryNotice({ type: "", text: "" });
|
||||||
|
this.treasuryNoticeTimer = null;
|
||||||
|
}, 3500);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAmount(value) {
|
||||||
|
const amount = Number(value);
|
||||||
|
return Number.isFinite(amount) ? Math.round(amount) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputValue(id) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
return el ? el.value : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
isOwnerMember(memberName) {
|
||||||
|
return (
|
||||||
|
String(memberName || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase() ===
|
||||||
|
String(portalData.org.owner || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
closePortal() {
|
||||||
|
if (
|
||||||
|
typeof A3API !== "undefined" &&
|
||||||
|
typeof A3API.SendAlert === "function"
|
||||||
|
) {
|
||||||
|
A3API.SendAlert(
|
||||||
|
JSON.stringify({
|
||||||
|
event: "org::close",
|
||||||
|
data: {},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registryStore) {
|
||||||
|
registryStore.setView("home");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openModal(type) {
|
||||||
|
if (
|
||||||
|
(type === "payroll" ||
|
||||||
|
type === "transfer" ||
|
||||||
|
type === "credit") &&
|
||||||
|
!permissions.canManageTreasury()
|
||||||
|
) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Only the organization leader or CEO can manage treasury actions.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "disband" && !permissions.canDisbandOrg()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setModal({ type });
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal() {
|
||||||
|
store.setModal(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMember(memberName) {
|
||||||
|
if (!permissions.canManageMembers()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isOwnerMember(memberName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setMembers((currentMembers) =>
|
||||||
|
currentMembers.filter(
|
||||||
|
(member) => member.name !== memberName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
store.setCreditLines((currentLines) =>
|
||||||
|
currentLines.filter((line) => line.member !== memberName),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
disbandOrganization() {
|
||||||
|
if (!permissions.canDisbandOrg()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setOrgDisbanded(true);
|
||||||
|
this.closeModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
runPayroll(amountPerMember) {
|
||||||
|
if (!permissions.canManageTreasury()) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Only the organization leader or CEO can manage treasury actions.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const members = store.getMembers();
|
||||||
|
const funds = store.getFunds();
|
||||||
|
|
||||||
|
if (members.length === 0) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"No members available for payroll.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amountPerMember <= 0) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Enter a valid payroll amount.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = amountPerMember * members.length;
|
||||||
|
if (total > funds) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Insufficient org funds for payroll.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setFunds(funds - total);
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"success",
|
||||||
|
`Payroll sent to ${members.length} members for ${this.formatCurrency(total)}.`,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFundsToMember(memberName, amount) {
|
||||||
|
if (!permissions.canManageTreasury()) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Only the organization leader or CEO can manage treasury actions.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const funds = store.getFunds();
|
||||||
|
|
||||||
|
if (!memberName) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Select a member to receive funds.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Enter a valid transfer amount.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount > funds) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Insufficient org funds for this transfer.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setFunds(funds - amount);
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"success",
|
||||||
|
`${this.formatCurrency(amount)} sent to ${memberName}.`,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
grantCreditLine(memberName, amount) {
|
||||||
|
if (!permissions.canManageTreasury()) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Only the organization leader or CEO can manage treasury actions.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!memberName) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Select a member for the credit line.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Enter a valid credit line amount.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setCreditLines((currentLines) => {
|
||||||
|
const existingIndex = currentLines.findIndex(
|
||||||
|
(line) => line.member === memberName,
|
||||||
|
);
|
||||||
|
if (existingIndex === -1) {
|
||||||
|
return [
|
||||||
|
...currentLines,
|
||||||
|
{ member: memberName, amount },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedLines = [...currentLines];
|
||||||
|
updatedLines[existingIndex] = {
|
||||||
|
member: memberName,
|
||||||
|
amount,
|
||||||
|
};
|
||||||
|
return updatedLines;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"success",
|
||||||
|
`Credit line of ${this.formatCurrency(amount)} assigned to ${memberName}.`,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OrgPortalActions();
|
||||||
|
};
|
||||||
|
})();
|
||||||
75
arma/client/addons/org/ui/_site/logic/portalPermissions.js
Normal file
75
arma/client/addons/org/ui/_site/logic/portalPermissions.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedLogic = (window.SharedLogic = window.SharedLogic || {});
|
||||||
|
|
||||||
|
SharedLogic.createPortalPermissions = function createPortalPermissions({
|
||||||
|
portalData,
|
||||||
|
session,
|
||||||
|
}) {
|
||||||
|
class OrgPortalPermissions {
|
||||||
|
getNormalizedRole() {
|
||||||
|
return String(session.role || "")
|
||||||
|
.trim()
|
||||||
|
.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
isDefaultOrg() {
|
||||||
|
return (
|
||||||
|
portalData.org.isDefault === true ||
|
||||||
|
String(portalData.org.tag || "")
|
||||||
|
.trim()
|
||||||
|
.toUpperCase() === "DEFAULT"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isOrgOwner() {
|
||||||
|
const ownerUid = String(
|
||||||
|
portalData.org.ownerUid || portalData.org.owner || "",
|
||||||
|
)
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const actorUid = String(session.actorUid || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
if (ownerUid && actorUid) {
|
||||||
|
return actorUid === ownerUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
String(session.actorName || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase() ===
|
||||||
|
String(portalData.org.owner || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSessionCeo() {
|
||||||
|
return session.ceo === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isOrgLeaderOrCeo() {
|
||||||
|
return (
|
||||||
|
this.isOrgOwner() ||
|
||||||
|
this.getNormalizedRole() === "LEADER" ||
|
||||||
|
(this.isDefaultOrg() && this.isSessionCeo())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
canManageMembers() {
|
||||||
|
return this.isOrgLeaderOrCeo();
|
||||||
|
}
|
||||||
|
|
||||||
|
canManageTreasury() {
|
||||||
|
return this.isOrgLeaderOrCeo();
|
||||||
|
}
|
||||||
|
|
||||||
|
canDisbandOrg() {
|
||||||
|
return this.isOrgLeaderOrCeo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OrgPortalPermissions();
|
||||||
|
};
|
||||||
|
})();
|
||||||
38
arma/client/addons/org/ui/_site/logic/portalStore.js
Normal file
38
arma/client/addons/org/ui/_site/logic/portalStore.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedLogic = (window.SharedLogic = window.SharedLogic || {});
|
||||||
|
|
||||||
|
SharedLogic.createPortalStore = function createPortalStore({
|
||||||
|
createSignal,
|
||||||
|
portalData,
|
||||||
|
}) {
|
||||||
|
class OrgPortalStore {
|
||||||
|
constructor() {
|
||||||
|
[this.getFunds, this.setFunds] = createSignal(portalData.funds);
|
||||||
|
[this.getMembers, this.setMembers] = createSignal([
|
||||||
|
...portalData.members,
|
||||||
|
]);
|
||||||
|
[this.getCreditLines, this.setCreditLines] = createSignal([]);
|
||||||
|
[this.getTreasuryNotice, this.setTreasuryNotice] = createSignal(
|
||||||
|
{
|
||||||
|
type: "",
|
||||||
|
text: "",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
[this.getModal, this.setModal] = createSignal(null);
|
||||||
|
[this.getOrgDisbanded, this.setOrgDisbanded] =
|
||||||
|
createSignal(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
hydrateFromPayload(payload) {
|
||||||
|
this.setFunds(payload.portalData.funds || 0);
|
||||||
|
this.setMembers([...(payload.portalData.members || [])]);
|
||||||
|
this.setCreditLines([]);
|
||||||
|
this.setTreasuryNotice({ type: "", text: "" });
|
||||||
|
this.setModal(null);
|
||||||
|
this.setOrgDisbanded(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OrgPortalStore();
|
||||||
|
};
|
||||||
|
})();
|
||||||
69
arma/client/addons/org/ui/_site/logic/registryStore.js
Normal file
69
arma/client/addons/org/ui/_site/logic/registryStore.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedLogic = (window.SharedLogic = window.SharedLogic || {});
|
||||||
|
|
||||||
|
SharedLogic.createRegistryStore = function createRegistryStore({
|
||||||
|
createSignal,
|
||||||
|
onHydratePortal,
|
||||||
|
}) {
|
||||||
|
class RegistryStore {
|
||||||
|
constructor() {
|
||||||
|
[this.getView, this.setView] = createSignal("home");
|
||||||
|
[this.getIsAuthenticating, this.setIsAuthenticating] =
|
||||||
|
createSignal(false);
|
||||||
|
[this.getLoginError, this.setLoginError] = createSignal("");
|
||||||
|
[this.getIsCreating, this.setIsCreating] = createSignal(false);
|
||||||
|
[this.getCreateError, this.setCreateError] = createSignal("");
|
||||||
|
}
|
||||||
|
|
||||||
|
startLogin() {
|
||||||
|
this.setLoginError("");
|
||||||
|
this.setIsAuthenticating(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
startCreate() {
|
||||||
|
this.setCreateError("");
|
||||||
|
this.setIsCreating(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
failLogin(message) {
|
||||||
|
this.setIsAuthenticating(false);
|
||||||
|
this.setLoginError(message || "Authentication failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
failCreate(message) {
|
||||||
|
this.setIsCreating(false);
|
||||||
|
this.setCreateError(
|
||||||
|
message || "Organization registration failed.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
hydratePortal(payload) {
|
||||||
|
return Boolean(onHydratePortal && onHydratePortal(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
completeLogin(payload) {
|
||||||
|
if (!this.hydratePortal(payload)) {
|
||||||
|
this.failLogin("Login response was missing portal data.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setLoginError("");
|
||||||
|
this.setIsAuthenticating(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
completeCreate(payload) {
|
||||||
|
if (!this.hydratePortal(payload)) {
|
||||||
|
this.failCreate(
|
||||||
|
"Organization registration response was missing portal data.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setCreateError("");
|
||||||
|
this.setIsCreating(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RegistryStore();
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -4,289 +4,12 @@
|
|||||||
const store = OrgPortal.store;
|
const store = OrgPortal.store;
|
||||||
const permissions = OrgPortal.permissions;
|
const permissions = OrgPortal.permissions;
|
||||||
const registryStore = window.RegistryApp.store;
|
const registryStore = window.RegistryApp.store;
|
||||||
|
const SharedLogic = (window.SharedLogic = window.SharedLogic || {});
|
||||||
|
|
||||||
class OrgPortalActions {
|
OrgPortal.actions = SharedLogic.createPortalActions({
|
||||||
constructor() {
|
portalData,
|
||||||
this.treasuryNoticeTimer = null;
|
store,
|
||||||
}
|
permissions,
|
||||||
|
registryStore,
|
||||||
formatCurrency(value) {
|
});
|
||||||
return "$" + value.toLocaleString();
|
|
||||||
}
|
|
||||||
|
|
||||||
formatVehicleType(type) {
|
|
||||||
if (!type) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return type.charAt(0).toUpperCase() + type.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
formatAssetType(type) {
|
|
||||||
if (!type) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return type.charAt(0).toUpperCase() + type.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
formatDisplayName(value) {
|
|
||||||
if (!value) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(value)
|
|
||||||
.trim()
|
|
||||||
.split(/\s+/)
|
|
||||||
.map((part) => {
|
|
||||||
if (!part) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
part.charAt(0).toUpperCase() +
|
|
||||||
part.slice(1).toLowerCase()
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
getAssetReadiness() {
|
|
||||||
if (portalData.fleet.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const total = portalData.fleet.reduce(
|
|
||||||
(sum, unit) => sum + (100 - parseInt(unit.damage, 10)),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
return Math.round(total / portalData.fleet.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
showTreasuryNotice(type, text) {
|
|
||||||
store.setTreasuryNotice({ type, text });
|
|
||||||
|
|
||||||
if (this.treasuryNoticeTimer) {
|
|
||||||
clearTimeout(this.treasuryNoticeTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.treasuryNoticeTimer = setTimeout(() => {
|
|
||||||
store.setTreasuryNotice({ type: "", text: "" });
|
|
||||||
this.treasuryNoticeTimer = null;
|
|
||||||
}, 3500);
|
|
||||||
}
|
|
||||||
|
|
||||||
parseAmount(value) {
|
|
||||||
const amount = Number(value);
|
|
||||||
return Number.isFinite(amount) ? Math.round(amount) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
getInputValue(id) {
|
|
||||||
const el = document.getElementById(id);
|
|
||||||
return el ? el.value : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
closePortal() {
|
|
||||||
if (
|
|
||||||
typeof A3API !== "undefined" &&
|
|
||||||
typeof A3API.SendAlert === "function"
|
|
||||||
) {
|
|
||||||
A3API.SendAlert(
|
|
||||||
JSON.stringify({
|
|
||||||
event: "org::close",
|
|
||||||
data: {},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (registryStore) {
|
|
||||||
registryStore.setView("home");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openModal(type) {
|
|
||||||
if (
|
|
||||||
(type === "payroll" ||
|
|
||||||
type === "transfer" ||
|
|
||||||
type === "credit") &&
|
|
||||||
!permissions.canManageTreasury()
|
|
||||||
) {
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"error",
|
|
||||||
"Only the organization leader or CEO can manage treasury actions.",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "disband" && !permissions.canDisbandOrg()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
store.setModal({ type });
|
|
||||||
}
|
|
||||||
|
|
||||||
closeModal() {
|
|
||||||
store.setModal(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMember(memberName) {
|
|
||||||
if (!permissions.canManageMembers()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
store.setMembers((currentMembers) =>
|
|
||||||
currentMembers.filter((member) => member.name !== memberName),
|
|
||||||
);
|
|
||||||
store.setCreditLines((currentLines) =>
|
|
||||||
currentLines.filter((line) => line.member !== memberName),
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
disbandOrganization() {
|
|
||||||
if (!permissions.canDisbandOrg()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
store.setOrgDisbanded(true);
|
|
||||||
this.closeModal();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
runPayroll(amountPerMember) {
|
|
||||||
if (!permissions.canManageTreasury()) {
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"error",
|
|
||||||
"Only the organization leader or CEO can manage treasury actions.",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const members = store.getMembers();
|
|
||||||
const funds = store.getFunds();
|
|
||||||
|
|
||||||
if (members.length === 0) {
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"error",
|
|
||||||
"No members available for payroll.",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amountPerMember <= 0) {
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"error",
|
|
||||||
"Enter a valid payroll amount.",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const total = amountPerMember * members.length;
|
|
||||||
if (total > funds) {
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"error",
|
|
||||||
"Insufficient org funds for payroll.",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
store.setFunds(funds - total);
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"success",
|
|
||||||
`Payroll sent to ${members.length} members for ${this.formatCurrency(total)}.`,
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendFundsToMember(memberName, amount) {
|
|
||||||
if (!permissions.canManageTreasury()) {
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"error",
|
|
||||||
"Only the organization leader or CEO can manage treasury actions.",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const funds = store.getFunds();
|
|
||||||
|
|
||||||
if (!memberName) {
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"error",
|
|
||||||
"Select a member to receive funds.",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount <= 0) {
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"error",
|
|
||||||
"Enter a valid transfer amount.",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount > funds) {
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"error",
|
|
||||||
"Insufficient org funds for this transfer.",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
store.setFunds(funds - amount);
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"success",
|
|
||||||
`${this.formatCurrency(amount)} sent to ${memberName}.`,
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
grantCreditLine(memberName, amount) {
|
|
||||||
if (!permissions.canManageTreasury()) {
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"error",
|
|
||||||
"Only the organization leader or CEO can manage treasury actions.",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!memberName) {
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"error",
|
|
||||||
"Select a member for the credit line.",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount <= 0) {
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"error",
|
|
||||||
"Enter a valid credit line amount.",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
store.setCreditLines((currentLines) => {
|
|
||||||
const existingIndex = currentLines.findIndex(
|
|
||||||
(line) => line.member === memberName,
|
|
||||||
);
|
|
||||||
if (existingIndex === -1) {
|
|
||||||
return [...currentLines, { member: memberName, amount }];
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedLines = [...currentLines];
|
|
||||||
updatedLines[existingIndex] = { member: memberName, amount };
|
|
||||||
return updatedLines;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"success",
|
|
||||||
`Credit line of ${this.formatCurrency(amount)} assigned to ${memberName}.`,
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OrgPortal.actions = new OrgPortalActions();
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
.org-activity-row {
|
|
||||||
padding: 1rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-left: 3px solid #94a3b8;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
background: #f8fafc;
|
|
||||||
|
|
||||||
&:nth-child(even) {
|
|
||||||
background: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
rgb(248 250 252) 0%,
|
|
||||||
rgb(241 245 249) 100%
|
|
||||||
);
|
|
||||||
border-color: rgb(148 163 184 / 0.45);
|
|
||||||
border-left-color: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--text-main);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-activity-time {
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 0.35rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
const { h } = OrgPortal.runtime;
|
|
||||||
const { portalData } = OrgPortal.data;
|
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
|
||||||
|
|
||||||
OrgPortal.componentFns.ActivityCard = function ActivityCard() {
|
|
||||||
return h(
|
|
||||||
"section",
|
|
||||||
{ className: "card org-panel org-scroll-panel org-span-6" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-panel-head" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h2", { className: "org-panel-title" }, "Command Feed"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
{ className: "org-panel-subtitle" },
|
|
||||||
"Recent organization-level actions and updates.",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-activity-list" },
|
|
||||||
...portalData.activity.map((item) =>
|
|
||||||
h(
|
|
||||||
"article",
|
|
||||||
{ className: "org-activity-row" },
|
|
||||||
h(
|
|
||||||
"span",
|
|
||||||
{ className: "org-activity-time" },
|
|
||||||
item.time,
|
|
||||||
),
|
|
||||||
h("p", null, item.text),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
const { h } = OrgPortal.runtime;
|
|
||||||
const { portalData } = OrgPortal.data;
|
|
||||||
const actions = OrgPortal.actions;
|
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
|
||||||
|
|
||||||
OrgPortal.componentFns.AssetsCard = function AssetsCard() {
|
|
||||||
const SimpleStat = OrgPortal.componentFns.SimpleStat;
|
|
||||||
|
|
||||||
return h(
|
|
||||||
"section",
|
|
||||||
{ className: "card org-panel org-scroll-panel org-span-7" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-panel-head" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h2", { className: "org-panel-title" }, "Assets"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
{ className: "org-panel-subtitle" },
|
|
||||||
"Inventory supplies and equipment with quantity totals.",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-simple-list" },
|
|
||||||
...portalData.assets.map((asset) =>
|
|
||||||
h(
|
|
||||||
"article",
|
|
||||||
{ className: "org-simple-row" },
|
|
||||||
h(
|
|
||||||
"strong",
|
|
||||||
{ className: "org-simple-name" },
|
|
||||||
asset.name,
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-simple-meta" },
|
|
||||||
SimpleStat(
|
|
||||||
"Type",
|
|
||||||
actions.formatAssetType(asset.type),
|
|
||||||
),
|
|
||||||
SimpleStat("Quantity", asset.quantity),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
.org-danger-panel {
|
|
||||||
border-color: #fecaca;
|
|
||||||
background: linear-gradient(180deg, #ffffff 0%, #fff7f7 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-danger-copy {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
strong,
|
|
||||||
p {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0.4rem 0 0;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-empty-state {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
const { h } = OrgPortal.runtime;
|
|
||||||
const permissions = OrgPortal.permissions;
|
|
||||||
const actions = OrgPortal.actions;
|
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
|
||||||
|
|
||||||
OrgPortal.componentFns.DangerCard = function DangerCard() {
|
|
||||||
if (!permissions.canDisbandOrg()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return h(
|
|
||||||
"section",
|
|
||||||
{ className: "card org-panel org-span-12 org-danger-panel" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-panel-head" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h(
|
|
||||||
"h2",
|
|
||||||
{ className: "org-panel-title" },
|
|
||||||
"Organization Controls",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
{ className: "org-panel-subtitle" },
|
|
||||||
"Leader-only actions for membership and permanent organization removal.",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-danger-copy" },
|
|
||||||
h("strong", null, "Disband organization"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
null,
|
|
||||||
"This removes the organization and revokes access to the portal for all members.",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
className: "org-danger-btn",
|
|
||||||
onClick: () => actions.openModal("disband"),
|
|
||||||
},
|
|
||||||
"Disband Organization",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
const { h } = OrgPortal.runtime;
|
|
||||||
const { portalData } = OrgPortal.data;
|
|
||||||
const registryStore = window.RegistryApp.store;
|
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
|
||||||
|
|
||||||
OrgPortal.componentFns.DisbandedView = function DisbandedView() {
|
|
||||||
return h(
|
|
||||||
"section",
|
|
||||||
{ className: "card org-panel org-span-12 org-empty-state" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-panel-head" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-eyebrow" },
|
|
||||||
"Organization Removed",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"h2",
|
|
||||||
{ className: "org-panel-title" },
|
|
||||||
portalData.org.name,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
{ className: "org-summary" },
|
|
||||||
"This organization has been disbanded. Member access, assets, and fleet management are no longer available from this portal preview.",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
className: "org-secondary-btn",
|
|
||||||
onClick: () => registryStore.setView("home"),
|
|
||||||
},
|
|
||||||
"Return to Registry",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
const { h } = OrgPortal.runtime;
|
|
||||||
const { portalData } = OrgPortal.data;
|
|
||||||
const actions = OrgPortal.actions;
|
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
|
||||||
|
|
||||||
OrgPortal.componentFns.FleetCard = function FleetCard() {
|
|
||||||
const SimpleStat = OrgPortal.componentFns.SimpleStat;
|
|
||||||
|
|
||||||
return h(
|
|
||||||
"section",
|
|
||||||
{ className: "card org-panel org-scroll-panel org-span-7" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-panel-head" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h2", { className: "org-panel-title" }, "Fleet"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
{ className: "org-panel-subtitle" },
|
|
||||||
"Individual vehicles with type, status, and overall damage.",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-simple-list" },
|
|
||||||
...portalData.fleet.map((unit) =>
|
|
||||||
h(
|
|
||||||
"article",
|
|
||||||
{ className: "org-simple-row" },
|
|
||||||
h(
|
|
||||||
"strong",
|
|
||||||
{ className: "org-simple-name" },
|
|
||||||
unit.name,
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-simple-meta" },
|
|
||||||
SimpleStat(
|
|
||||||
"Type",
|
|
||||||
actions.formatVehicleType(unit.type),
|
|
||||||
),
|
|
||||||
SimpleStat("Status", unit.status),
|
|
||||||
SimpleStat("Damage", unit.damage),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
const { h } = OrgPortal.runtime;
|
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
|
||||||
|
|
||||||
OrgPortal.componentFns.Footer = function Footer() {
|
|
||||||
return h(
|
|
||||||
"div",
|
|
||||||
{ className: "footer" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "wrapper" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h3", null, "Organization Controls"),
|
|
||||||
h(
|
|
||||||
"ul",
|
|
||||||
{ style: { listStyleType: "none", padding: 0 } },
|
|
||||||
h("li", null, "Roster Management"),
|
|
||||||
h("li", null, "Fleet Assignment"),
|
|
||||||
h("li", null, "Treasury Permissions"),
|
|
||||||
h("li", null, "Asset Registry"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h3", null, "Planned Extensions"),
|
|
||||||
h(
|
|
||||||
"ul",
|
|
||||||
{ style: { listStyleType: "none", padding: 0 } },
|
|
||||||
h("li", null, "Contracts Board"),
|
|
||||||
h("li", null, "Diplomacy Layer"),
|
|
||||||
h("li", null, "Procurement Queue"),
|
|
||||||
h("li", null, "Reputation History"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
.org-roadmap-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
overflow: auto;
|
|
||||||
padding-right: 0.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-roadmap-card {
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.7rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
background: #f8fafc;
|
|
||||||
|
|
||||||
&:nth-child(4n + 2),
|
|
||||||
&:nth-child(4n + 3) {
|
|
||||||
background: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
rgb(248 250 252) 0%,
|
|
||||||
rgb(241 245 249) 100%
|
|
||||||
);
|
|
||||||
border-color: rgb(100 116 139 / 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--text-main);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-list-tag {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0.2rem 0.55rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
background: #e2e8f0;
|
|
||||||
color: var(--primary-hover);
|
|
||||||
|
|
||||||
.org-roadmap-card:nth-child(4n + 2) &,
|
|
||||||
.org-roadmap-card:nth-child(4n + 3) & {
|
|
||||||
background: #cbd5e1;
|
|
||||||
color: #1e293b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
|
||||||
.org-roadmap-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-roadmap-card {
|
|
||||||
&:nth-child(4n + 3) {
|
|
||||||
background: #f8fafc;
|
|
||||||
border-color: var(--border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-list-tag {
|
|
||||||
.org-roadmap-card:nth-child(4n + 3) & {
|
|
||||||
background: #e2e8f0;
|
|
||||||
color: var(--primary-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
const { h } = OrgPortal.runtime;
|
|
||||||
const { portalData } = OrgPortal.data;
|
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
|
||||||
|
|
||||||
OrgPortal.componentFns.FutureCard = function FutureCard() {
|
|
||||||
return h(
|
|
||||||
"section",
|
|
||||||
{ className: "card org-panel org-scroll-panel org-span-6" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-panel-head" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h(
|
|
||||||
"h2",
|
|
||||||
{ className: "org-panel-title" },
|
|
||||||
"Expansion Slots",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
{ className: "org-panel-subtitle" },
|
|
||||||
"Potential modules are tagged by status such as Planned, In Design, In Review, and Future Review.",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-roadmap-grid" },
|
|
||||||
...portalData.roadmap.map((item) =>
|
|
||||||
h(
|
|
||||||
"article",
|
|
||||||
{ className: "org-roadmap-card" },
|
|
||||||
h("span", { className: "org-list-tag" }, item.status),
|
|
||||||
h("strong", null, item.name),
|
|
||||||
h("p", null, item.detail),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
const { h } = OrgPortal.runtime;
|
|
||||||
const store = OrgPortal.store;
|
|
||||||
|
|
||||||
OrgPortal.components = OrgPortal.components || {};
|
|
||||||
|
|
||||||
OrgPortal.components.App = function App() {
|
|
||||||
const PortalHeader = OrgPortal.componentFns.PortalHeader;
|
|
||||||
const OverviewCard = OrgPortal.componentFns.OverviewCard;
|
|
||||||
const FleetCard = OrgPortal.componentFns.FleetCard;
|
|
||||||
const TreasuryCard = OrgPortal.componentFns.TreasuryCard;
|
|
||||||
const MembersCard = OrgPortal.componentFns.MembersCard;
|
|
||||||
const AssetsCard = OrgPortal.componentFns.AssetsCard;
|
|
||||||
const ActivityCard = OrgPortal.componentFns.ActivityCard;
|
|
||||||
const FutureCard = OrgPortal.componentFns.FutureCard;
|
|
||||||
const DangerCard = OrgPortal.componentFns.DangerCard;
|
|
||||||
const ModalLayer = OrgPortal.componentFns.ModalLayer;
|
|
||||||
const DisbandedView = OrgPortal.componentFns.DisbandedView;
|
|
||||||
const Footer = OrgPortal.componentFns.Footer;
|
|
||||||
|
|
||||||
if (store.getOrgDisbanded()) {
|
|
||||||
return h(
|
|
||||||
"main",
|
|
||||||
null,
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "container" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-dashboard-grid" },
|
|
||||||
PortalHeader(),
|
|
||||||
DisbandedView(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ModalLayer(),
|
|
||||||
Footer(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return h(
|
|
||||||
"main",
|
|
||||||
null,
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "container" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-dashboard-grid" },
|
|
||||||
PortalHeader(),
|
|
||||||
OverviewCard(),
|
|
||||||
FleetCard(),
|
|
||||||
TreasuryCard(),
|
|
||||||
MembersCard(),
|
|
||||||
AssetsCard(),
|
|
||||||
ActivityCard(),
|
|
||||||
FutureCard(),
|
|
||||||
DangerCard(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ModalLayer(),
|
|
||||||
Footer(),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
.org-dashboard-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-panel {
|
|
||||||
margin-bottom: 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-scroll-panel {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-height: 31rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-span-12 {
|
|
||||||
grid-column: span 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-span-7 {
|
|
||||||
grid-column: span 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-span-6 {
|
|
||||||
grid-column: span 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-span-5 {
|
|
||||||
grid-column: span 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-panel-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
|
|
||||||
&.org-panel-head-stack {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-eyebrow {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-panel-title {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--primary-hover);
|
|
||||||
font-size: 1.45rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-panel-subtitle {
|
|
||||||
margin: 0.35rem 0 0;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
|
||||||
.org-span-12,
|
|
||||||
.org-span-7,
|
|
||||||
.org-span-6,
|
|
||||||
.org-span-5 {
|
|
||||||
grid-column: span 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-panel-head {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
.org-metric-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-metric-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.45rem;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
|
||||||
|
|
||||||
&:nth-child(4n + 2),
|
|
||||||
&:nth-child(4n + 3) {
|
|
||||||
background: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
rgb(248 250 252) 0%,
|
|
||||||
rgb(226 232 240) 100%
|
|
||||||
);
|
|
||||||
border-color: rgb(100 116 139 / 0.35);
|
|
||||||
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-metric-label {
|
|
||||||
font-size: 0.76rem;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-metric-value {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
color: var(--primary-hover);
|
|
||||||
line-height: 1.1;
|
|
||||||
|
|
||||||
.org-metric-card:nth-child(4n + 2) &,
|
|
||||||
.org-metric-card:nth-child(4n + 3) & {
|
|
||||||
color: #334155;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-metric-note {
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
|
||||||
.org-metric-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-metric-card {
|
|
||||||
&:nth-child(4n + 3) {
|
|
||||||
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
|
||||||
border-color: var(--border);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-metric-value {
|
|
||||||
.org-metric-card:nth-child(4n + 3) & {
|
|
||||||
color: var(--primary-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
const { h } = OrgPortal.runtime;
|
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
|
||||||
|
|
||||||
OrgPortal.componentFns.MetricCard = function MetricCard(
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
note,
|
|
||||||
) {
|
|
||||||
return h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-metric-card" },
|
|
||||||
h("span", { className: "org-metric-label" }, label),
|
|
||||||
h("strong", { className: "org-metric-value" }, value),
|
|
||||||
h("span", { className: "org-metric-note" }, note),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,131 +0,0 @@
|
|||||||
.org-modal-backdrop {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgb(15 23 42 / 0.38);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 1.5rem;
|
|
||||||
z-index: 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-modal-card {
|
|
||||||
width: min(100%, 30rem);
|
|
||||||
margin-bottom: 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-modal-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-modal-close {
|
|
||||||
width: 2.25rem;
|
|
||||||
height: 2.25rem;
|
|
||||||
padding: 0;
|
|
||||||
background: var(--bg-surface);
|
|
||||||
color: var(--text-main);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
box-shadow: none;
|
|
||||||
transform: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--bg-surface-hover);
|
|
||||||
color: var(--text-main);
|
|
||||||
box-shadow: none;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-modal-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.75rem;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: var(--bg-app);
|
|
||||||
color: var(--text-main);
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 1rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
transition:
|
|
||||||
border-color 0.2s,
|
|
||||||
box-shadow 0.2s;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--primary);
|
|
||||||
box-shadow: 0 0 0 2px rgb(71 85 105 / 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
background: #f1f5f9;
|
|
||||||
color: var(--text-muted);
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-modal-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 0.75rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
|
|
||||||
button + button {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-danger-confirm {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
border: 1px solid #fecaca;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
background: #fff1f2;
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--text-main);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-danger-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.75rem;
|
|
||||||
|
|
||||||
button + button {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
|
||||||
.org-modal-head,
|
|
||||||
.org-danger-confirm {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
.org-hero-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1.3fr 1fr;
|
|
||||||
gap: 1.5rem;
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-summary {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.05rem;
|
|
||||||
color: var(--text-main);
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-meta-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-meta-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.4rem;
|
|
||||||
padding: 1rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
background: #f8fafc;
|
|
||||||
|
|
||||||
&:nth-child(even) {
|
|
||||||
background: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
rgb(241 245 249) 0%,
|
|
||||||
rgb(226 232 240) 100%
|
|
||||||
);
|
|
||||||
border-color: rgb(148 163 184 / 0.45);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-meta-label {
|
|
||||||
font-size: 0.76rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-meta-value {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--primary-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
|
||||||
.org-hero-grid,
|
|
||||||
.org-meta-row {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
const { h } = OrgPortal.runtime;
|
|
||||||
const { portalData, session } = OrgPortal.data;
|
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
|
||||||
|
|
||||||
OrgPortal.componentFns.PortalHeader = function PortalHeader() {
|
|
||||||
return h(
|
|
||||||
"section",
|
|
||||||
{ className: "card org-panel org-span-12 org-page-header" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-page-heading" },
|
|
||||||
h("span", { className: "org-page-kicker" }, portalData.org.tag),
|
|
||||||
h("h1", { className: "org-page-title" }, portalData.org.name),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
{ className: "org-page-subtitle" },
|
|
||||||
"Player organization command portal",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"span",
|
|
||||||
{ className: "org-page-meta" },
|
|
||||||
`${session.actorName} - ${session.role}`,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
.org-simple-list,
|
|
||||||
.org-name-list,
|
|
||||||
.org-activity-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
gap: 0.85rem;
|
|
||||||
min-height: 0;
|
|
||||||
overflow: auto;
|
|
||||||
padding-right: 0.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-simple-list,
|
|
||||||
.org-name-list,
|
|
||||||
.org-activity-list,
|
|
||||||
.org-roadmap-grid {
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: #94a3b8 #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-simple-row,
|
|
||||||
.org-name-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
background: #f8fafc;
|
|
||||||
|
|
||||||
&:nth-child(even) {
|
|
||||||
background: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
rgb(248 250 252) 0%,
|
|
||||||
rgb(241 245 249) 100%
|
|
||||||
);
|
|
||||||
border-color: rgb(148 163 184 / 0.45);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-simple-name {
|
|
||||||
color: var(--primary-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-simple-meta {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-name-row {
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
|
||||||
.org-simple-row,
|
|
||||||
.org-name-row {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
.org-simple-stat {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.2rem;
|
|
||||||
min-width: 90px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-simple-label {
|
|
||||||
font-size: 0.72rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-simple-value {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
color: var(--text-main);
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
const { h } = OrgPortal.runtime;
|
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
|
||||||
|
|
||||||
OrgPortal.componentFns.SimpleStat = function SimpleStat(label, value) {
|
|
||||||
return h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-simple-stat" },
|
|
||||||
h("span", { className: "org-simple-label" }, label),
|
|
||||||
h("strong", { className: "org-simple-value" }, value),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
.org-finance-meta {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
padding: 1rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
background: #f8fafc;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-action-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.75rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
button + button {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-treasury-notice {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding: 0.85rem 1rem;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
font-size: 0.92rem;
|
|
||||||
|
|
||||||
&.is-success {
|
|
||||||
background: #ecfdf5;
|
|
||||||
border: 1px solid #bbf7d0;
|
|
||||||
color: #166534;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-error {
|
|
||||||
background: #fef2f2;
|
|
||||||
border: 1px solid #fecaca;
|
|
||||||
color: #991b1b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-credit-lines {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-access-note {
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-credit-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-credit-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 0.9rem 1rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
background: #f8fafc;
|
|
||||||
|
|
||||||
&:nth-child(even) {
|
|
||||||
background: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
rgb(248 250 252) 0%,
|
|
||||||
rgb(241 245 249) 100%
|
|
||||||
);
|
|
||||||
border-color: rgb(148 163 184 / 0.45);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
|
||||||
.org-finance-meta {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-credit-row {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,126 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
const { h } = OrgPortal.runtime;
|
|
||||||
const { portalData } = OrgPortal.data;
|
|
||||||
const store = OrgPortal.store;
|
|
||||||
const permissions = OrgPortal.permissions;
|
|
||||||
const actions = OrgPortal.actions;
|
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
|
||||||
|
|
||||||
OrgPortal.componentFns.TreasuryCard = function TreasuryCard() {
|
|
||||||
const notice = store.getTreasuryNotice();
|
|
||||||
const creditLines = store.getCreditLines();
|
|
||||||
const allowTreasuryActions = permissions.canManageTreasury();
|
|
||||||
|
|
||||||
return h(
|
|
||||||
"section",
|
|
||||||
{ className: "card org-panel org-span-5" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-panel-head" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h2", { className: "org-panel-title" }, "Treasury"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
{ className: "org-panel-subtitle" },
|
|
||||||
"Organization funds, reputation, and member payouts.",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-finance-meta" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("span", { className: "org-meta-label" }, "Funds"),
|
|
||||||
h("strong", null, actions.formatCurrency(store.getFunds())),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("span", { className: "org-meta-label" }, "Reputation"),
|
|
||||||
h("strong", null, `${portalData.reputation}`),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
allowTreasuryActions
|
|
||||||
? h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-action-grid" },
|
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
onClick: () => actions.openModal("payroll"),
|
|
||||||
},
|
|
||||||
"Run Payroll",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
className: "org-secondary-btn",
|
|
||||||
onClick: () => actions.openModal("transfer"),
|
|
||||||
},
|
|
||||||
"Send Funds",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
className: "org-secondary-btn",
|
|
||||||
onClick: () => actions.openModal("credit"),
|
|
||||||
},
|
|
||||||
"Credit Line",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: h(
|
|
||||||
"p",
|
|
||||||
{ className: "org-access-note" },
|
|
||||||
"Only the organization leader or CEO can manage treasury actions.",
|
|
||||||
),
|
|
||||||
notice.text
|
|
||||||
? h(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
className:
|
|
||||||
notice.type === "error"
|
|
||||||
? "org-treasury-notice is-error"
|
|
||||||
: "org-treasury-notice is-success",
|
|
||||||
},
|
|
||||||
notice.text,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
creditLines.length > 0
|
|
||||||
? h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-credit-lines" },
|
|
||||||
h(
|
|
||||||
"span",
|
|
||||||
{ className: "org-meta-label" },
|
|
||||||
"Active Credit Lines",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-credit-list" },
|
|
||||||
...creditLines.map((line) =>
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-credit-row" },
|
|
||||||
h("span", null, line.member),
|
|
||||||
h(
|
|
||||||
"strong",
|
|
||||||
null,
|
|
||||||
actions.formatCurrency(line.amount),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,71 +1,10 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
const { portalData, session } = OrgPortal.data;
|
const { portalData, session } = OrgPortal.data;
|
||||||
|
const SharedLogic = (window.SharedLogic = window.SharedLogic || {});
|
||||||
|
|
||||||
class OrgPortalPermissions {
|
OrgPortal.permissions = SharedLogic.createPortalPermissions({
|
||||||
getNormalizedRole() {
|
portalData,
|
||||||
return String(session.role || "")
|
session,
|
||||||
.trim()
|
});
|
||||||
.toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
isDefaultOrg() {
|
|
||||||
return (
|
|
||||||
portalData.org.isDefault === true ||
|
|
||||||
String(portalData.org.tag || "")
|
|
||||||
.trim()
|
|
||||||
.toUpperCase() === "DEFAULT"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
isOrgOwner() {
|
|
||||||
const ownerUid = String(
|
|
||||||
portalData.org.ownerUid || portalData.org.owner || "",
|
|
||||||
)
|
|
||||||
.trim()
|
|
||||||
.toLowerCase();
|
|
||||||
const actorUid = String(session.actorUid || "")
|
|
||||||
.trim()
|
|
||||||
.toLowerCase();
|
|
||||||
|
|
||||||
if (ownerUid && actorUid) {
|
|
||||||
return actorUid === ownerUid;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
String(session.actorName || "")
|
|
||||||
.trim()
|
|
||||||
.toLowerCase() ===
|
|
||||||
String(portalData.org.owner || "")
|
|
||||||
.trim()
|
|
||||||
.toLowerCase()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
isSessionCeo() {
|
|
||||||
return session.ceo === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
isOrgLeaderOrCeo() {
|
|
||||||
return (
|
|
||||||
this.isOrgOwner() ||
|
|
||||||
this.getNormalizedRole() === "LEADER" ||
|
|
||||||
(this.isDefaultOrg() && this.isSessionCeo())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
canManageMembers() {
|
|
||||||
return this.isOrgLeaderOrCeo();
|
|
||||||
}
|
|
||||||
|
|
||||||
canManageTreasury() {
|
|
||||||
return this.isOrgLeaderOrCeo();
|
|
||||||
}
|
|
||||||
|
|
||||||
canDisbandOrg() {
|
|
||||||
return this.isOrgLeaderOrCeo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OrgPortal.permissions = new OrgPortalPermissions();
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -1,98 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
|
||||||
|
|
||||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
||||||
const SVG_TAGS = new Set([
|
|
||||||
"svg",
|
|
||||||
"path",
|
|
||||||
"circle",
|
|
||||||
"rect",
|
|
||||||
"line",
|
|
||||||
"polyline",
|
|
||||||
"polygon",
|
|
||||||
"g",
|
|
||||||
"defs",
|
|
||||||
"use",
|
|
||||||
"text",
|
|
||||||
"tspan",
|
|
||||||
"clipPath",
|
|
||||||
"mask",
|
|
||||||
]);
|
|
||||||
|
|
||||||
function h(tag, props = {}, ...children) {
|
|
||||||
const isSvg = SVG_TAGS.has(tag);
|
|
||||||
const el = isSvg
|
|
||||||
? document.createElementNS(SVG_NS, tag)
|
|
||||||
: document.createElement(tag);
|
|
||||||
|
|
||||||
if (props) {
|
|
||||||
Object.entries(props).forEach(([key, value]) => {
|
|
||||||
if (key.startsWith("on") && typeof value === "function") {
|
|
||||||
el.addEventListener(key.substring(2).toLowerCase(), value);
|
|
||||||
} else if (key === "className") {
|
|
||||||
if (isSvg) {
|
|
||||||
el.setAttribute("class", value);
|
|
||||||
} else {
|
|
||||||
el.className = value;
|
|
||||||
}
|
|
||||||
} else if (key === "style" && typeof value === "object") {
|
|
||||||
Object.assign(el.style, value);
|
|
||||||
} else if (typeof value === "boolean") {
|
|
||||||
if (value) {
|
|
||||||
el.setAttribute(key, "");
|
|
||||||
} else {
|
|
||||||
el.removeAttribute(key);
|
|
||||||
}
|
|
||||||
} else if (value === null || value === undefined) {
|
|
||||||
el.removeAttribute(key);
|
|
||||||
} else {
|
|
||||||
el.setAttribute(key, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
children.forEach((child) => {
|
|
||||||
if (typeof child === "string" || typeof child === "number") {
|
|
||||||
el.appendChild(document.createTextNode(child));
|
|
||||||
} else if (child instanceof Node) {
|
|
||||||
el.appendChild(child);
|
|
||||||
} else if (Array.isArray(child)) {
|
|
||||||
child.forEach((c) => el.appendChild(c));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rootContainer = null;
|
|
||||||
let rootComponent = null;
|
|
||||||
|
|
||||||
function render(component, container) {
|
|
||||||
rootContainer = container;
|
|
||||||
rootComponent = component;
|
|
||||||
rerender();
|
|
||||||
}
|
|
||||||
|
|
||||||
function rerender() {
|
|
||||||
rootContainer.innerHTML = "";
|
|
||||||
rootContainer.appendChild(rootComponent());
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSignal(initialValue) {
|
|
||||||
let value = initialValue;
|
|
||||||
|
|
||||||
const getValue = () => value;
|
|
||||||
const setValue = (newValue) => {
|
|
||||||
value = typeof newValue === "function" ? newValue(value) : newValue;
|
|
||||||
rerender();
|
|
||||||
};
|
|
||||||
|
|
||||||
return [getValue, setValue];
|
|
||||||
}
|
|
||||||
|
|
||||||
OrgPortal.runtime = {
|
|
||||||
h,
|
|
||||||
render,
|
|
||||||
createSignal,
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -2,31 +2,10 @@
|
|||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
const { createSignal } = window.RegistryApp.runtime;
|
const { createSignal } = window.RegistryApp.runtime;
|
||||||
const { portalData } = OrgPortal.data;
|
const { portalData } = OrgPortal.data;
|
||||||
|
const SharedLogic = (window.SharedLogic = window.SharedLogic || {});
|
||||||
|
|
||||||
class OrgPortalStore {
|
OrgPortal.store = SharedLogic.createPortalStore({
|
||||||
constructor() {
|
createSignal,
|
||||||
[this.getFunds, this.setFunds] = createSignal(portalData.funds);
|
portalData,
|
||||||
[this.getMembers, this.setMembers] = createSignal([
|
});
|
||||||
...portalData.members,
|
|
||||||
]);
|
|
||||||
[this.getCreditLines, this.setCreditLines] = createSignal([]);
|
|
||||||
[this.getTreasuryNotice, this.setTreasuryNotice] = createSignal({
|
|
||||||
type: "",
|
|
||||||
text: "",
|
|
||||||
});
|
|
||||||
[this.getModal, this.setModal] = createSignal(null);
|
|
||||||
[this.getOrgDisbanded, this.setOrgDisbanded] = createSignal(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
hydrateFromPayload(payload) {
|
|
||||||
this.setFunds(payload.portalData.funds || 0);
|
|
||||||
this.setMembers([...(payload.portalData.members || [])]);
|
|
||||||
this.setCreditLines([]);
|
|
||||||
this.setTreasuryNotice({ type: "", text: "" });
|
|
||||||
this.setModal(null);
|
|
||||||
this.setOrgDisbanded(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OrgPortal.store = new OrgPortalStore();
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
|
||||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||||
const SVG_TAGS = new Set([
|
const SVG_TAGS = new Set([
|
||||||
@ -66,6 +67,7 @@
|
|||||||
|
|
||||||
let rootContainer = null;
|
let rootContainer = null;
|
||||||
let rootComponent = null;
|
let rootComponent = null;
|
||||||
|
const injectedStyles = new Set();
|
||||||
|
|
||||||
function render(component, container) {
|
function render(component, container) {
|
||||||
rootContainer = container;
|
rootContainer = container;
|
||||||
@ -74,10 +76,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rerender() {
|
function rerender() {
|
||||||
|
if (!rootContainer || !rootComponent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
rootContainer.innerHTML = "";
|
rootContainer.innerHTML = "";
|
||||||
rootContainer.appendChild(rootComponent());
|
rootContainer.appendChild(rootComponent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureScopedStyle(id, cssText) {
|
||||||
|
if (!id || !cssText || injectedStyles.has(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.setAttribute("data-ui-style", id);
|
||||||
|
style.textContent = cssText;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
injectedStyles.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
function createSignal(initialValue) {
|
function createSignal(initialValue) {
|
||||||
let value = initialValue;
|
let value = initialValue;
|
||||||
|
|
||||||
@ -90,9 +108,14 @@
|
|||||||
return [getValue, setValue];
|
return [getValue, setValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
RegistryApp.runtime = {
|
const runtime = {
|
||||||
h,
|
h,
|
||||||
render,
|
render,
|
||||||
createSignal,
|
createSignal,
|
||||||
|
ensureScopedStyle,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RegistryApp.runtime = runtime;
|
||||||
|
OrgPortal.runtime = runtime;
|
||||||
|
window.AppRuntime = runtime;
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -1,43 +1,23 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
const { createSignal } = RegistryApp.runtime;
|
const { createSignal } = RegistryApp.runtime;
|
||||||
|
const SharedLogic = (window.SharedLogic = window.SharedLogic || {});
|
||||||
|
|
||||||
class RegistryStore {
|
RegistryApp.store = SharedLogic.createRegistryStore({
|
||||||
constructor() {
|
createSignal,
|
||||||
[this.getView, this.setView] = createSignal("home");
|
onHydratePortal(payload) {
|
||||||
[this.getIsAuthenticating, this.setIsAuthenticating] =
|
|
||||||
createSignal(false);
|
|
||||||
[this.getLoginError, this.setLoginError] = createSignal("");
|
|
||||||
}
|
|
||||||
|
|
||||||
startLogin() {
|
|
||||||
this.setLoginError("");
|
|
||||||
this.setIsAuthenticating(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
failLogin(message) {
|
|
||||||
this.setIsAuthenticating(false);
|
|
||||||
this.setLoginError(message || "Authentication failed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
completeLogin(payload) {
|
|
||||||
const OrgPortal = window.OrgPortal;
|
const OrgPortal = window.OrgPortal;
|
||||||
const portalData = payload?.portalData;
|
const portalData = payload?.portalData;
|
||||||
const session = payload?.session;
|
const session = payload?.session;
|
||||||
|
|
||||||
if (!OrgPortal || !portalData || !session) {
|
if (!OrgPortal || !portalData || !session) {
|
||||||
this.failLogin("Login response was missing portal data.");
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OrgPortal.data.applyLoginPayload(payload);
|
OrgPortal.data.applyLoginPayload(payload);
|
||||||
OrgPortal.store.hydrateFromPayload(payload);
|
OrgPortal.store.hydrateFromPayload(payload);
|
||||||
|
RegistryApp.store.setView("portal");
|
||||||
this.setLoginError("");
|
return true;
|
||||||
this.setIsAuthenticating(false);
|
},
|
||||||
this.setView("portal");
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RegistryApp.store = new RegistryStore();
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
36
arma/client/addons/org/ui/_site/views/DisbandedView.js
Normal file
36
arma/client/addons/org/ui/_site/views/DisbandedView.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h } = OrgPortal.runtime;
|
||||||
|
const { portalData } = OrgPortal.data;
|
||||||
|
const registryStore = window.RegistryApp.store;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.DisbandedView = function DisbandedView() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-span-12 org-empty-state",
|
||||||
|
eyebrow: "Organization Removed",
|
||||||
|
title: portalData.org.name,
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h(
|
||||||
|
"p",
|
||||||
|
{ className: "org-summary" },
|
||||||
|
"This organization has been disbanded. Member access, assets, and fleet management are no longer available from this portal preview.",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-secondary-btn",
|
||||||
|
onClick: () => registryStore.setView("home"),
|
||||||
|
},
|
||||||
|
"Return to Registry",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
89
arma/client/addons/org/ui/_site/views/HomeView.js
Normal file
89
arma/client/addons/org/ui/_site/views/HomeView.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
(function () {
|
||||||
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const { h, ensureScopedStyle } = RegistryApp.runtime;
|
||||||
|
const store = RegistryApp.store;
|
||||||
|
const bridge = RegistryApp.bridge;
|
||||||
|
const scopeAttr = "data-ui-home-view";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const homeViewCss = `
|
||||||
|
${scopeSelector} {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .home-feedback {
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
background: #fef2f2;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
||||||
|
|
||||||
|
RegistryApp.componentFns.HomeView = function HomeView() {
|
||||||
|
const isAuthenticating = store.getIsAuthenticating();
|
||||||
|
const loginError = store.getLoginError();
|
||||||
|
ensureScopedStyle("main-home-view", homeViewCss);
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{ className: "content", [scopeAttr]: "" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "card" },
|
||||||
|
h("h2", null, "Create Organization"),
|
||||||
|
h(
|
||||||
|
"p",
|
||||||
|
null,
|
||||||
|
"Establish your Task Force, PMC, or Milsim unit with the Global Organization Network. Receive your official unit designator and TO&E authorization instantly.",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{ onClick: () => store.setView("create") },
|
||||||
|
"Register",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "card" },
|
||||||
|
h("h2", null, "Organization Portal"),
|
||||||
|
h(
|
||||||
|
"p",
|
||||||
|
null,
|
||||||
|
"Access your unit dashboard to modify rosters, adjust active deployments, and submit after-action reports through the secure field uplink.",
|
||||||
|
),
|
||||||
|
loginError
|
||||||
|
? h("div", { className: "home-feedback" }, loginError)
|
||||||
|
: null,
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
disabled: isAuthenticating,
|
||||||
|
onClick: () => {
|
||||||
|
if (!bridge) {
|
||||||
|
store.failLogin(
|
||||||
|
"Login bridge is not available.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.requestLogin({});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isAuthenticating ? "Opening Portal..." : "Login",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
206
arma/client/addons/org/ui/_site/views/PortalView.js
Normal file
206
arma/client/addons/org/ui/_site/views/PortalView.js
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const { portalData, session } = OrgPortal.data;
|
||||||
|
const store = OrgPortal.store;
|
||||||
|
const portalViewScope = "[data-ui-portal-view]";
|
||||||
|
|
||||||
|
ensureScopedStyle(
|
||||||
|
"portal-view",
|
||||||
|
`
|
||||||
|
${portalViewScope} .org-toast-stack {
|
||||||
|
position: fixed;
|
||||||
|
top: 1.5rem;
|
||||||
|
right: 2rem;
|
||||||
|
z-index: 20;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
${portalViewScope} .org-toast {
|
||||||
|
max-width: 24rem;
|
||||||
|
padding: 0.9rem 1rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 12px 28px rgb(15 23 42 / 0.14);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
${portalViewScope} .org-toast.is-success {
|
||||||
|
background: #ecfdf5;
|
||||||
|
border-color: #bbf7d0;
|
||||||
|
color: #166534;
|
||||||
|
}
|
||||||
|
|
||||||
|
${portalViewScope} .org-toast.is-error {
|
||||||
|
background: #fef2f2;
|
||||||
|
border-color: #fecaca;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
${portalViewScope} .org-dashboard-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${portalViewScope} .org-panel {
|
||||||
|
margin-bottom: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
${portalViewScope} .org-scroll-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 31rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
${portalViewScope} .org-span-12 {
|
||||||
|
grid-column: span 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
${portalViewScope} .org-span-7 {
|
||||||
|
grid-column: span 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
${portalViewScope} .org-span-6 {
|
||||||
|
grid-column: span 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
${portalViewScope} .org-span-5 {
|
||||||
|
grid-column: span 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${portalViewScope} .org-toast-stack {
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${portalViewScope} .org-toast {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
${portalViewScope} .org-span-12,
|
||||||
|
${portalViewScope} .org-span-7,
|
||||||
|
${portalViewScope} .org-span-6,
|
||||||
|
${portalViewScope} .org-span-5 {
|
||||||
|
grid-column: span 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
OrgPortal.components = OrgPortal.components || {};
|
||||||
|
|
||||||
|
OrgPortal.components.App = function App() {
|
||||||
|
const Hero = window.SharedUI.componentFns.Hero;
|
||||||
|
const Footer = window.SharedUI.componentFns.Footer;
|
||||||
|
const OverviewCard = OrgPortal.componentFns.OverviewCard;
|
||||||
|
const FleetCard = OrgPortal.componentFns.FleetCard;
|
||||||
|
const TreasuryCard = OrgPortal.componentFns.TreasuryCard;
|
||||||
|
const MembersCard = OrgPortal.componentFns.MembersCard;
|
||||||
|
const AssetsCard = OrgPortal.componentFns.AssetsCard;
|
||||||
|
const ActivityCard = OrgPortal.componentFns.ActivityCard;
|
||||||
|
const FutureCard = OrgPortal.componentFns.FutureCard;
|
||||||
|
const DangerCard = OrgPortal.componentFns.DangerCard;
|
||||||
|
const ModalLayer = OrgPortal.componentFns.ModalLayer;
|
||||||
|
const DisbandedView = OrgPortal.componentFns.DisbandedView;
|
||||||
|
const treasuryNotice = store.getTreasuryNotice();
|
||||||
|
const footerSections = [
|
||||||
|
{
|
||||||
|
title: "Organization Controls",
|
||||||
|
items: [
|
||||||
|
"Roster Management",
|
||||||
|
"Fleet Assignment",
|
||||||
|
"Treasury Permissions",
|
||||||
|
"Asset Registry",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Planned Extensions",
|
||||||
|
items: [
|
||||||
|
"Contracts Board",
|
||||||
|
"Diplomacy Layer",
|
||||||
|
"Procurement Queue",
|
||||||
|
"Reputation History",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (store.getOrgDisbanded()) {
|
||||||
|
return h(
|
||||||
|
"main",
|
||||||
|
{ "data-ui-portal-view": "" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "container" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-dashboard-grid" },
|
||||||
|
Hero({
|
||||||
|
kicker: portalData.org.tag,
|
||||||
|
title: portalData.org.name,
|
||||||
|
subtitle: "Player organization command portal",
|
||||||
|
meta: `${session.actorName} - ${session.role}`,
|
||||||
|
}),
|
||||||
|
DisbandedView(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ModalLayer(),
|
||||||
|
Footer({ sections: footerSections }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"main",
|
||||||
|
{ "data-ui-portal-view": "" },
|
||||||
|
treasuryNotice.text
|
||||||
|
? h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-toast-stack" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
treasuryNotice.type === "error"
|
||||||
|
? "org-toast is-error"
|
||||||
|
: "org-toast is-success",
|
||||||
|
},
|
||||||
|
treasuryNotice.text,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "container" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-dashboard-grid" },
|
||||||
|
Hero({
|
||||||
|
kicker: portalData.org.tag,
|
||||||
|
title: portalData.org.name,
|
||||||
|
subtitle: "Player organization command portal",
|
||||||
|
meta: `${session.actorName} - ${session.role}`,
|
||||||
|
}),
|
||||||
|
OverviewCard(),
|
||||||
|
FleetCard(),
|
||||||
|
TreasuryCard(),
|
||||||
|
MembersCard(),
|
||||||
|
AssetsCard(),
|
||||||
|
ActivityCard(),
|
||||||
|
FutureCard(),
|
||||||
|
DangerCard(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ModalLayer(),
|
||||||
|
Footer({ sections: footerSections }),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -1,26 +1,175 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
const { h } = RegistryApp.runtime;
|
const { h, ensureScopedStyle } = RegistryApp.runtime;
|
||||||
const store = RegistryApp.store;
|
const store = RegistryApp.store;
|
||||||
|
const bridge = RegistryApp.bridge;
|
||||||
|
const scopeAttr = "data-ui-registration-view";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const registrationViewCss = `
|
||||||
|
${scopeSelector} {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .info-panel {
|
||||||
|
text-align: left;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .create-feature-list {
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .create-feature-item {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .create-feature-icon {
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .price-tag {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--bg-app);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .price-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .price-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .form-panel {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-form label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-form input,
|
||||||
|
${scopeSelector} .app-form select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--bg-app);
|
||||||
|
color: var(--text-main);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-form input:focus,
|
||||||
|
${scopeSelector} .app-form select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 2px rgb(59 130 246 / 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .form-actions {
|
||||||
|
margin-top: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .submit-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .cancel-link {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .cancel-link:hover {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .form-feedback {
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .form-feedback.is-error {
|
||||||
|
background: #fef2f2;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
||||||
|
|
||||||
RegistryApp.componentFns.CreateOrgForm = function CreateOrgForm() {
|
RegistryApp.componentFns.RegistrationView = function RegistrationView() {
|
||||||
|
const isCreating = store.getIsCreating();
|
||||||
|
const createError = store.getCreateError();
|
||||||
|
ensureScopedStyle("main-registration-view", registrationViewCss);
|
||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
const data = {
|
const data = {
|
||||||
orgName: String(
|
orgName: String(
|
||||||
document.getElementById("org-create-name")?.value || "",
|
document.getElementById("org-create-name")?.value || "",
|
||||||
),
|
).trim(),
|
||||||
type: String(
|
type: String(
|
||||||
document.getElementById("org-create-type")?.value || "",
|
document.getElementById("org-create-type")?.value || "",
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
console.log("Org Registration:", data);
|
|
||||||
|
if (!bridge || typeof bridge.requestCreateOrg !== "function") {
|
||||||
|
store.failCreate("Registration bridge is not available.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.requestCreateOrg(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "split-container" },
|
{ className: "split-container", [scopeAttr]: "" },
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "info-panel" },
|
{ className: "info-panel" },
|
||||||
@ -32,24 +181,10 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"ul",
|
"ul",
|
||||||
{
|
{ className: "create-feature-list" },
|
||||||
style: {
|
|
||||||
textAlign: "left",
|
|
||||||
marginTop: "1.5rem",
|
|
||||||
listStyleType: "none",
|
|
||||||
padding: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
h(
|
h(
|
||||||
"li",
|
"li",
|
||||||
{
|
{ className: "create-feature-item" },
|
||||||
style: {
|
|
||||||
marginBottom: "0.5rem",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "0.5rem",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
h(
|
h(
|
||||||
"svg",
|
"svg",
|
||||||
{
|
{
|
||||||
@ -59,11 +194,7 @@
|
|||||||
"stroke-width": "2",
|
"stroke-width": "2",
|
||||||
"stroke-linecap": "round",
|
"stroke-linecap": "round",
|
||||||
"stroke-linejoin": "round",
|
"stroke-linejoin": "round",
|
||||||
style: {
|
className: "create-feature-icon",
|
||||||
width: "1.2rem",
|
|
||||||
height: "1.2rem",
|
|
||||||
flexShrink: "0",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
h("path", { d: "M20 6L9 17l-5-5" }),
|
h("path", { d: "M20 6L9 17l-5-5" }),
|
||||||
),
|
),
|
||||||
@ -71,14 +202,7 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"li",
|
"li",
|
||||||
{
|
{ className: "create-feature-item" },
|
||||||
style: {
|
|
||||||
marginBottom: "0.5rem",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "0.5rem",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
h(
|
h(
|
||||||
"svg",
|
"svg",
|
||||||
{
|
{
|
||||||
@ -88,11 +212,7 @@
|
|||||||
"stroke-width": "2",
|
"stroke-width": "2",
|
||||||
"stroke-linecap": "round",
|
"stroke-linecap": "round",
|
||||||
"stroke-linejoin": "round",
|
"stroke-linejoin": "round",
|
||||||
style: {
|
className: "create-feature-icon",
|
||||||
width: "1.2rem",
|
|
||||||
height: "1.2rem",
|
|
||||||
flexShrink: "0",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
h("path", { d: "M20 6L9 17l-5-5" }),
|
h("path", { d: "M20 6L9 17l-5-5" }),
|
||||||
),
|
),
|
||||||
@ -100,14 +220,7 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"li",
|
"li",
|
||||||
{
|
{ className: "create-feature-item" },
|
||||||
style: {
|
|
||||||
marginBottom: "0.5rem",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "0.5rem",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
h(
|
h(
|
||||||
"svg",
|
"svg",
|
||||||
{
|
{
|
||||||
@ -117,11 +230,7 @@
|
|||||||
"stroke-width": "2",
|
"stroke-width": "2",
|
||||||
"stroke-linecap": "round",
|
"stroke-linecap": "round",
|
||||||
"stroke-linejoin": "round",
|
"stroke-linejoin": "round",
|
||||||
style: {
|
className: "create-feature-icon",
|
||||||
width: "1.2rem",
|
|
||||||
height: "1.2rem",
|
|
||||||
flexShrink: "0",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
h("path", { d: "M20 6L9 17l-5-5" }),
|
h("path", { d: "M20 6L9 17l-5-5" }),
|
||||||
),
|
),
|
||||||
@ -129,14 +238,7 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"li",
|
"li",
|
||||||
{
|
{ className: "create-feature-item" },
|
||||||
style: {
|
|
||||||
marginBottom: "0.5rem",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "0.5rem",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
h(
|
h(
|
||||||
"svg",
|
"svg",
|
||||||
{
|
{
|
||||||
@ -146,11 +248,7 @@
|
|||||||
"stroke-width": "2",
|
"stroke-width": "2",
|
||||||
"stroke-linecap": "round",
|
"stroke-linecap": "round",
|
||||||
"stroke-linejoin": "round",
|
"stroke-linejoin": "round",
|
||||||
style: {
|
className: "create-feature-icon",
|
||||||
width: "1.2rem",
|
|
||||||
height: "1.2rem",
|
|
||||||
flexShrink: "0",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
h("path", { d: "M20 6L9 17l-5-5" }),
|
h("path", { d: "M20 6L9 17l-5-5" }),
|
||||||
),
|
),
|
||||||
@ -159,44 +257,14 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{
|
{ className: "price-tag" },
|
||||||
className: "price-tag",
|
h("span", { className: "price-label" }, "Registration Fee"),
|
||||||
style: {
|
h("span", { className: "price-value" }, "$50,000"),
|
||||||
marginTop: "2rem",
|
|
||||||
padding: "1rem",
|
|
||||||
background: "var(--bg-app)",
|
|
||||||
borderRadius: "var(--radius)",
|
|
||||||
border: "1px solid var(--border)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
h(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
style: {
|
|
||||||
display: "block",
|
|
||||||
fontSize: "0.9rem",
|
|
||||||
color: "var(--text-muted)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Registration Fee",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
style: {
|
|
||||||
display: "block",
|
|
||||||
fontSize: "2rem",
|
|
||||||
fontWeight: "700",
|
|
||||||
color: "var(--primary)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"$50,000",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "form-panel card", style: { margin: 0 } },
|
{ className: "form-panel card" },
|
||||||
h("h2", null, "Organization Registration"),
|
h("h2", null, "Organization Registration"),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
@ -239,14 +307,24 @@
|
|||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "form-actions" },
|
{ className: "form-actions" },
|
||||||
|
createError
|
||||||
|
? h(
|
||||||
|
"div",
|
||||||
|
{ className: "form-feedback is-error" },
|
||||||
|
createError,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
h(
|
h(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
style: { width: "100%" },
|
className: "submit-btn",
|
||||||
|
disabled: isCreating,
|
||||||
onClick: handleCreate,
|
onClick: handleCreate,
|
||||||
},
|
},
|
||||||
"Submit Registration",
|
isCreating
|
||||||
|
? "Submitting Registration..."
|
||||||
|
: "Submit Registration",
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"span",
|
"span",
|
||||||
@ -14,6 +14,29 @@ PREP_RECOMPILE_END;
|
|||||||
GVAR(OrgStore) call ["init", [_uid]];
|
GVAR(OrgStore) call ["init", [_uid]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestCreateOrg), {
|
||||||
|
params [["_uid", "", [""]], ["_orgName", "", [""]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "" || { _orgName isEqualTo "" }) exitWith {
|
||||||
|
diag_log "[FORGE:Server:Org] Empty/Invalid UID or Organization Name!"
|
||||||
|
};
|
||||||
|
|
||||||
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
private _result = GVAR(OrgStore) call ["register", [_uid, _orgName]];
|
||||||
|
|
||||||
|
if (_result getOrDefault ["success", false]) then {
|
||||||
|
private _org = _result getOrDefault ["org", createHashMap];
|
||||||
|
private _orgID = _org getOrDefault ["id", ""];
|
||||||
|
|
||||||
|
if (_orgID isNotEqualTo "") then {
|
||||||
|
private _actorPatch = EGVAR(actor,ActorStore) call ["set", [EGVAR(actor,Registry), "actor:update", _uid, "organization", _orgID, true]];
|
||||||
|
[CRPC(actor,responseSyncActor), [_actorPatch], _player] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
[CRPC(org,responseCreateOrg), [_result], _player] call CFUNC(targetEvent);
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(requestGetOrg), {
|
[QGVAR(requestGetOrg), {
|
||||||
params [["_uid", "", [""]], ["_field", "", [""]]];
|
params [["_uid", "", [""]], ["_field", "", [""]]];
|
||||||
|
|
||||||
|
|||||||
@ -82,7 +82,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
if !(_isSuccess) exitWith {
|
if !(_isSuccess) exitWith {
|
||||||
["ERROR", "Failed to check for default org!"] call EFUNC(common,log);
|
["ERROR", "Failed to check for default org!"] call EFUNC(common,log);
|
||||||
|
|
||||||
private _fallbackDefaultOrg = createHashMapFromArray [
|
private _defaultOrg = createHashMapFromArray [
|
||||||
["id", "default"],
|
["id", "default"],
|
||||||
["owner", "server"],
|
["owner", "server"],
|
||||||
["name", "Forge Dynamics"],
|
["name", "Forge Dynamics"],
|
||||||
@ -90,9 +90,9 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["reputation", 0],
|
["reputation", 0],
|
||||||
["members", createHashMap]
|
["members", createHashMap]
|
||||||
];
|
];
|
||||||
GVAR(Registry) set ["default", _fallbackDefaultOrg];
|
GVAR(Registry) set ["default", _defaultOrg];
|
||||||
|
|
||||||
_fallbackDefaultOrg
|
_defaultOrg
|
||||||
};
|
};
|
||||||
|
|
||||||
private _finalOrg = createHashMap;
|
private _finalOrg = createHashMap;
|
||||||
@ -112,6 +112,109 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
GVAR(Registry) set ["default", _finalOrg];
|
GVAR(Registry) set ["default", _finalOrg];
|
||||||
}],
|
}],
|
||||||
|
["verifyMember", compileFinal {
|
||||||
|
params [["_org", createHashMap, [createHashMap]], ["_orgID", "", [""]], ["_uid", "", [""]], ["_player", objNull, [objNull]], ["_actor", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if (_orgID isEqualTo "" || { _uid isEqualTo "" }) exitWith { _org };
|
||||||
|
|
||||||
|
private _members = _org getOrDefault ["members", createHashMap];
|
||||||
|
if ((_members getOrDefault [_uid, objNull]) isNotEqualTo objNull) exitWith { _org };
|
||||||
|
|
||||||
|
["org:members:add", [_orgID, _uid]] call EFUNC(extension,extCall) params ["_memberResult", "_memberSuccess"];
|
||||||
|
if (!_memberSuccess) then {
|
||||||
|
["WARNING", format ["Failed to add %1 to org %2 members: %3", _uid, _orgID, _memberResult]] call EFUNC(common,log);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _memberName = _actor getOrDefault ["name", ""];
|
||||||
|
if (_memberName isEqualTo "" && { _player isNotEqualTo objNull }) then { _memberName = name _player; };
|
||||||
|
if (_memberName isEqualTo "") then { _memberName = "Unknown"; };
|
||||||
|
|
||||||
|
private _finalMembers = +_members;
|
||||||
|
_finalMembers set [_uid, createHashMapFromArray [["uid", _uid], ["name", _memberName]]];
|
||||||
|
_org set ["members", _finalMembers];
|
||||||
|
|
||||||
|
_org
|
||||||
|
}],
|
||||||
|
["register", compileFinal {
|
||||||
|
params [["_uid", "", [""]], ["_orgName", "", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", ""],
|
||||||
|
["org", createHashMap]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "" || { _orgName isEqualTo "" }) exitWith {
|
||||||
|
_result set ["message", "A valid player and organization name are required."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
||||||
|
private _existingOrgID = _actor getOrDefault ["organization", ""];
|
||||||
|
|
||||||
|
if (_existingOrgID isNotEqualTo "") exitWith {
|
||||||
|
_result set ["message", "Player already belongs to an organization."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _orgID = _actor getOrDefault ["phone_number", ""];
|
||||||
|
if (_orgID isEqualTo "") exitWith {
|
||||||
|
_result set ["message", "Player phone number was not available for organization registration."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
["org:exists", [_orgID]] call EFUNC(extension,extCall) params ["_existsResult", "_existsSuccess"];
|
||||||
|
if (!_existsSuccess) exitWith {
|
||||||
|
_result set ["message", "Unable to verify organization ID availability."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_existsResult isEqualTo "true") exitWith {
|
||||||
|
_result set ["message", "An organization already exists for this phone number."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _org = createHashMapFromArray [
|
||||||
|
["id", _orgID],
|
||||||
|
["owner", _uid],
|
||||||
|
["name", _orgName],
|
||||||
|
["funds", 0],
|
||||||
|
["reputation", 0],
|
||||||
|
["members", createHashMap]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _json = _self call ["toJSON", [_org]];
|
||||||
|
["org:create", [_orgID, _json]] call EFUNC(extension,extCall) params ["_createResult", "_createSuccess"];
|
||||||
|
if (!_createSuccess) exitWith {
|
||||||
|
_result set ["message", format ["Failed to create organization: %1", _createResult]];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_createResult isNotEqualTo "") then {
|
||||||
|
_org = _self call ["toHashMap", [_createResult]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_org set ["members", createHashMap];
|
||||||
|
_org = _self call ["verifyMember", [_org, _orgID, _uid, _player, _actor]];
|
||||||
|
|
||||||
|
["org:members:remove", ["default", _uid]] call EFUNC(extension,extCall);
|
||||||
|
|
||||||
|
private _defaultOrg = GVAR(Registry) getOrDefault ["default", createHashMap];
|
||||||
|
if (_defaultOrg isNotEqualTo createHashMap) then {
|
||||||
|
private _defaultMembers = _defaultOrg getOrDefault ["members", createHashMap];
|
||||||
|
_defaultMembers deleteAt _uid;
|
||||||
|
_defaultOrg set ["members", _defaultMembers];
|
||||||
|
GVAR(Registry) set ["default", _defaultOrg, true];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(IndexRegistry) set [_uid, createHashMapFromArray [["orgID", _orgID]]];
|
||||||
|
GVAR(Registry) set [_orgID, _org, true];
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["org", _org];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
["init", compileFinal {
|
["init", compileFinal {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
@ -121,24 +224,26 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
if (_orgID isEqualTo "") then { _orgID = "default" };
|
if (_orgID isEqualTo "") then { _orgID = "default" };
|
||||||
|
|
||||||
private _cached = GVAR(Registry) getOrDefault [_orgID, nil];
|
private _cached = GVAR(Registry) getOrDefault [_orgID, nil];
|
||||||
if !(isNil { _cached }) exitWith { [CRPC(org,responseInitOrg), [_cached], _player] call CFUNC(targetEvent); _cached };
|
if !(isNil { _cached }) exitWith {
|
||||||
|
private _cachedOwner = _cached getOrDefault ["owner", ""];
|
||||||
|
if (_orgID isEqualTo "default" || { _cachedOwner isEqualTo _uid }) then {
|
||||||
|
_cached = _self call ["verifyMember", [_cached, _orgID, _uid, _player, _actor]];
|
||||||
|
};
|
||||||
|
GVAR(Registry) set [_orgID, _cached, true];
|
||||||
|
[CRPC(org,responseInitOrg), [_cached], _player] call CFUNC(targetEvent);
|
||||||
|
|
||||||
|
_cached
|
||||||
|
};
|
||||||
|
|
||||||
["org:exists", [_orgID]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
["org:exists", [_orgID]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||||
if !(_isSuccess) exitWith {
|
if !(_isSuccess) exitWith {
|
||||||
["ERROR", format ["Failed to check for org %1! Using fallback org.", _orgID]] call EFUNC(common,log);
|
["ERROR", format ["Failed to check for org %1! Using fallback org.", _orgID]] call EFUNC(common,log);
|
||||||
|
|
||||||
private _fallbackOrg = GVAR(Registry) getOrDefault ["default", createHashMapFromArray [
|
private _fallbackOrg = GVAR(Registry) getOrDefault ["default", createHashMap];
|
||||||
["id", "default"],
|
|
||||||
["owner", "server"],
|
|
||||||
["name", "Forge Dynamics"],
|
|
||||||
["funds", 200000],
|
|
||||||
["reputation", 0],
|
|
||||||
["members", createHashMap]
|
|
||||||
]];
|
|
||||||
|
|
||||||
private _entry = createHashMapFromArray [["orgID", _orgID]];
|
private _entry = createHashMapFromArray [["orgID", _orgID]];
|
||||||
GVAR(IndexRegistry) set [_uid, _entry];
|
GVAR(IndexRegistry) set [_uid, _entry];
|
||||||
|
|
||||||
|
if (_orgID isEqualTo "default") then { _fallbackOrg = _self call ["verifyMember", [_fallbackOrg, _orgID, _uid, _player, _actor]]; };
|
||||||
GVAR(Registry) set [_orgID, _fallbackOrg, true];
|
GVAR(Registry) set [_orgID, _fallbackOrg, true];
|
||||||
[CRPC(org,responseInitOrg), [_fallbackOrg], _player] call CFUNC(targetEvent);
|
[CRPC(org,responseInitOrg), [_fallbackOrg], _player] call CFUNC(targetEvent);
|
||||||
|
|
||||||
@ -154,6 +259,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["INFO", format ["Found org for %1", _orgID]] call EFUNC(common,log);
|
["INFO", format ["Found org for %1", _orgID]] call EFUNC(common,log);
|
||||||
} else {
|
} else {
|
||||||
["WARNING", format ["No existing org found for %1, using default org.", _uid]] call EFUNC(common,log);
|
["WARNING", format ["No existing org found for %1, using default org.", _uid]] call EFUNC(common,log);
|
||||||
|
_finalOrg = GVAR(Registry) getOrDefault ["default", createHashMap];
|
||||||
|
_orgID = "default";
|
||||||
};
|
};
|
||||||
|
|
||||||
private _entry = createHashMapFromArray [["orgID", _orgID]];
|
private _entry = createHashMapFromArray [["orgID", _orgID]];
|
||||||
@ -177,6 +284,10 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_finalOrg set ["members", _finalMembers];
|
_finalOrg set ["members", _finalMembers];
|
||||||
// _finalOrg set ["assets", _finalAssets];
|
// _finalOrg set ["assets", _finalAssets];
|
||||||
|
|
||||||
|
private _finalOwner = _finalOrg getOrDefault ["owner", ""];
|
||||||
|
if (_orgID isEqualTo "default" || { _finalOwner isEqualTo _uid }) then {
|
||||||
|
_finalOrg = _self call ["verifyMember", [_finalOrg, _orgID, _uid, _player, _actor]];
|
||||||
|
};
|
||||||
GVAR(Registry) set [_orgID, _finalOrg, true];
|
GVAR(Registry) set [_orgID, _finalOrg, true];
|
||||||
[CRPC(org,responseInitOrg), [_finalOrg], _player] call CFUNC(targetEvent);
|
[CRPC(org,responseInitOrg), [_finalOrg], _player] call CFUNC(targetEvent);
|
||||||
|
|
||||||
|
|||||||
107
arma/ui/apps/components/AppShell.js
Normal file
107
arma/ui/apps/components/AppShell.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
(function () {
|
||||||
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const { h } = RegistryApp.runtime;
|
||||||
|
const store = RegistryApp.store;
|
||||||
|
|
||||||
|
RegistryApp.components = RegistryApp.components || {};
|
||||||
|
|
||||||
|
RegistryApp.components.App = function App() {
|
||||||
|
const Navbar = window.SharedUI.componentFns.Navbar;
|
||||||
|
const Header = window.SharedUI.componentFns.Header;
|
||||||
|
const Footer = window.SharedUI.componentFns.Footer;
|
||||||
|
const HomeView = RegistryApp.componentFns.HomeView;
|
||||||
|
const RegistrationView = RegistryApp.componentFns.RegistrationView;
|
||||||
|
const PortalApp =
|
||||||
|
window.OrgPortal && window.OrgPortal.components
|
||||||
|
? window.OrgPortal.components.App
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const view = store.getView();
|
||||||
|
const viewLabel =
|
||||||
|
view === "create"
|
||||||
|
? "Organization Registration"
|
||||||
|
: view === "portal"
|
||||||
|
? "Organization Portal"
|
||||||
|
: "Entry Hub";
|
||||||
|
const actionLabel = view === "portal" ? "Sign Out" : "Close";
|
||||||
|
const footerSections = [
|
||||||
|
{
|
||||||
|
title: "Registry Resources",
|
||||||
|
items: [
|
||||||
|
"Registration Guidelines",
|
||||||
|
"Tax & Fee Schedule",
|
||||||
|
"Legal Compliance",
|
||||||
|
"Trademark Database",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Bureau Support",
|
||||||
|
items: [
|
||||||
|
"Office: Sector 7 Admin Block",
|
||||||
|
"Hours: 0800 - 1600 (GST)",
|
||||||
|
"Helpdesk: 555-01-REGISTRY",
|
||||||
|
"support@org-bureau.gov",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function closeRegistry() {
|
||||||
|
if (
|
||||||
|
typeof A3API !== "undefined" &&
|
||||||
|
typeof A3API.SendAlert === "function"
|
||||||
|
) {
|
||||||
|
A3API.SendAlert(
|
||||||
|
JSON.stringify({
|
||||||
|
event: "org::close",
|
||||||
|
data: {},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setView("home");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (view === "portal" && PortalApp) {
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
Navbar({
|
||||||
|
title: "Global Organization Network",
|
||||||
|
viewLabel,
|
||||||
|
actionLabel,
|
||||||
|
onAction: closeRegistry,
|
||||||
|
}),
|
||||||
|
PortalApp(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mainContent;
|
||||||
|
if (view === "home") {
|
||||||
|
mainContent = HomeView();
|
||||||
|
} else if (view === "create") {
|
||||||
|
mainContent = RegistrationView();
|
||||||
|
}
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"main",
|
||||||
|
null,
|
||||||
|
Navbar({
|
||||||
|
title: "Global Organization Network",
|
||||||
|
viewLabel,
|
||||||
|
actionLabel,
|
||||||
|
onAction: closeRegistry,
|
||||||
|
}),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "container" },
|
||||||
|
Header({
|
||||||
|
title: "Global Organization Network",
|
||||||
|
onTitleClick: () => store.setView("home"),
|
||||||
|
}),
|
||||||
|
mainContent,
|
||||||
|
),
|
||||||
|
Footer({ sections: footerSections }),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
32
arma/ui/apps/components/footer.js
Normal file
32
arma/ui/apps/components/footer.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedUI = (window.SharedUI = window.SharedUI || {});
|
||||||
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const { h } = RegistryApp.runtime;
|
||||||
|
|
||||||
|
SharedUI.componentFns = SharedUI.componentFns || {};
|
||||||
|
|
||||||
|
SharedUI.componentFns.Footer = function Footer({ sections = [] }) {
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{ className: "footer" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "wrapper" },
|
||||||
|
...sections.map((section) =>
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h("h3", null, section.title),
|
||||||
|
h(
|
||||||
|
"ul",
|
||||||
|
{ style: { listStyleType: "none", padding: 0 } },
|
||||||
|
...(section.items || []).map((item) =>
|
||||||
|
h("li", null, item),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
27
arma/ui/apps/components/header.js
Normal file
27
arma/ui/apps/components/header.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedUI = (window.SharedUI = window.SharedUI || {});
|
||||||
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const { h } = RegistryApp.runtime;
|
||||||
|
|
||||||
|
SharedUI.componentFns = SharedUI.componentFns || {};
|
||||||
|
|
||||||
|
SharedUI.componentFns.Header = function Header({
|
||||||
|
title,
|
||||||
|
subtitle = "Organization Registration & Management Portal",
|
||||||
|
onTitleClick = null,
|
||||||
|
}) {
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{ className: "header" },
|
||||||
|
h(
|
||||||
|
"h1",
|
||||||
|
{
|
||||||
|
style: { cursor: onTitleClick ? "pointer" : "default" },
|
||||||
|
onClick: onTitleClick,
|
||||||
|
},
|
||||||
|
title,
|
||||||
|
),
|
||||||
|
h("p", null, subtitle),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
35
arma/ui/apps/components/hero.js
Normal file
35
arma/ui/apps/components/hero.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedUI = (window.SharedUI = window.SharedUI || {});
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h } = OrgPortal.runtime;
|
||||||
|
|
||||||
|
SharedUI.componentFns = SharedUI.componentFns || {};
|
||||||
|
|
||||||
|
SharedUI.componentFns.Hero = function Hero({
|
||||||
|
className = "",
|
||||||
|
kicker = "",
|
||||||
|
title = "",
|
||||||
|
subtitle = "",
|
||||||
|
meta = "",
|
||||||
|
}) {
|
||||||
|
const finalClassName = [
|
||||||
|
"card org-panel org-span-12 org-page-header",
|
||||||
|
className,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"section",
|
||||||
|
{ className: finalClassName },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-page-heading" },
|
||||||
|
h("span", { className: "org-page-kicker" }, kicker),
|
||||||
|
h("h1", { className: "org-page-title" }, title),
|
||||||
|
h("p", { className: "org-page-subtitle" }, subtitle),
|
||||||
|
h("span", { className: "org-page-meta" }, meta),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
190
arma/ui/apps/components/modal.js
Normal file
190
arma/ui/apps/components/modal.js
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedUI = (window.SharedUI = window.SharedUI || {});
|
||||||
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const { h, ensureScopedStyle } = RegistryApp.runtime;
|
||||||
|
const scopeAttr = "data-ui-modal";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const modalCss = `
|
||||||
|
${scopeSelector} {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgb(15 23 42 / 0.38);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-card {
|
||||||
|
width: min(100%, 30rem);
|
||||||
|
margin-bottom: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-title {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
font-size: 1.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-close {
|
||||||
|
width: 2.25rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
padding: 0;
|
||||||
|
background: var(--bg-surface);
|
||||||
|
color: var(--text-main);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
box-shadow: none;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-close:hover {
|
||||||
|
background: var(--bg-surface-hover);
|
||||||
|
color: var(--text-main);
|
||||||
|
box-shadow: none;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-form label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-form input,
|
||||||
|
${scopeSelector} .app-modal-form select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--bg-app);
|
||||||
|
color: var(--text-main);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-form input:focus,
|
||||||
|
${scopeSelector} .app-modal-form select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 2px rgb(71 85 105 / 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-form input:disabled,
|
||||||
|
${scopeSelector} .app-modal-form select:disabled {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-actions button + button,
|
||||||
|
${scopeSelector} .app-modal-danger-actions button + button {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-danger {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #fff1f2;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-danger p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-modal-danger-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .app-modal-head,
|
||||||
|
${scopeSelector} .app-modal-danger {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
SharedUI.componentFns = SharedUI.componentFns || {};
|
||||||
|
|
||||||
|
SharedUI.componentFns.Modal = function Modal({
|
||||||
|
title = "",
|
||||||
|
body = null,
|
||||||
|
onClose = null,
|
||||||
|
}) {
|
||||||
|
ensureScopedStyle("shared-modal", modalCss);
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className: "app-modal-backdrop",
|
||||||
|
[scopeAttr]: "",
|
||||||
|
onClick: (e) => {
|
||||||
|
if (e.target === e.currentTarget && onClose) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "card app-modal-card" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "app-modal-head" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h("h2", { className: "app-modal-title" }, title),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "app-modal-close",
|
||||||
|
onClick: onClose,
|
||||||
|
"aria-label": "Close dialog",
|
||||||
|
},
|
||||||
|
"x",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
129
arma/ui/apps/components/navbar.js
Normal file
129
arma/ui/apps/components/navbar.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedUI = (window.SharedUI = window.SharedUI || {});
|
||||||
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const { h, ensureScopedStyle } = RegistryApp.runtime;
|
||||||
|
const scopeAttr = "data-ui-navbar";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const navbarCss = `
|
||||||
|
${scopeSelector} {
|
||||||
|
background: var(--bg-surface);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-navbar-inner {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 1200px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-navbar-brand {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-navbar-kicker {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-navbar-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-navbar-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-navbar-view {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-close-btn {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-muted);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-close-btn:hover {
|
||||||
|
background: var(--bg-surface-hover);
|
||||||
|
color: var(--primary-hover);
|
||||||
|
border-color: var(--primary);
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .app-navbar-inner {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .app-navbar-actions {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
SharedUI.componentFns = SharedUI.componentFns || {};
|
||||||
|
|
||||||
|
SharedUI.componentFns.Navbar = function Navbar({
|
||||||
|
kicker = "ORBIS",
|
||||||
|
title = "",
|
||||||
|
viewLabel = "",
|
||||||
|
actionLabel = "",
|
||||||
|
onAction = null,
|
||||||
|
}) {
|
||||||
|
ensureScopedStyle("shared-navbar", navbarCss);
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"nav",
|
||||||
|
{ className: "app-navbar", [scopeAttr]: "" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "app-navbar-inner" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "app-navbar-brand" },
|
||||||
|
h("span", { className: "app-navbar-kicker" }, kicker),
|
||||||
|
h("span", { className: "app-navbar-title" }, title),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "app-navbar-actions" },
|
||||||
|
h("span", { className: "app-navbar-view" }, viewLabel),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "app-close-btn",
|
||||||
|
onClick: onAction,
|
||||||
|
},
|
||||||
|
actionLabel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
83
arma/ui/apps/components/panelCard.js
Normal file
83
arma/ui/apps/components/panelCard.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedUI = (window.SharedUI = window.SharedUI || {});
|
||||||
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const { h, ensureScopedStyle } = RegistryApp.runtime;
|
||||||
|
const scopeAttr = "data-ui-panel-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const panelCardCss = `
|
||||||
|
${scopeSelector} .org-panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-eyebrow {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-panel-title {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
font-size: 1.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-panel-subtitle {
|
||||||
|
margin: 0.35rem 0 0;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-panel-head {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
SharedUI.componentFns = SharedUI.componentFns || {};
|
||||||
|
|
||||||
|
SharedUI.componentFns.PanelCard = function PanelCard({
|
||||||
|
className = "",
|
||||||
|
eyebrow = "",
|
||||||
|
title = "",
|
||||||
|
subtitle = "",
|
||||||
|
headerExtras = null,
|
||||||
|
body = null,
|
||||||
|
rootProps = {},
|
||||||
|
}) {
|
||||||
|
const finalClassName = ["card org-panel", className]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ");
|
||||||
|
ensureScopedStyle("shared-panel-card", panelCardCss);
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"section",
|
||||||
|
{ className: finalClassName, [scopeAttr]: "", ...rootProps },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-panel-head" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
eyebrow
|
||||||
|
? h("div", { className: "org-eyebrow" }, eyebrow)
|
||||||
|
: null,
|
||||||
|
h("h2", { className: "org-panel-title" }, title),
|
||||||
|
subtitle
|
||||||
|
? h("p", { className: "org-panel-subtitle" }, subtitle)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
headerExtras,
|
||||||
|
),
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
79
arma/ui/apps/components/portal/activityCard.js
Normal file
79
arma/ui/apps/components/portal/activityCard.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const { portalData } = OrgPortal.data;
|
||||||
|
const scopeAttr = "data-ui-activity-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const activityCardCss = `
|
||||||
|
${scopeSelector} .org-activity-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
gap: 0.85rem;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #94a3b8 #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-activity-row {
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-left: 3px solid #94a3b8;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-activity-row:nth-child(even) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);
|
||||||
|
border-color: rgb(148 163 184 / 0.45);
|
||||||
|
border-left-color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-activity-row p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-activity-time {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.ActivityCard = function ActivityCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
ensureScopedStyle("portal-activity-card", activityCardCss);
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-scroll-panel org-span-6",
|
||||||
|
title: "Command Feed",
|
||||||
|
subtitle: "Recent organization-level actions and updates.",
|
||||||
|
rootProps: { [scopeAttr]: "" },
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-activity-list" },
|
||||||
|
...portalData.activity.map((item) =>
|
||||||
|
h(
|
||||||
|
"article",
|
||||||
|
{ className: "org-activity-row" },
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "org-activity-time" },
|
||||||
|
item.time,
|
||||||
|
),
|
||||||
|
h("p", null, item.text),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
94
arma/ui/apps/components/portal/assetsCard.js
Normal file
94
arma/ui/apps/components/portal/assetsCard.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const { portalData } = OrgPortal.data;
|
||||||
|
const actions = OrgPortal.actions;
|
||||||
|
const scopeAttr = "data-ui-assets-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const assetsCardCss = `
|
||||||
|
${scopeSelector} .org-simple-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
gap: 0.85rem;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #94a3b8 #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-row:nth-child(even) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);
|
||||||
|
border-color: rgb(148 163 184 / 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-name {
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-simple-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.AssetsCard = function AssetsCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
const SimpleStat = OrgPortal.componentFns.SimpleStat;
|
||||||
|
ensureScopedStyle("portal-assets-card", assetsCardCss);
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-scroll-panel org-span-7",
|
||||||
|
title: "Assets",
|
||||||
|
subtitle: "Inventory supplies and equipment with quantity totals.",
|
||||||
|
rootProps: { [scopeAttr]: "" },
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-simple-list" },
|
||||||
|
...portalData.assets.map((asset) =>
|
||||||
|
h(
|
||||||
|
"article",
|
||||||
|
{ className: "org-simple-row" },
|
||||||
|
h(
|
||||||
|
"strong",
|
||||||
|
{ className: "org-simple-name" },
|
||||||
|
asset.name,
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-simple-meta" },
|
||||||
|
SimpleStat(
|
||||||
|
"Type",
|
||||||
|
actions.formatAssetType(asset.type),
|
||||||
|
),
|
||||||
|
SimpleStat("Quantity", asset.quantity),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
70
arma/ui/apps/components/portal/dangerCard.js
Normal file
70
arma/ui/apps/components/portal/dangerCard.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const permissions = OrgPortal.permissions;
|
||||||
|
const actions = OrgPortal.actions;
|
||||||
|
const scopeAttr = "data-ui-danger-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const dangerCardCss = `
|
||||||
|
${scopeSelector} {
|
||||||
|
border-color: #fecaca;
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #fff7f7 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-danger-copy {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-danger-copy strong,
|
||||||
|
${scopeSelector} .org-danger-copy p {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-danger-copy p {
|
||||||
|
margin: 0.4rem 0 0;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.DangerCard = function DangerCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
ensureScopedStyle("portal-danger-card", dangerCardCss);
|
||||||
|
|
||||||
|
if (!permissions.canDisbandOrg()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-span-12 org-danger-panel",
|
||||||
|
title: "Organization Controls",
|
||||||
|
subtitle:
|
||||||
|
"Leader-only actions for membership and permanent organization removal.",
|
||||||
|
rootProps: { [scopeAttr]: "" },
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-danger-copy" },
|
||||||
|
h("strong", null, "Disband organization"),
|
||||||
|
h(
|
||||||
|
"p",
|
||||||
|
null,
|
||||||
|
"This removes the organization and revokes access to the portal for all members.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-danger-btn",
|
||||||
|
onClick: () => actions.openModal("disband"),
|
||||||
|
},
|
||||||
|
"Disband Organization",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
101
arma/ui/apps/components/portal/fleetCard.js
Normal file
101
arma/ui/apps/components/portal/fleetCard.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const { portalData } = OrgPortal.data;
|
||||||
|
const actions = OrgPortal.actions;
|
||||||
|
const scopeAttr = "data-ui-fleet-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const fleetCardCss = `
|
||||||
|
${scopeSelector} .org-simple-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
gap: 0.85rem;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #94a3b8 #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} {
|
||||||
|
min-height: 32.5rem;
|
||||||
|
max-height: 32.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-row:nth-child(even) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);
|
||||||
|
border-color: rgb(148 163 184 / 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-name {
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-simple-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.FleetCard = function FleetCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
const SimpleStat = OrgPortal.componentFns.SimpleStat;
|
||||||
|
ensureScopedStyle("portal-fleet-card", fleetCardCss);
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-scroll-panel org-span-7",
|
||||||
|
title: "Fleet",
|
||||||
|
subtitle:
|
||||||
|
"Individual vehicles with type, status, and overall damage.",
|
||||||
|
rootProps: { [scopeAttr]: "" },
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-simple-list" },
|
||||||
|
...portalData.fleet.map((unit) =>
|
||||||
|
h(
|
||||||
|
"article",
|
||||||
|
{ className: "org-simple-row" },
|
||||||
|
h(
|
||||||
|
"strong",
|
||||||
|
{ className: "org-simple-name" },
|
||||||
|
unit.name,
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-simple-meta" },
|
||||||
|
SimpleStat(
|
||||||
|
"Type",
|
||||||
|
actions.formatVehicleType(unit.type),
|
||||||
|
),
|
||||||
|
SimpleStat("Status", unit.status),
|
||||||
|
SimpleStat("Damage", unit.damage),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
105
arma/ui/apps/components/portal/futureCard.js
Normal file
105
arma/ui/apps/components/portal/futureCard.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const { portalData } = OrgPortal.data;
|
||||||
|
const scopeAttr = "data-ui-future-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const futureCardCss = `
|
||||||
|
${scopeSelector} .org-roadmap-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #94a3b8 #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-roadmap-card {
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.7rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-roadmap-card:nth-child(4n + 2),
|
||||||
|
${scopeSelector} .org-roadmap-card:nth-child(4n + 3) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);
|
||||||
|
border-color: rgb(100 116 139 / 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-roadmap-card p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-list-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.2rem 0.55rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-roadmap-card:nth-child(4n + 2) .org-list-tag,
|
||||||
|
${scopeSelector} .org-roadmap-card:nth-child(4n + 3) .org-list-tag {
|
||||||
|
background: #cbd5e1;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-roadmap-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-roadmap-card:nth-child(4n + 3) {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-roadmap-card:nth-child(4n + 3) .org-list-tag {
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.FutureCard = function FutureCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
ensureScopedStyle("portal-future-card", futureCardCss);
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-scroll-panel org-span-6",
|
||||||
|
title: "Expansion Slots",
|
||||||
|
subtitle:
|
||||||
|
"Potential modules are tagged by status such as Planned, In Design, In Review, and Future Review.",
|
||||||
|
rootProps: { [scopeAttr]: "" },
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-roadmap-grid" },
|
||||||
|
...portalData.roadmap.map((item) =>
|
||||||
|
h(
|
||||||
|
"article",
|
||||||
|
{ className: "org-roadmap-card" },
|
||||||
|
h("span", { className: "org-list-tag" }, item.status),
|
||||||
|
h("strong", null, item.name),
|
||||||
|
h("p", null, item.detail),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -1,42 +1,83 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
const { h } = OrgPortal.runtime;
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
const store = OrgPortal.store;
|
const store = OrgPortal.store;
|
||||||
const permissions = OrgPortal.permissions;
|
const permissions = OrgPortal.permissions;
|
||||||
const actions = OrgPortal.actions;
|
const actions = OrgPortal.actions;
|
||||||
|
const scopeAttr = "data-ui-members-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const membersCardCss = `
|
||||||
|
${scopeSelector} .org-name-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
gap: 0.85rem;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #94a3b8 #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-row:nth-child(even) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);
|
||||||
|
border-color: rgb(148 163 184 / 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-row button {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-name-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-row button {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
OrgPortal.componentFns.MembersCard = function MembersCard() {
|
OrgPortal.componentFns.MembersCard = function MembersCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
const members = store.getMembers();
|
const members = store.getMembers();
|
||||||
const allowMemberManagement = permissions.canManageMembers();
|
const allowMemberManagement = permissions.canManageMembers();
|
||||||
|
ensureScopedStyle("portal-members-card", membersCardCss);
|
||||||
|
|
||||||
return h(
|
return PanelCard({
|
||||||
"section",
|
className: "org-scroll-panel org-span-5",
|
||||||
{ className: "card org-panel org-scroll-panel org-span-5" },
|
title: "Members",
|
||||||
h(
|
subtitle:
|
||||||
"div",
|
"Current roster listing. The organization owner cannot be removed.",
|
||||||
{ className: "org-panel-head" },
|
rootProps: { [scopeAttr]: "" },
|
||||||
h(
|
body: h(
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h2", { className: "org-panel-title" }, "Members"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
{ className: "org-panel-subtitle" },
|
|
||||||
"Current roster listing with member removal controls.",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-name-list" },
|
{ className: "org-name-list" },
|
||||||
...members.map((member) =>
|
...members.map((member) => {
|
||||||
h(
|
const canRemoveMember =
|
||||||
|
allowMemberManagement &&
|
||||||
|
!actions.isOwnerMember(member.name);
|
||||||
|
|
||||||
|
return h(
|
||||||
"article",
|
"article",
|
||||||
{ className: "org-name-row" },
|
{ className: "org-name-row" },
|
||||||
h("strong", null, member.name),
|
h("strong", null, member.name),
|
||||||
allowMemberManagement
|
canRemoveMember
|
||||||
? h(
|
? h(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
@ -67,9 +108,9 @@
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
);
|
||||||
),
|
}),
|
||||||
),
|
),
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
77
arma/ui/apps/components/portal/metricCard.js
Normal file
77
arma/ui/apps/components/portal/metricCard.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const scopeAttr = "data-ui-metric-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const metricCardCss = `
|
||||||
|
${scopeSelector} {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.45rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector}:nth-child(4n + 2),
|
||||||
|
${scopeSelector}:nth-child(4n + 3) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(226 232 240) 100%);
|
||||||
|
border-color: rgb(100 116 139 / 0.35);
|
||||||
|
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-metric-label {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-metric-value {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector}:nth-child(4n + 2) .org-metric-value,
|
||||||
|
${scopeSelector}:nth-child(4n + 3) .org-metric-value {
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-metric-note {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector}:nth-child(4n + 3) {
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
||||||
|
border-color: var(--border);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector}:nth-child(4n + 3) .org-metric-value {
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.MetricCard = function MetricCard(
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
note,
|
||||||
|
) {
|
||||||
|
ensureScopedStyle("portal-metric-card", metricCardCss);
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-metric-card", [scopeAttr]: "" },
|
||||||
|
h("span", { className: "org-metric-label" }, label),
|
||||||
|
h("strong", { className: "org-metric-value" }, value),
|
||||||
|
h("span", { className: "org-metric-note" }, note),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -8,6 +8,7 @@
|
|||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
OrgPortal.componentFns.ModalLayer = function ModalLayer() {
|
OrgPortal.componentFns.ModalLayer = function ModalLayer() {
|
||||||
|
const Modal = window.SharedUI.componentFns.Modal;
|
||||||
const modal = store.getModal();
|
const modal = store.getModal();
|
||||||
if (!modal) {
|
if (!modal) {
|
||||||
return null;
|
return null;
|
||||||
@ -24,7 +25,7 @@
|
|||||||
title = "Run Payroll";
|
title = "Run Payroll";
|
||||||
body = h(
|
body = h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-modal-form" },
|
{ className: "app-modal-form" },
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
null,
|
null,
|
||||||
@ -39,7 +40,7 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-modal-actions" },
|
{ className: "app-modal-actions" },
|
||||||
h(
|
h(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
@ -75,7 +76,7 @@
|
|||||||
title = "Send Funds";
|
title = "Send Funds";
|
||||||
body = h(
|
body = h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-modal-form" },
|
{ className: "app-modal-form" },
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
null,
|
null,
|
||||||
@ -104,7 +105,7 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-modal-actions" },
|
{ className: "app-modal-actions" },
|
||||||
h(
|
h(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
@ -146,7 +147,7 @@
|
|||||||
title = "Assign Credit Line";
|
title = "Assign Credit Line";
|
||||||
body = h(
|
body = h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-modal-form" },
|
{ className: "app-modal-form" },
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
null,
|
null,
|
||||||
@ -172,7 +173,7 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-modal-actions" },
|
{ className: "app-modal-actions" },
|
||||||
h(
|
h(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
@ -214,7 +215,7 @@
|
|||||||
title = "Disband Organization";
|
title = "Disband Organization";
|
||||||
body = h(
|
body = h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-danger-confirm" },
|
{ className: "app-modal-danger" },
|
||||||
h(
|
h(
|
||||||
"p",
|
"p",
|
||||||
null,
|
null,
|
||||||
@ -224,7 +225,7 @@
|
|||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-danger-actions" },
|
{ className: "app-modal-danger-actions" },
|
||||||
h(
|
h(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
@ -247,40 +248,10 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return h(
|
return Modal({
|
||||||
"div",
|
title,
|
||||||
{
|
body,
|
||||||
className: "org-modal-backdrop",
|
onClose: () => actions.closeModal(),
|
||||||
onClick: (e) => {
|
});
|
||||||
if (e.target === e.currentTarget) {
|
|
||||||
actions.closeModal();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "card org-modal-card" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "org-modal-head" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h2", { className: "org-panel-title" }, title),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
className: "org-modal-close",
|
|
||||||
onClick: () => actions.closeModal(),
|
|
||||||
"aria-label": "Close dialog",
|
|
||||||
},
|
|
||||||
"x",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
@ -1,35 +1,90 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
const { h } = OrgPortal.runtime;
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
const { portalData } = OrgPortal.data;
|
const { portalData } = OrgPortal.data;
|
||||||
const store = OrgPortal.store;
|
const store = OrgPortal.store;
|
||||||
const actions = OrgPortal.actions;
|
const actions = OrgPortal.actions;
|
||||||
|
const scopeAttr = "data-ui-overview-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const overviewCardCss = `
|
||||||
|
${scopeSelector} .org-hero-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.3fr 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-summary {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-meta-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-meta-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-meta-item:nth-child(even) {
|
||||||
|
background: linear-gradient(180deg, rgb(241 245 249) 0%, rgb(226 232 240) 100%);
|
||||||
|
border-color: rgb(148 163 184 / 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-meta-label {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-meta-value {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-metric-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-hero-grid,
|
||||||
|
${scopeSelector} .org-meta-row,
|
||||||
|
${scopeSelector} .org-metric-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
OrgPortal.componentFns.OverviewCard = function OverviewCard() {
|
OrgPortal.componentFns.OverviewCard = function OverviewCard() {
|
||||||
const MetricCard = OrgPortal.componentFns.MetricCard;
|
const MetricCard = OrgPortal.componentFns.MetricCard;
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
const readiness = actions.getAssetReadiness();
|
const readiness = actions.getAssetReadiness();
|
||||||
const headquarters = portalData.org.headquarters || "ArmA Verse";
|
const headquarters = portalData.org.headquarters || "ArmA Verse";
|
||||||
|
ensureScopedStyle("portal-overview-card", overviewCardCss);
|
||||||
|
|
||||||
return h(
|
return PanelCard({
|
||||||
"section",
|
className: "org-span-12",
|
||||||
{ className: "card org-panel org-span-12" },
|
eyebrow: portalData.org.tag,
|
||||||
h(
|
title: "Organization Overview",
|
||||||
"div",
|
rootProps: { [scopeAttr]: "" },
|
||||||
{ className: "org-panel-head" },
|
body: h(
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("div", { className: "org-eyebrow" }, portalData.org.tag),
|
|
||||||
h(
|
|
||||||
"h2",
|
|
||||||
{ className: "org-panel-title" },
|
|
||||||
"Organization Overview",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-hero-grid" },
|
{ className: "org-hero-grid" },
|
||||||
h(
|
h(
|
||||||
@ -115,6 +170,6 @@
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
39
arma/ui/apps/components/portal/simpleStat.js
Normal file
39
arma/ui/apps/components/portal/simpleStat.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
|
const scopeAttr = "data-ui-simple-stat";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const simpleStatCss = `
|
||||||
|
${scopeSelector} {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.2rem;
|
||||||
|
min-width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-label {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-simple-value {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.SimpleStat = function SimpleStat(label, value) {
|
||||||
|
ensureScopedStyle("portal-simple-stat", simpleStatCss);
|
||||||
|
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-simple-stat", [scopeAttr]: "" },
|
||||||
|
h("span", { className: "org-simple-label" }, label),
|
||||||
|
h("strong", { className: "org-simple-value" }, value),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
||||||
430
arma/ui/apps/components/portal/treasuryCard.js
Normal file
430
arma/ui/apps/components/portal/treasuryCard.js
Normal file
@ -0,0 +1,430 @@
|
|||||||
|
(function () {
|
||||||
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
const { h, ensureScopedStyle, createSignal } = OrgPortal.runtime;
|
||||||
|
const { portalData } = OrgPortal.data;
|
||||||
|
const store = OrgPortal.store;
|
||||||
|
const permissions = OrgPortal.permissions;
|
||||||
|
const actions = OrgPortal.actions;
|
||||||
|
const scopeAttr = "data-ui-treasury-card";
|
||||||
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
|
const [getTreasuryTab, setTreasuryTab] = createSignal("overview");
|
||||||
|
const [getTreasuryMenuOpen, setTreasuryMenuOpen] = createSignal(false);
|
||||||
|
const treasuryCardCss = `
|
||||||
|
${scopeSelector} .org-treasury-menu {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-btn {
|
||||||
|
width: 2.75rem;
|
||||||
|
height: 2.75rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: #f8fafc;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-btn:hover {
|
||||||
|
color: var(--primary-hover);
|
||||||
|
border-color: rgb(148 163 184 / 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-btn svg {
|
||||||
|
width: 1.1rem;
|
||||||
|
height: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 0.6rem);
|
||||||
|
right: 0;
|
||||||
|
min-width: 10.5rem;
|
||||||
|
padding: 0.45rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 12px 28px rgb(15 23 42 / 0.12);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-option + .org-menu-option {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-option {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-main);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-option:hover {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: rgb(148 163 184 / 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-menu-option.is-active {
|
||||||
|
background: rgb(226 232 240 / 0.7);
|
||||||
|
color: var(--primary-hover);
|
||||||
|
border-color: rgb(148 163 184 / 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-finance-meta {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-finance-meta > div {
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-meta-label {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-action-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-action-grid button + button {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-action-grid button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-access-note {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-summary {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-summary strong {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-summary span:last-child {
|
||||||
|
font-size: 0.92rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-lines-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-line-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-line-row:nth-child(even) {
|
||||||
|
background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);
|
||||||
|
border-color: rgb(148 163 184 / 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-line-member {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-line-label {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-line-empty {
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: #f8fafc;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-finance-meta {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-credit-line-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
OrgPortal.componentFns = OrgPortal.componentFns || {};
|
||||||
|
|
||||||
|
OrgPortal.componentFns.TreasuryCard = function TreasuryCard() {
|
||||||
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
|
const creditLines = store.getCreditLines();
|
||||||
|
const allowTreasuryActions = permissions.canManageTreasury();
|
||||||
|
const activeTab = getTreasuryTab();
|
||||||
|
const isMenuOpen = getTreasuryMenuOpen();
|
||||||
|
const activeCreditLabel =
|
||||||
|
creditLines.length === 1
|
||||||
|
? "1 active credit line"
|
||||||
|
: `${creditLines.length} active credit lines`;
|
||||||
|
ensureScopedStyle("portal-treasury-card", treasuryCardCss);
|
||||||
|
|
||||||
|
return PanelCard({
|
||||||
|
className: "org-span-5",
|
||||||
|
title: "Treasury",
|
||||||
|
subtitle: "Organization funds, reputation, and member payouts.",
|
||||||
|
headerExtras: h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-treasury-menu" },
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-menu-btn",
|
||||||
|
title: "Treasury views",
|
||||||
|
"aria-label": "Treasury views",
|
||||||
|
onClick: () => setTreasuryMenuOpen((open) => !open),
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "currentColor",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
"aria-hidden": "true",
|
||||||
|
},
|
||||||
|
h("line", { x1: "4", y1: "7", x2: "20", y2: "7" }),
|
||||||
|
h("line", { x1: "4", y1: "12", x2: "20", y2: "12" }),
|
||||||
|
h("line", { x1: "4", y1: "17", x2: "20", y2: "17" }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
isMenuOpen
|
||||||
|
? h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-menu-dropdown" },
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className:
|
||||||
|
activeTab === "overview"
|
||||||
|
? "org-menu-option is-active"
|
||||||
|
: "org-menu-option",
|
||||||
|
onClick: () => {
|
||||||
|
setTreasuryTab("overview");
|
||||||
|
setTreasuryMenuOpen(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Overview",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className:
|
||||||
|
activeTab === "credit"
|
||||||
|
? "org-menu-option is-active"
|
||||||
|
: "org-menu-option",
|
||||||
|
onClick: () => {
|
||||||
|
setTreasuryTab("credit");
|
||||||
|
setTreasuryMenuOpen(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Credit Lines",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
rootProps: { [scopeAttr]: "" },
|
||||||
|
body: h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
activeTab === "credit"
|
||||||
|
? creditLines.length > 0
|
||||||
|
? h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-credit-lines-list" },
|
||||||
|
...creditLines.map((line) =>
|
||||||
|
h(
|
||||||
|
"article",
|
||||||
|
{ className: "org-credit-line-row" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-credit-line-member",
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-credit-line-label",
|
||||||
|
},
|
||||||
|
"Member",
|
||||||
|
),
|
||||||
|
h("strong", null, line.member),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-credit-line-member",
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-credit-line-label",
|
||||||
|
},
|
||||||
|
"Amount",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"strong",
|
||||||
|
null,
|
||||||
|
actions.formatCurrency(
|
||||||
|
line.amount,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-credit-line-empty" },
|
||||||
|
"No active credit lines.",
|
||||||
|
)
|
||||||
|
: h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-finance-meta" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "org-meta-label" },
|
||||||
|
"Funds",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"strong",
|
||||||
|
null,
|
||||||
|
actions.formatCurrency(store.getFunds()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "org-meta-label" },
|
||||||
|
"Reputation",
|
||||||
|
),
|
||||||
|
h("strong", null, `${portalData.reputation}`),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
allowTreasuryActions
|
||||||
|
? h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-action-grid" },
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
onClick: () =>
|
||||||
|
actions.openModal("payroll"),
|
||||||
|
},
|
||||||
|
"Run Payroll",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-secondary-btn",
|
||||||
|
onClick: () =>
|
||||||
|
actions.openModal("transfer"),
|
||||||
|
},
|
||||||
|
"Send Funds",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-secondary-btn",
|
||||||
|
onClick: () =>
|
||||||
|
actions.openModal("credit"),
|
||||||
|
},
|
||||||
|
"Credit Line",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: h(
|
||||||
|
"p",
|
||||||
|
{ className: "org-access-note" },
|
||||||
|
"Only the organization leader or CEO can manage treasury actions.",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-credit-summary" },
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "org-meta-label" },
|
||||||
|
"Credit Line Status",
|
||||||
|
),
|
||||||
|
h("strong", null, activeCreditLabel),
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
null,
|
||||||
|
creditLines.length > 0
|
||||||
|
? "Open the Credit Lines tab to review assigned members and amounts."
|
||||||
|
: "Assign a credit line to create the first approved member limit.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
318
arma/ui/apps/logic/portalActions.js
Normal file
318
arma/ui/apps/logic/portalActions.js
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedLogic = (window.SharedLogic = window.SharedLogic || {});
|
||||||
|
|
||||||
|
SharedLogic.createPortalActions = function createPortalActions({
|
||||||
|
portalData,
|
||||||
|
store,
|
||||||
|
permissions,
|
||||||
|
registryStore,
|
||||||
|
}) {
|
||||||
|
class OrgPortalActions {
|
||||||
|
constructor() {
|
||||||
|
this.treasuryNoticeTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatCurrency(value) {
|
||||||
|
return "$" + value.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
formatVehicleType(type) {
|
||||||
|
if (!type) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.charAt(0).toUpperCase() + type.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatAssetType(type) {
|
||||||
|
if (!type) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.charAt(0).toUpperCase() + type.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDisplayName(value) {
|
||||||
|
if (!value) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value)
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/)
|
||||||
|
.map((part) => {
|
||||||
|
if (!part) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
part.charAt(0).toUpperCase() +
|
||||||
|
part.slice(1).toLowerCase()
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
getAssetReadiness() {
|
||||||
|
if (portalData.fleet.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = portalData.fleet.reduce(
|
||||||
|
(sum, unit) => sum + (100 - parseInt(unit.damage, 10)),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
return Math.round(total / portalData.fleet.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
showTreasuryNotice(type, text) {
|
||||||
|
store.setTreasuryNotice({ type, text });
|
||||||
|
|
||||||
|
if (this.treasuryNoticeTimer) {
|
||||||
|
clearTimeout(this.treasuryNoticeTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.treasuryNoticeTimer = setTimeout(() => {
|
||||||
|
store.setTreasuryNotice({ type: "", text: "" });
|
||||||
|
this.treasuryNoticeTimer = null;
|
||||||
|
}, 3500);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAmount(value) {
|
||||||
|
const amount = Number(value);
|
||||||
|
return Number.isFinite(amount) ? Math.round(amount) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputValue(id) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
return el ? el.value : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
isOwnerMember(memberName) {
|
||||||
|
return (
|
||||||
|
String(memberName || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase() ===
|
||||||
|
String(portalData.org.owner || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
closePortal() {
|
||||||
|
if (
|
||||||
|
typeof A3API !== "undefined" &&
|
||||||
|
typeof A3API.SendAlert === "function"
|
||||||
|
) {
|
||||||
|
A3API.SendAlert(
|
||||||
|
JSON.stringify({
|
||||||
|
event: "org::close",
|
||||||
|
data: {},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registryStore) {
|
||||||
|
registryStore.setView("home");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openModal(type) {
|
||||||
|
if (
|
||||||
|
(type === "payroll" ||
|
||||||
|
type === "transfer" ||
|
||||||
|
type === "credit") &&
|
||||||
|
!permissions.canManageTreasury()
|
||||||
|
) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Only the organization leader or CEO can manage treasury actions.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "disband" && !permissions.canDisbandOrg()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setModal({ type });
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal() {
|
||||||
|
store.setModal(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMember(memberName) {
|
||||||
|
if (!permissions.canManageMembers()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isOwnerMember(memberName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setMembers((currentMembers) =>
|
||||||
|
currentMembers.filter(
|
||||||
|
(member) => member.name !== memberName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
store.setCreditLines((currentLines) =>
|
||||||
|
currentLines.filter((line) => line.member !== memberName),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
disbandOrganization() {
|
||||||
|
if (!permissions.canDisbandOrg()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setOrgDisbanded(true);
|
||||||
|
this.closeModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
runPayroll(amountPerMember) {
|
||||||
|
if (!permissions.canManageTreasury()) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Only the organization leader or CEO can manage treasury actions.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const members = store.getMembers();
|
||||||
|
const funds = store.getFunds();
|
||||||
|
|
||||||
|
if (members.length === 0) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"No members available for payroll.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amountPerMember <= 0) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Enter a valid payroll amount.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = amountPerMember * members.length;
|
||||||
|
if (total > funds) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Insufficient org funds for payroll.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setFunds(funds - total);
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"success",
|
||||||
|
`Payroll sent to ${members.length} members for ${this.formatCurrency(total)}.`,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFundsToMember(memberName, amount) {
|
||||||
|
if (!permissions.canManageTreasury()) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Only the organization leader or CEO can manage treasury actions.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const funds = store.getFunds();
|
||||||
|
|
||||||
|
if (!memberName) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Select a member to receive funds.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Enter a valid transfer amount.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount > funds) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Insufficient org funds for this transfer.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setFunds(funds - amount);
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"success",
|
||||||
|
`${this.formatCurrency(amount)} sent to ${memberName}.`,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
grantCreditLine(memberName, amount) {
|
||||||
|
if (!permissions.canManageTreasury()) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Only the organization leader or CEO can manage treasury actions.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!memberName) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Select a member for the credit line.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Enter a valid credit line amount.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setCreditLines((currentLines) => {
|
||||||
|
const existingIndex = currentLines.findIndex(
|
||||||
|
(line) => line.member === memberName,
|
||||||
|
);
|
||||||
|
if (existingIndex === -1) {
|
||||||
|
return [
|
||||||
|
...currentLines,
|
||||||
|
{ member: memberName, amount },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedLines = [...currentLines];
|
||||||
|
updatedLines[existingIndex] = {
|
||||||
|
member: memberName,
|
||||||
|
amount,
|
||||||
|
};
|
||||||
|
return updatedLines;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"success",
|
||||||
|
`Credit line of ${this.formatCurrency(amount)} assigned to ${memberName}.`,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OrgPortalActions();
|
||||||
|
};
|
||||||
|
})();
|
||||||
75
arma/ui/apps/logic/portalPermissions.js
Normal file
75
arma/ui/apps/logic/portalPermissions.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedLogic = (window.SharedLogic = window.SharedLogic || {});
|
||||||
|
|
||||||
|
SharedLogic.createPortalPermissions = function createPortalPermissions({
|
||||||
|
portalData,
|
||||||
|
session,
|
||||||
|
}) {
|
||||||
|
class OrgPortalPermissions {
|
||||||
|
getNormalizedRole() {
|
||||||
|
return String(session.role || "")
|
||||||
|
.trim()
|
||||||
|
.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
isDefaultOrg() {
|
||||||
|
return (
|
||||||
|
portalData.org.isDefault === true ||
|
||||||
|
String(portalData.org.tag || "")
|
||||||
|
.trim()
|
||||||
|
.toUpperCase() === "DEFAULT"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isOrgOwner() {
|
||||||
|
const ownerUid = String(
|
||||||
|
portalData.org.ownerUid || portalData.org.owner || "",
|
||||||
|
)
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const actorUid = String(session.actorUid || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
if (ownerUid && actorUid) {
|
||||||
|
return actorUid === ownerUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
String(session.actorName || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase() ===
|
||||||
|
String(portalData.org.owner || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSessionCeo() {
|
||||||
|
return session.ceo === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isOrgLeaderOrCeo() {
|
||||||
|
return (
|
||||||
|
this.isOrgOwner() ||
|
||||||
|
this.getNormalizedRole() === "LEADER" ||
|
||||||
|
(this.isDefaultOrg() && this.isSessionCeo())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
canManageMembers() {
|
||||||
|
return this.isOrgLeaderOrCeo();
|
||||||
|
}
|
||||||
|
|
||||||
|
canManageTreasury() {
|
||||||
|
return this.isOrgLeaderOrCeo();
|
||||||
|
}
|
||||||
|
|
||||||
|
canDisbandOrg() {
|
||||||
|
return this.isOrgLeaderOrCeo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OrgPortalPermissions();
|
||||||
|
};
|
||||||
|
})();
|
||||||
38
arma/ui/apps/logic/portalStore.js
Normal file
38
arma/ui/apps/logic/portalStore.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedLogic = (window.SharedLogic = window.SharedLogic || {});
|
||||||
|
|
||||||
|
SharedLogic.createPortalStore = function createPortalStore({
|
||||||
|
createSignal,
|
||||||
|
portalData,
|
||||||
|
}) {
|
||||||
|
class OrgPortalStore {
|
||||||
|
constructor() {
|
||||||
|
[this.getFunds, this.setFunds] = createSignal(portalData.funds);
|
||||||
|
[this.getMembers, this.setMembers] = createSignal([
|
||||||
|
...portalData.members,
|
||||||
|
]);
|
||||||
|
[this.getCreditLines, this.setCreditLines] = createSignal([]);
|
||||||
|
[this.getTreasuryNotice, this.setTreasuryNotice] = createSignal(
|
||||||
|
{
|
||||||
|
type: "",
|
||||||
|
text: "",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
[this.getModal, this.setModal] = createSignal(null);
|
||||||
|
[this.getOrgDisbanded, this.setOrgDisbanded] =
|
||||||
|
createSignal(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
hydrateFromPayload(payload) {
|
||||||
|
this.setFunds(payload.portalData.funds || 0);
|
||||||
|
this.setMembers([...(payload.portalData.members || [])]);
|
||||||
|
this.setCreditLines([]);
|
||||||
|
this.setTreasuryNotice({ type: "", text: "" });
|
||||||
|
this.setModal(null);
|
||||||
|
this.setOrgDisbanded(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OrgPortalStore();
|
||||||
|
};
|
||||||
|
})();
|
||||||
69
arma/ui/apps/logic/registryStore.js
Normal file
69
arma/ui/apps/logic/registryStore.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
(function () {
|
||||||
|
const SharedLogic = (window.SharedLogic = window.SharedLogic || {});
|
||||||
|
|
||||||
|
SharedLogic.createRegistryStore = function createRegistryStore({
|
||||||
|
createSignal,
|
||||||
|
onHydratePortal,
|
||||||
|
}) {
|
||||||
|
class RegistryStore {
|
||||||
|
constructor() {
|
||||||
|
[this.getView, this.setView] = createSignal("home");
|
||||||
|
[this.getIsAuthenticating, this.setIsAuthenticating] =
|
||||||
|
createSignal(false);
|
||||||
|
[this.getLoginError, this.setLoginError] = createSignal("");
|
||||||
|
[this.getIsCreating, this.setIsCreating] = createSignal(false);
|
||||||
|
[this.getCreateError, this.setCreateError] = createSignal("");
|
||||||
|
}
|
||||||
|
|
||||||
|
startLogin() {
|
||||||
|
this.setLoginError("");
|
||||||
|
this.setIsAuthenticating(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
startCreate() {
|
||||||
|
this.setCreateError("");
|
||||||
|
this.setIsCreating(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
failLogin(message) {
|
||||||
|
this.setIsAuthenticating(false);
|
||||||
|
this.setLoginError(message || "Authentication failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
failCreate(message) {
|
||||||
|
this.setIsCreating(false);
|
||||||
|
this.setCreateError(
|
||||||
|
message || "Organization registration failed.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
hydratePortal(payload) {
|
||||||
|
return Boolean(onHydratePortal && onHydratePortal(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
completeLogin(payload) {
|
||||||
|
if (!this.hydratePortal(payload)) {
|
||||||
|
this.failLogin("Login response was missing portal data.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setLoginError("");
|
||||||
|
this.setIsAuthenticating(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
completeCreate(payload) {
|
||||||
|
if (!this.hydratePortal(payload)) {
|
||||||
|
this.failCreate(
|
||||||
|
"Organization registration response was missing portal data.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setCreateError("");
|
||||||
|
this.setIsCreating(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RegistryStore();
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -36,12 +36,39 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
if (!credentials.email || !credentials.password) {
|
store.completeLogin(getMockPayload());
|
||||||
store.failLogin("Enter both email and password.");
|
}, 350);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestCreateOrg(registration) {
|
||||||
|
store.startCreate();
|
||||||
|
|
||||||
|
const sent = sendEvent("org::create::request", registration);
|
||||||
|
if (sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
const orgName = String(registration.orgName || "").trim();
|
||||||
|
if (!orgName) {
|
||||||
|
store.failCreate("Enter an organization name.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.completeLogin(getMockPayload());
|
const payload = getMockPayload();
|
||||||
|
payload.portalData.org.name = orgName;
|
||||||
|
payload.portalData.org.tag = String(Date.now()).slice(-10);
|
||||||
|
payload.portalData.org.owner =
|
||||||
|
payload.session.actorName || "Unknown";
|
||||||
|
payload.portalData.org.ownerUid = payload.session.actorUid || "";
|
||||||
|
payload.portalData.org.isDefault = false;
|
||||||
|
payload.session.role = "Leader";
|
||||||
|
payload.session.ceo = false;
|
||||||
|
payload.portalData.members = [
|
||||||
|
{ name: payload.session.actorName || "Unknown" },
|
||||||
|
];
|
||||||
|
|
||||||
|
store.completeCreate(payload);
|
||||||
}, 350);
|
}, 350);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,18 +91,33 @@
|
|||||||
store.failLogin(payloadData.message || "Authentication failed.");
|
store.failLogin(payloadData.message || "Authentication failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event === "org::create::success") {
|
||||||
|
store.completeCreate(payloadData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event === "org::create::failure") {
|
||||||
|
store.failCreate(
|
||||||
|
payloadData.message || "Organization registration failed.",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RegistryApp.bridge = {
|
RegistryApp.bridge = {
|
||||||
requestLogin,
|
requestLogin,
|
||||||
|
requestCreateOrg,
|
||||||
receive,
|
receive,
|
||||||
sendEvent,
|
sendEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
window.OrgUIBridge = {
|
window.OrgUIBridge = {
|
||||||
requestLogin,
|
requestLogin,
|
||||||
|
requestCreateOrg,
|
||||||
receive,
|
receive,
|
||||||
receiveLoginSuccess: (data) => receive("org::login::success", data),
|
receiveLoginSuccess: (data) => receive("org::login::success", data),
|
||||||
receiveLoginFailure: (data) => receive("org::login::failure", data),
|
receiveLoginFailure: (data) => receive("org::login::failure", data),
|
||||||
|
receiveCreateSuccess: (data) => receive("org::create::success", data),
|
||||||
|
receiveCreateFailure: (data) => receive("org::create::failure", data),
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -1,43 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
|
||||||
const { h } = RegistryApp.runtime;
|
|
||||||
|
|
||||||
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
|
||||||
|
|
||||||
RegistryApp.componentFns.Footer = function Footer() {
|
|
||||||
return h(
|
|
||||||
"div",
|
|
||||||
{ className: "footer" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "wrapper" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h3", null, "Registry Resources"),
|
|
||||||
h(
|
|
||||||
"ul",
|
|
||||||
{ style: { listStyleType: "none", padding: 0 } },
|
|
||||||
h("li", null, "Registration Guidelines"),
|
|
||||||
h("li", null, "Tax & Fee Schedule"),
|
|
||||||
h("li", null, "Legal Compliance"),
|
|
||||||
h("li", null, "Trademark Database"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
null,
|
|
||||||
h("h3", null, "Bureau Support"),
|
|
||||||
h(
|
|
||||||
"ul",
|
|
||||||
{ style: { listStyleType: "none", padding: 0 } },
|
|
||||||
h("li", null, "Office: Sector 7 Admin Block"),
|
|
||||||
h("li", null, "Hours: 0800 - 1600 (GST)"),
|
|
||||||
h("li", null, "Helpdesk: 555-01-REGISTRY"),
|
|
||||||
h("li", null, "support@org-bureau.gov"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
.split-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 2rem;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-panel {
|
|
||||||
text-align: left;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.75rem;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: var(--bg-app);
|
|
||||||
color: var(--text-main);
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 1rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
transition: border-color 0.2s;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--primary);
|
|
||||||
box-shadow: 0 0 0 2px rgb(59 130 246 / 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-actions {
|
|
||||||
margin-top: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-link {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-feedback {
|
|
||||||
padding: 0.85rem 1rem;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
font-size: 0.92rem;
|
|
||||||
|
|
||||||
&.is-error {
|
|
||||||
background: #fef2f2;
|
|
||||||
border: 1px solid #fecaca;
|
|
||||||
color: #991b1b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
|
||||||
.split-container {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
|
||||||
const { h } = RegistryApp.runtime;
|
|
||||||
const store = RegistryApp.store;
|
|
||||||
|
|
||||||
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
|
||||||
|
|
||||||
RegistryApp.componentFns.Header = function Header({ title }) {
|
|
||||||
return h(
|
|
||||||
"div",
|
|
||||||
{ className: "header" },
|
|
||||||
h(
|
|
||||||
"h1",
|
|
||||||
{
|
|
||||||
style: { cursor: "pointer" },
|
|
||||||
onClick: () => store.setView("home"),
|
|
||||||
},
|
|
||||||
title,
|
|
||||||
),
|
|
||||||
h("p", null, "Organization Registration & Management Portal"),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
.content {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 2rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
|
||||||
.content {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
|
||||||
const { h } = RegistryApp.runtime;
|
|
||||||
const store = RegistryApp.store;
|
|
||||||
|
|
||||||
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
|
||||||
|
|
||||||
RegistryApp.componentFns.HomeView = function HomeView() {
|
|
||||||
return h(
|
|
||||||
"div",
|
|
||||||
{ className: "content" },
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "card" },
|
|
||||||
h("h2", null, "Create Organization"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
null,
|
|
||||||
"Establish your Task Force, PMC, or Milsim unit with the Global Organization Network. Receive your official unit designator and TO&E authorization instantly.",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{ onClick: () => store.setView("create") },
|
|
||||||
"Register",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "card" },
|
|
||||||
h("h2", null, "Organization Portal"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
null,
|
|
||||||
"Access your unit dashboard to modify rosters, adjust active deployments, and submit after-action reports through the secure field uplink.",
|
|
||||||
),
|
|
||||||
h("button", { onClick: () => store.setView("login") }, "Login"),
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "card", style: { gridColumn: "span 2" } },
|
|
||||||
h("h2", null, "Organization Portal Preview"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
null,
|
|
||||||
"Review the refactor direction for a player organization portal with fleet, assets, treasury, reputation, roster management, and reserved space for future modules.",
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
"button",
|
|
||||||
{
|
|
||||||
onClick: () => store.setView("portal"),
|
|
||||||
},
|
|
||||||
"Open Portal Preview",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user