Align org UI with live org data
This commit is contained in:
parent
964e839625
commit
cdfc8dda80
@ -1,4 +1,86 @@
|
|||||||
forge_client_org
|
forge_client_org
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Description for this addon
|
Player organization UI and client integration.
|
||||||
|
|
||||||
|
UI Login Contract
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The web UI sends the following request through `A3API.SendAlert`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event": "org::login::request",
|
||||||
|
"data": {
|
||||||
|
"email": "admin@spearnet.mil",
|
||||||
|
"password": "secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
On success, SQF should call the browser bridge with:
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _payload = createHashMapFromArray [
|
||||||
|
["session", createHashMapFromArray [
|
||||||
|
["actorName", name player],
|
||||||
|
["role", "Leader"]
|
||||||
|
]],
|
||||||
|
["portalData", createHashMapFromArray [
|
||||||
|
["org", createHashMapFromArray [
|
||||||
|
["name", "Black Rifle Company"],
|
||||||
|
["tag", "BRC-0160566824"],
|
||||||
|
["type", "Private Military Company"],
|
||||||
|
["status", "Operational"],
|
||||||
|
["headquarters", "Georgetown Command Annex"],
|
||||||
|
["owner", "Jacob Schmidt"]
|
||||||
|
]],
|
||||||
|
["funds", 482750],
|
||||||
|
["reputation", 72],
|
||||||
|
["members", [
|
||||||
|
createHashMapFromArray [["name", "Jacob Schmidt"]],
|
||||||
|
createHashMapFromArray [["name", "Mara Velez"]]
|
||||||
|
]],
|
||||||
|
["fleet", [
|
||||||
|
createHashMapFromArray [
|
||||||
|
["name", "UH-80 Ghost Hawk"],
|
||||||
|
["type", "helicopter"],
|
||||||
|
["status", "Ready"],
|
||||||
|
["damage", "16%"]
|
||||||
|
]
|
||||||
|
]],
|
||||||
|
["assets", [
|
||||||
|
createHashMapFromArray [
|
||||||
|
["name", "First Aid Kits"],
|
||||||
|
["type", "items"],
|
||||||
|
["quantity", "36"]
|
||||||
|
]
|
||||||
|
]],
|
||||||
|
["activity", []],
|
||||||
|
["roadmap", []]
|
||||||
|
]]
|
||||||
|
];
|
||||||
|
|
||||||
|
_control ctrlWebBrowserAction [
|
||||||
|
"ExecJS",
|
||||||
|
format ["OrgUIBridge.receiveLoginSuccess(%1)", toJSON _payload]
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
On failure:
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _payload = createHashMapFromArray [
|
||||||
|
["message", "Invalid credentials."]
|
||||||
|
];
|
||||||
|
|
||||||
|
_control ctrlWebBrowserAction [
|
||||||
|
"ExecJS",
|
||||||
|
format ["OrgUIBridge.receiveLoginFailure(%1)", toJSON _payload]
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Current implementation:
|
||||||
|
- `fnc_handleUIEvents.sqf` now handles `org::login::request`
|
||||||
|
- success hydrates the portal with `session` + `portalData`
|
||||||
|
- failure returns a single `message` string for inline UI feedback
|
||||||
|
|||||||
@ -23,81 +23,174 @@ private _event = _alert get "event";
|
|||||||
private _data = _alert get "data";
|
private _data = _alert get "data";
|
||||||
// private _display = displayChild findDisplay 46;
|
// private _display = displayChild findDisplay 46;
|
||||||
|
|
||||||
|
private _fnc_execBridge = {
|
||||||
|
params ["_control", "_fnName", "_payload"];
|
||||||
|
|
||||||
|
private _json = toJSON _payload;
|
||||||
|
_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::ready": {
|
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 _name = _orgData getOrDefault ["name", "Unknown"];
|
private _orgId = _orgData getOrDefault ["id", ""];
|
||||||
private _id = _orgData getOrDefault ["id", ""];
|
private _orgName = _orgData getOrDefault ["name", ""];
|
||||||
private _funds = _orgData getOrDefault ["funds", 0];
|
|
||||||
private _reputation = _orgData getOrDefault ["reputation", 0];
|
|
||||||
private _membersRaw = _orgData getOrDefault ["members", []];
|
|
||||||
private _membersList = [];
|
|
||||||
|
|
||||||
// Handle members
|
if (_email isEqualTo "" || {_password isEqualTo ""}) exitWith {
|
||||||
private _fnc_processMember = {
|
[_control, "receiveLoginFailure", createHashMapFromArray [
|
||||||
params ["_mData"];
|
["message", "Enter both email and password."]
|
||||||
|
]] call _fnc_execBridge;
|
||||||
private _mName = _mData getOrDefault ["name", "Unknown"];
|
|
||||||
createHashMapFromArray [
|
|
||||||
["name", _mName],
|
|
||||||
["rank", "Member"],
|
|
||||||
["online", true]
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
if (_orgId isEqualTo "" && {_orgName isEqualTo ""}) exitWith {
|
||||||
_membersList pushBack ([_y] call _fnc_processMember);
|
[_control, "receiveLoginFailure", createHashMapFromArray [
|
||||||
} forEach _membersRaw;
|
["message", "No organization data is available for this player."]
|
||||||
|
]] call _fnc_execBridge;
|
||||||
private _totalMembers = count _membersList;
|
|
||||||
|
|
||||||
// Handle assets
|
|
||||||
private _assetsRaw = _orgData getOrDefault ["assets", createHashMap];
|
|
||||||
private _assetsList = [];
|
|
||||||
|
|
||||||
private _fnc_processAsset = {
|
|
||||||
params ["_aData"];
|
|
||||||
|
|
||||||
private _aName = _aData getOrDefault ["name", "Unknown Asset"];
|
|
||||||
private _aLocation = _aData getOrDefault ["location", "Unknown Location"];
|
|
||||||
private _aIcon = _aData getOrDefault ["icon", "📦"];
|
|
||||||
|
|
||||||
createHashMapFromArray [
|
|
||||||
["name", _aName],
|
|
||||||
["location", _aLocation],
|
|
||||||
["icon", _aIcon]
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
[_control, "receiveLoginSuccess", call _fnc_buildPortalPayload] call _fnc_execBridge;
|
||||||
_assetsList pushBack ([_y] call _fnc_processAsset);
|
};
|
||||||
} forEach _assetsRaw;
|
case "org::ready": {
|
||||||
|
[_control, "receive", createHashMapFromArray [
|
||||||
// Construct HashMap payload
|
["event", "org::ready"],
|
||||||
private _payload = createHashMapFromArray [
|
["data", createHashMapFromArray [
|
||||||
["org", createHashMapFromArray [
|
["loaded", true]
|
||||||
["name", _name],
|
]]
|
||||||
["tag", _id],
|
]] call _fnc_execBridge;
|
||||||
["status", "Active"]
|
|
||||||
]],
|
|
||||||
["stats", createHashMapFromArray [
|
|
||||||
["totalMembers", _totalMembers],
|
|
||||||
["onlineMembers", _totalMembers],
|
|
||||||
["balance", _funds],
|
|
||||||
["reputation", _reputation]
|
|
||||||
]],
|
|
||||||
["membersOnline", _membersList],
|
|
||||||
["assets", _assetsList],
|
|
||||||
["activities", []],
|
|
||||||
["missions", []]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _json = toJSON _payload;
|
|
||||||
|
|
||||||
_control ctrlWebBrowserAction ["ExecJS", format ["updateOrgDashboard(%1)", _json]];
|
|
||||||
};
|
};
|
||||||
default { hint format ["Unhandled UI event: %1", _event]; };
|
default { hint format ["Unhandled UI event: %1", _event]; };
|
||||||
};
|
};
|
||||||
|
|||||||
66
arma/client/addons/org/ui/_site/bridge.js
Normal file
66
arma/client/addons/org/ui/_site/bridge.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
(function () {
|
||||||
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const store = RegistryApp.store;
|
||||||
|
|
||||||
|
function sendEvent(event, data) {
|
||||||
|
if (
|
||||||
|
typeof A3API !== "undefined" &&
|
||||||
|
typeof A3API.SendAlert === "function"
|
||||||
|
) {
|
||||||
|
A3API.SendAlert(
|
||||||
|
JSON.stringify({
|
||||||
|
event,
|
||||||
|
data,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestLogin(credentials) {
|
||||||
|
store.startLogin();
|
||||||
|
|
||||||
|
const sent = sendEvent("org::login::request", credentials);
|
||||||
|
if (sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.failLogin("Arma login bridge is unavailable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function receive(eventOrPayload, data = {}) {
|
||||||
|
const event =
|
||||||
|
typeof eventOrPayload === "object" && eventOrPayload !== null
|
||||||
|
? eventOrPayload.event
|
||||||
|
: eventOrPayload;
|
||||||
|
const payloadData =
|
||||||
|
typeof eventOrPayload === "object" && eventOrPayload !== null
|
||||||
|
? eventOrPayload.data || {}
|
||||||
|
: data;
|
||||||
|
|
||||||
|
if (event === "org::login::success") {
|
||||||
|
store.completeLogin(payloadData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event === "org::login::failure") {
|
||||||
|
store.failLogin(payloadData.message || "Authentication failed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RegistryApp.bridge = {
|
||||||
|
requestLogin,
|
||||||
|
receive,
|
||||||
|
sendEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
window.OrgUIBridge = {
|
||||||
|
requestLogin,
|
||||||
|
receive,
|
||||||
|
receiveLoginSuccess: (data) => receive("org::login::success", data),
|
||||||
|
receiveLoginFailure: (data) => receive("org::login::failure", data),
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -42,23 +42,119 @@
|
|||||||
},
|
},
|
||||||
h(
|
h(
|
||||||
"li",
|
"li",
|
||||||
{ style: { marginBottom: "0.5rem" } },
|
{
|
||||||
"✅ Official Organization Designator",
|
style: {
|
||||||
|
marginBottom: "0.5rem",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.5rem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "#10b981",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
style: {
|
||||||
|
width: "1.2rem",
|
||||||
|
height: "1.2rem",
|
||||||
|
flexShrink: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h("path", { d: "M20 6L9 17l-5-5" }),
|
||||||
|
),
|
||||||
|
"Official Organization Designator",
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"li",
|
"li",
|
||||||
{ style: { marginBottom: "0.5rem" } },
|
{
|
||||||
"✅ Secure Comms Channel",
|
style: {
|
||||||
|
marginBottom: "0.5rem",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.5rem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "#10b981",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
style: {
|
||||||
|
width: "1.2rem",
|
||||||
|
height: "1.2rem",
|
||||||
|
flexShrink: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h("path", { d: "M20 6L9 17l-5-5" }),
|
||||||
|
),
|
||||||
|
"Secure Comms Channel",
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"li",
|
"li",
|
||||||
{ style: { marginBottom: "0.5rem" } },
|
{
|
||||||
"✅ Deployment Roster Access",
|
style: {
|
||||||
|
marginBottom: "0.5rem",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.5rem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "#10b981",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
style: {
|
||||||
|
width: "1.2rem",
|
||||||
|
height: "1.2rem",
|
||||||
|
flexShrink: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h("path", { d: "M20 6L9 17l-5-5" }),
|
||||||
|
),
|
||||||
|
"Deployment Roster Access",
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"li",
|
"li",
|
||||||
{ style: { marginBottom: "0.5rem" } },
|
{
|
||||||
"✅ After-Action Report Tools",
|
style: {
|
||||||
|
marginBottom: "0.5rem",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.5rem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "#10b981",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
style: {
|
||||||
|
width: "1.2rem",
|
||||||
|
height: "1.2rem",
|
||||||
|
flexShrink: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h("path", { d: "M20 6L9 17l-5-5" }),
|
||||||
|
),
|
||||||
|
"After-Action Report Tools",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
|
|||||||
@ -65,6 +65,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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) {
|
@media (max-width: 960px) {
|
||||||
.split-container {
|
.split-container {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
@ -35,23 +35,6 @@
|
|||||||
),
|
),
|
||||||
h("button", { onClick: () => store.setView("login") }, "Login"),
|
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",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -6,6 +6,10 @@
|
|||||||
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
||||||
|
|
||||||
RegistryApp.componentFns.LoginForm = function LoginForm() {
|
RegistryApp.componentFns.LoginForm = function LoginForm() {
|
||||||
|
const bridge = RegistryApp.bridge;
|
||||||
|
const isAuthenticating = store.getIsAuthenticating();
|
||||||
|
const loginError = store.getLoginError();
|
||||||
|
|
||||||
const handleLogin = () => {
|
const handleLogin = () => {
|
||||||
const data = {
|
const data = {
|
||||||
email: String(
|
email: String(
|
||||||
@ -15,8 +19,13 @@
|
|||||||
document.getElementById("org-login-password")?.value || "",
|
document.getElementById("org-login-password")?.value || "",
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
console.log("Login Attempt:", data);
|
|
||||||
store.setView("portal");
|
if (!bridge) {
|
||||||
|
store.failLogin("Login bridge is not available.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.requestLogin(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
@ -47,8 +56,16 @@
|
|||||||
id: "org-login-password",
|
id: "org-login-password",
|
||||||
type: "password",
|
type: "password",
|
||||||
placeholder: "********",
|
placeholder: "********",
|
||||||
|
disabled: isAuthenticating,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
loginError
|
||||||
|
? h(
|
||||||
|
"div",
|
||||||
|
{ className: "form-feedback is-error" },
|
||||||
|
loginError,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "form-actions" },
|
{ className: "form-actions" },
|
||||||
@ -58,8 +75,11 @@
|
|||||||
type: "button",
|
type: "button",
|
||||||
style: { width: "100%" },
|
style: { width: "100%" },
|
||||||
onClick: handleLogin,
|
onClick: handleLogin,
|
||||||
|
disabled: isAuthenticating,
|
||||||
},
|
},
|
||||||
"Access Authenticator",
|
isAuthenticating
|
||||||
|
? "Authenticating..."
|
||||||
|
: "Access Authenticator",
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"span",
|
"span",
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
const scriptFiles = [
|
const scriptFiles = [
|
||||||
"runtime.js",
|
"runtime.js",
|
||||||
"state.js",
|
"state.js",
|
||||||
|
"bridge.js",
|
||||||
"portal\\runtime.js",
|
"portal\\runtime.js",
|
||||||
"portal\\data.js",
|
"portal\\data.js",
|
||||||
"portal\\store.js",
|
"portal\\store.js",
|
||||||
|
|||||||
@ -30,7 +30,32 @@
|
|||||||
return type.charAt(0).toUpperCase() + type.slice(1);
|
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() {
|
getAssetReadiness() {
|
||||||
|
if (portalData.fleet.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const total = portalData.fleet.reduce(
|
const total = portalData.fleet.reduce(
|
||||||
(sum, unit) => sum + (100 - parseInt(unit.damage, 10)),
|
(sum, unit) => sum + (100 - parseInt(unit.damage, 10)),
|
||||||
0,
|
0,
|
||||||
|
|||||||
@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
OrgPortal.componentFns.OverviewCard = function OverviewCard() {
|
OrgPortal.componentFns.OverviewCard = function OverviewCard() {
|
||||||
const MetricCard = OrgPortal.componentFns.MetricCard;
|
const MetricCard = OrgPortal.componentFns.MetricCard;
|
||||||
|
const readiness = actions.getAssetReadiness();
|
||||||
|
const headquarters = portalData.org.headquarters || "ArmA Verse";
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
"section",
|
"section",
|
||||||
@ -38,7 +40,7 @@
|
|||||||
{ className: "org-summary" },
|
{ className: "org-summary" },
|
||||||
portalData.org.type,
|
portalData.org.type,
|
||||||
" operating from ",
|
" operating from ",
|
||||||
portalData.org.headquarters,
|
headquarters,
|
||||||
". Treasury, fleet status, inventory, and roster management are surfaced here first.",
|
". Treasury, fleet status, inventory, and roster management are surfaced here first.",
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
@ -55,7 +57,7 @@
|
|||||||
h(
|
h(
|
||||||
"span",
|
"span",
|
||||||
{ className: "org-meta-value" },
|
{ className: "org-meta-value" },
|
||||||
portalData.org.owner,
|
actions.formatDisplayName(portalData.org.owner),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
@ -83,7 +85,7 @@
|
|||||||
h(
|
h(
|
||||||
"span",
|
"span",
|
||||||
{ className: "org-meta-value" },
|
{ className: "org-meta-value" },
|
||||||
`${actions.getAssetReadiness()}%`,
|
readiness === null ? "N/A" : `${readiness}%`,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -11,7 +11,6 @@
|
|||||||
OrgPortal.componentFns.TreasuryCard = function TreasuryCard() {
|
OrgPortal.componentFns.TreasuryCard = function TreasuryCard() {
|
||||||
const notice = store.getTreasuryNotice();
|
const notice = store.getTreasuryNotice();
|
||||||
const creditLines = store.getCreditLines();
|
const creditLines = store.getCreditLines();
|
||||||
const noMembers = store.getMembers().length === 0;
|
|
||||||
const allowTreasuryActions = permissions.canManageTreasury();
|
const allowTreasuryActions = permissions.canManageTreasury();
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
@ -56,7 +55,6 @@
|
|||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
onClick: () => actions.openModal("payroll"),
|
onClick: () => actions.openModal("payroll"),
|
||||||
disabled: noMembers,
|
|
||||||
},
|
},
|
||||||
"Run Payroll",
|
"Run Payroll",
|
||||||
),
|
),
|
||||||
@ -66,7 +64,6 @@
|
|||||||
type: "button",
|
type: "button",
|
||||||
className: "org-secondary-btn",
|
className: "org-secondary-btn",
|
||||||
onClick: () => actions.openModal("transfer"),
|
onClick: () => actions.openModal("transfer"),
|
||||||
disabled: noMembers,
|
|
||||||
},
|
},
|
||||||
"Send Funds",
|
"Send Funds",
|
||||||
),
|
),
|
||||||
@ -76,7 +73,6 @@
|
|||||||
type: "button",
|
type: "button",
|
||||||
className: "org-secondary-btn",
|
className: "org-secondary-btn",
|
||||||
onClick: () => actions.openModal("credit"),
|
onClick: () => actions.openModal("credit"),
|
||||||
disabled: noMembers,
|
|
||||||
},
|
},
|
||||||
"Credit Line",
|
"Credit Line",
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,92 +1,37 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
|
||||||
|
function cloneValue(value) {
|
||||||
|
return JSON.parse(JSON.stringify(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceObject(target, source) {
|
||||||
|
Object.keys(target).forEach((key) => delete target[key]);
|
||||||
|
Object.assign(target, cloneValue(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceArray(target, source) {
|
||||||
|
target.splice(0, target.length, ...cloneValue(source));
|
||||||
|
}
|
||||||
|
|
||||||
OrgPortal.data = {
|
OrgPortal.data = {
|
||||||
portalData: {
|
portalData: {
|
||||||
org: {
|
org: {
|
||||||
name: "Black Rifle Company",
|
name: "",
|
||||||
tag: "BRC-0160566824",
|
tag: "",
|
||||||
type: "Private Military Company",
|
type: "Organization",
|
||||||
status: "Operational",
|
status: "Operational",
|
||||||
headquarters: "Georgetown Command Annex",
|
headquarters: "ArmA Verse",
|
||||||
owner: "Jacob Schmidt",
|
owner: "",
|
||||||
|
ownerUid: "",
|
||||||
|
isDefault: false,
|
||||||
},
|
},
|
||||||
funds: 482750,
|
funds: 0,
|
||||||
reputation: 72,
|
reputation: 0,
|
||||||
members: [
|
members: [],
|
||||||
{ name: "Jacob Schmidt" },
|
fleet: [],
|
||||||
{ name: "Mara Velez" },
|
assets: [],
|
||||||
{ name: "Rylan Cross" },
|
activity: [],
|
||||||
{ name: "Noah Briggs" },
|
|
||||||
{ name: "Elena Price" },
|
|
||||||
{ name: "Isaac Rowe" },
|
|
||||||
{ name: "Talia Boone" },
|
|
||||||
{ name: "Cade Mercer" },
|
|
||||||
],
|
|
||||||
fleet: [
|
|
||||||
{
|
|
||||||
name: "UH-80 Ghost Hawk",
|
|
||||||
type: "helicopter",
|
|
||||||
status: "Ready",
|
|
||||||
damage: "16%",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MH-9 Hummingbird",
|
|
||||||
type: "helicopter",
|
|
||||||
status: "Ready",
|
|
||||||
damage: "8%",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "M-ATV Patrol 1",
|
|
||||||
type: "car",
|
|
||||||
status: "Fielded",
|
|
||||||
damage: "24%",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "M2A1 Slammer",
|
|
||||||
type: "armor",
|
|
||||||
status: "Ready",
|
|
||||||
damage: "11%",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "RHIB Patrol Boat",
|
|
||||||
type: "naval",
|
|
||||||
status: "Repairing",
|
|
||||||
damage: "32%",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
assets: [
|
|
||||||
{ name: "First Aid Kits", type: "items", quantity: "36" },
|
|
||||||
{ name: "MX 6.5 mm Rifles", type: "weapons", quantity: "18" },
|
|
||||||
{
|
|
||||||
name: "6.5 mm Magazines",
|
|
||||||
type: "magazines",
|
|
||||||
quantity: "120",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Carryall Backpacks",
|
|
||||||
type: "backpacks",
|
|
||||||
quantity: "24",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
activity: [
|
|
||||||
{
|
|
||||||
time: "08:20",
|
|
||||||
text: "Treasury cleared contractor payment for northern route escort.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: "07:45",
|
|
||||||
text: "Viper Flight completed readiness checks on all rotary assets.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: "07:10",
|
|
||||||
text: "New recruit Cade Mercer accepted into ground training roster.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: "06:30",
|
|
||||||
text: "North Depot inventory count pushed reserve ratio above target.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
roadmap: [
|
roadmap: [
|
||||||
{
|
{
|
||||||
name: "Contracts Board",
|
name: "Contracts Board",
|
||||||
@ -111,8 +56,35 @@
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
actorName: "Jacob Schmidt",
|
actorName: "",
|
||||||
role: "Leader",
|
actorUid: "",
|
||||||
|
role: "",
|
||||||
|
ceo: false,
|
||||||
|
},
|
||||||
|
applyLoginPayload(payload) {
|
||||||
|
replaceObject(this.portalData.org, payload.portalData.org || {});
|
||||||
|
this.portalData.funds = payload.portalData.funds || 0;
|
||||||
|
this.portalData.reputation = payload.portalData.reputation || 0;
|
||||||
|
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.members,
|
||||||
|
payload.portalData.members || [],
|
||||||
|
);
|
||||||
|
replaceArray(this.portalData.fleet, payload.portalData.fleet || []);
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.assets,
|
||||||
|
payload.portalData.assets || [],
|
||||||
|
);
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.activity,
|
||||||
|
payload.portalData.activity || [],
|
||||||
|
);
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.roadmap,
|
||||||
|
payload.portalData.roadmap || [],
|
||||||
|
);
|
||||||
|
|
||||||
|
replaceObject(this.session, payload.session || {});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -3,11 +3,54 @@
|
|||||||
const { portalData, session } = OrgPortal.data;
|
const { portalData, session } = OrgPortal.data;
|
||||||
|
|
||||||
class OrgPortalPermissions {
|
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() {
|
isOrgLeaderOrCeo() {
|
||||||
return (
|
return (
|
||||||
session.actorName === portalData.org.owner ||
|
this.isOrgOwner() ||
|
||||||
session.role === "Leader" ||
|
this.getNormalizedRole() === "LEADER" ||
|
||||||
session.role === "CEO"
|
(this.isDefaultOrg() && this.isSessionCeo())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,15 @@
|
|||||||
[this.getModal, this.setModal] = createSignal(null);
|
[this.getModal, this.setModal] = createSignal(null);
|
||||||
[this.getOrgDisbanded, this.setOrgDisbanded] = createSignal(false);
|
[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();
|
OrgPortal.store = new OrgPortalStore();
|
||||||
|
|||||||
@ -1,15 +1,40 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
|
||||||
|
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) {
|
function h(tag, props = {}, ...children) {
|
||||||
const el = document.createElement(tag);
|
const isSvg = SVG_TAGS.has(tag);
|
||||||
|
const el = isSvg
|
||||||
|
? document.createElementNS(SVG_NS, tag)
|
||||||
|
: document.createElement(tag);
|
||||||
|
|
||||||
if (props) {
|
if (props) {
|
||||||
Object.entries(props).forEach(([key, value]) => {
|
Object.entries(props).forEach(([key, value]) => {
|
||||||
if (key.startsWith("on") && typeof value === "function") {
|
if (key.startsWith("on") && typeof value === "function") {
|
||||||
el.addEventListener(key.substring(2).toLowerCase(), value);
|
el.addEventListener(key.substring(2).toLowerCase(), value);
|
||||||
} else if (key === "className") {
|
} else if (key === "className") {
|
||||||
el.className = value;
|
if (isSvg) {
|
||||||
|
el.setAttribute("class", value);
|
||||||
|
} else {
|
||||||
|
el.className = value;
|
||||||
|
}
|
||||||
} else if (key === "style" && typeof value === "object") {
|
} else if (key === "style" && typeof value === "object") {
|
||||||
Object.assign(el.style, value);
|
Object.assign(el.style, value);
|
||||||
} else if (typeof value === "boolean") {
|
} else if (typeof value === "boolean") {
|
||||||
|
|||||||
@ -5,6 +5,37 @@
|
|||||||
class RegistryStore {
|
class RegistryStore {
|
||||||
constructor() {
|
constructor() {
|
||||||
[this.getView, this.setView] = createSignal("home");
|
[this.getView, this.setView] = createSignal("home");
|
||||||
|
[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 portalData = payload?.portalData;
|
||||||
|
const session = payload?.session;
|
||||||
|
|
||||||
|
if (!OrgPortal || !portalData || !session) {
|
||||||
|
this.failLogin("Login response was missing portal data.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrgPortal.data.applyLoginPayload(payload);
|
||||||
|
OrgPortal.store.hydrateFromPayload(payload);
|
||||||
|
|
||||||
|
this.setLoginError("");
|
||||||
|
this.setIsAuthenticating(false);
|
||||||
|
this.setView("portal");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
81
arma/ui/apps/main/bridge.js
Normal file
81
arma/ui/apps/main/bridge.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
(function () {
|
||||||
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
const store = RegistryApp.store;
|
||||||
|
|
||||||
|
function sendEvent(event, data) {
|
||||||
|
if (
|
||||||
|
typeof A3API !== "undefined" &&
|
||||||
|
typeof A3API.SendAlert === "function"
|
||||||
|
) {
|
||||||
|
A3API.SendAlert(
|
||||||
|
JSON.stringify({
|
||||||
|
event,
|
||||||
|
data,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMockPayload() {
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
return {
|
||||||
|
session: JSON.parse(JSON.stringify(OrgPortal.data.session)),
|
||||||
|
portalData: JSON.parse(JSON.stringify(OrgPortal.data.portalData)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestLogin(credentials) {
|
||||||
|
store.startLogin();
|
||||||
|
|
||||||
|
const sent = sendEvent("org::login::request", credentials);
|
||||||
|
if (sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if (!credentials.email || !credentials.password) {
|
||||||
|
store.failLogin("Enter both email and password.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.completeLogin(getMockPayload());
|
||||||
|
}, 350);
|
||||||
|
}
|
||||||
|
|
||||||
|
function receive(eventOrPayload, data = {}) {
|
||||||
|
const event =
|
||||||
|
typeof eventOrPayload === "object" && eventOrPayload !== null
|
||||||
|
? eventOrPayload.event
|
||||||
|
: eventOrPayload;
|
||||||
|
const payloadData =
|
||||||
|
typeof eventOrPayload === "object" && eventOrPayload !== null
|
||||||
|
? eventOrPayload.data || {}
|
||||||
|
: data;
|
||||||
|
|
||||||
|
if (event === "org::login::success") {
|
||||||
|
store.completeLogin(payloadData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event === "org::login::failure") {
|
||||||
|
store.failLogin(payloadData.message || "Authentication failed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RegistryApp.bridge = {
|
||||||
|
requestLogin,
|
||||||
|
receive,
|
||||||
|
sendEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
window.OrgUIBridge = {
|
||||||
|
requestLogin,
|
||||||
|
receive,
|
||||||
|
receiveLoginSuccess: (data) => receive("org::login::success", data),
|
||||||
|
receiveLoginFailure: (data) => receive("org::login::failure", data),
|
||||||
|
};
|
||||||
|
})();
|
||||||
@ -42,23 +42,119 @@
|
|||||||
},
|
},
|
||||||
h(
|
h(
|
||||||
"li",
|
"li",
|
||||||
{ style: { marginBottom: "0.5rem" } },
|
{
|
||||||
"✅ Official Organization Designator",
|
style: {
|
||||||
|
marginBottom: "0.5rem",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.5rem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "#10b981",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
style: {
|
||||||
|
width: "1.2rem",
|
||||||
|
height: "1.2rem",
|
||||||
|
flexShrink: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h("path", { d: "M20 6L9 17l-5-5" }),
|
||||||
|
),
|
||||||
|
"Official Organization Designator",
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"li",
|
"li",
|
||||||
{ style: { marginBottom: "0.5rem" } },
|
{
|
||||||
"✅ Secure Comms Channel",
|
style: {
|
||||||
|
marginBottom: "0.5rem",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.5rem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "#10b981",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
style: {
|
||||||
|
width: "1.2rem",
|
||||||
|
height: "1.2rem",
|
||||||
|
flexShrink: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h("path", { d: "M20 6L9 17l-5-5" }),
|
||||||
|
),
|
||||||
|
"Secure Comms Channel",
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"li",
|
"li",
|
||||||
{ style: { marginBottom: "0.5rem" } },
|
{
|
||||||
"✅ Deployment Roster Access",
|
style: {
|
||||||
|
marginBottom: "0.5rem",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.5rem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "#10b981",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
style: {
|
||||||
|
width: "1.2rem",
|
||||||
|
height: "1.2rem",
|
||||||
|
flexShrink: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h("path", { d: "M20 6L9 17l-5-5" }),
|
||||||
|
),
|
||||||
|
"Deployment Roster Access",
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"li",
|
"li",
|
||||||
{ style: { marginBottom: "0.5rem" } },
|
{
|
||||||
"✅ After-Action Report Tools",
|
style: {
|
||||||
|
marginBottom: "0.5rem",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.5rem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "#10b981",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
style: {
|
||||||
|
width: "1.2rem",
|
||||||
|
height: "1.2rem",
|
||||||
|
flexShrink: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h("path", { d: "M20 6L9 17l-5-5" }),
|
||||||
|
),
|
||||||
|
"After-Action Report Tools",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
|
|||||||
@ -65,6 +65,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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) {
|
@media (max-width: 960px) {
|
||||||
.split-container {
|
.split-container {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
@ -6,6 +6,10 @@
|
|||||||
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
RegistryApp.componentFns = RegistryApp.componentFns || {};
|
||||||
|
|
||||||
RegistryApp.componentFns.LoginForm = function LoginForm() {
|
RegistryApp.componentFns.LoginForm = function LoginForm() {
|
||||||
|
const bridge = RegistryApp.bridge;
|
||||||
|
const isAuthenticating = store.getIsAuthenticating();
|
||||||
|
const loginError = store.getLoginError();
|
||||||
|
|
||||||
const handleLogin = () => {
|
const handleLogin = () => {
|
||||||
const data = {
|
const data = {
|
||||||
email: String(
|
email: String(
|
||||||
@ -15,8 +19,13 @@
|
|||||||
document.getElementById("org-login-password")?.value || "",
|
document.getElementById("org-login-password")?.value || "",
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
console.log("Login Attempt:", data);
|
|
||||||
store.setView("portal");
|
if (!bridge) {
|
||||||
|
store.failLogin("Login bridge is not available.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.requestLogin(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
@ -47,8 +56,16 @@
|
|||||||
id: "org-login-password",
|
id: "org-login-password",
|
||||||
type: "password",
|
type: "password",
|
||||||
placeholder: "********",
|
placeholder: "********",
|
||||||
|
disabled: isAuthenticating,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
loginError
|
||||||
|
? h(
|
||||||
|
"div",
|
||||||
|
{ className: "form-feedback is-error" },
|
||||||
|
loginError,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "form-actions" },
|
{ className: "form-actions" },
|
||||||
@ -58,8 +75,11 @@
|
|||||||
type: "button",
|
type: "button",
|
||||||
style: { width: "100%" },
|
style: { width: "100%" },
|
||||||
onClick: handleLogin,
|
onClick: handleLogin,
|
||||||
|
disabled: isAuthenticating,
|
||||||
},
|
},
|
||||||
"Access Authenticator",
|
isAuthenticating
|
||||||
|
? "Authenticating..."
|
||||||
|
: "Access Authenticator",
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"span",
|
"span",
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script src="runtime.js"></script>
|
<script src="runtime.js"></script>
|
||||||
<script src="state.js"></script>
|
<script src="state.js"></script>
|
||||||
|
<script src="bridge.js"></script>
|
||||||
<script src="../portal/runtime.js"></script>
|
<script src="../portal/runtime.js"></script>
|
||||||
<script src="../portal/data.js"></script>
|
<script src="../portal/data.js"></script>
|
||||||
<script src="../portal/store.js"></script>
|
<script src="../portal/store.js"></script>
|
||||||
|
|||||||
@ -1,15 +1,40 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
const RegistryApp = (window.RegistryApp = window.RegistryApp || {});
|
||||||
|
|
||||||
|
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) {
|
function h(tag, props = {}, ...children) {
|
||||||
const el = document.createElement(tag);
|
const isSvg = SVG_TAGS.has(tag);
|
||||||
|
const el = isSvg
|
||||||
|
? document.createElementNS(SVG_NS, tag)
|
||||||
|
: document.createElement(tag);
|
||||||
|
|
||||||
if (props) {
|
if (props) {
|
||||||
Object.entries(props).forEach(([key, value]) => {
|
Object.entries(props).forEach(([key, value]) => {
|
||||||
if (key.startsWith("on") && typeof value === "function") {
|
if (key.startsWith("on") && typeof value === "function") {
|
||||||
el.addEventListener(key.substring(2).toLowerCase(), value);
|
el.addEventListener(key.substring(2).toLowerCase(), value);
|
||||||
} else if (key === "className") {
|
} else if (key === "className") {
|
||||||
el.className = value;
|
if (isSvg) {
|
||||||
|
el.setAttribute("class", value);
|
||||||
|
} else {
|
||||||
|
el.className = value;
|
||||||
|
}
|
||||||
} else if (key === "style" && typeof value === "object") {
|
} else if (key === "style" && typeof value === "object") {
|
||||||
Object.assign(el.style, value);
|
Object.assign(el.style, value);
|
||||||
} else if (typeof value === "boolean") {
|
} else if (typeof value === "boolean") {
|
||||||
|
|||||||
@ -5,6 +5,37 @@
|
|||||||
class RegistryStore {
|
class RegistryStore {
|
||||||
constructor() {
|
constructor() {
|
||||||
[this.getView, this.setView] = createSignal("home");
|
[this.getView, this.setView] = createSignal("home");
|
||||||
|
[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 portalData = payload?.portalData;
|
||||||
|
const session = payload?.session;
|
||||||
|
|
||||||
|
if (!OrgPortal || !portalData || !session) {
|
||||||
|
this.failLogin("Login response was missing portal data.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrgPortal.data.applyLoginPayload(payload);
|
||||||
|
OrgPortal.store.hydrateFromPayload(payload);
|
||||||
|
|
||||||
|
this.setLoginError("");
|
||||||
|
this.setIsAuthenticating(false);
|
||||||
|
this.setView("portal");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,32 @@
|
|||||||
return type.charAt(0).toUpperCase() + type.slice(1);
|
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() {
|
getAssetReadiness() {
|
||||||
|
if (portalData.fleet.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const total = portalData.fleet.reduce(
|
const total = portalData.fleet.reduce(
|
||||||
(sum, unit) => sum + (100 - parseInt(unit.damage, 10)),
|
(sum, unit) => sum + (100 - parseInt(unit.damage, 10)),
|
||||||
0,
|
0,
|
||||||
|
|||||||
@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
OrgPortal.componentFns.OverviewCard = function OverviewCard() {
|
OrgPortal.componentFns.OverviewCard = function OverviewCard() {
|
||||||
const MetricCard = OrgPortal.componentFns.MetricCard;
|
const MetricCard = OrgPortal.componentFns.MetricCard;
|
||||||
|
const readiness = actions.getAssetReadiness();
|
||||||
|
const headquarters = portalData.org.headquarters || "ArmA Verse";
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
"section",
|
"section",
|
||||||
@ -38,7 +40,7 @@
|
|||||||
{ className: "org-summary" },
|
{ className: "org-summary" },
|
||||||
portalData.org.type,
|
portalData.org.type,
|
||||||
" operating from ",
|
" operating from ",
|
||||||
portalData.org.headquarters,
|
headquarters,
|
||||||
". Treasury, fleet status, inventory, and roster management are surfaced here first.",
|
". Treasury, fleet status, inventory, and roster management are surfaced here first.",
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
@ -55,7 +57,7 @@
|
|||||||
h(
|
h(
|
||||||
"span",
|
"span",
|
||||||
{ className: "org-meta-value" },
|
{ className: "org-meta-value" },
|
||||||
portalData.org.owner,
|
actions.formatDisplayName(portalData.org.owner),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
@ -83,7 +85,7 @@
|
|||||||
h(
|
h(
|
||||||
"span",
|
"span",
|
||||||
{ className: "org-meta-value" },
|
{ className: "org-meta-value" },
|
||||||
`${actions.getAssetReadiness()}%`,
|
readiness === null ? "N/A" : `${readiness}%`,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -11,7 +11,6 @@
|
|||||||
OrgPortal.componentFns.TreasuryCard = function TreasuryCard() {
|
OrgPortal.componentFns.TreasuryCard = function TreasuryCard() {
|
||||||
const notice = store.getTreasuryNotice();
|
const notice = store.getTreasuryNotice();
|
||||||
const creditLines = store.getCreditLines();
|
const creditLines = store.getCreditLines();
|
||||||
const noMembers = store.getMembers().length === 0;
|
|
||||||
const allowTreasuryActions = permissions.canManageTreasury();
|
const allowTreasuryActions = permissions.canManageTreasury();
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
@ -56,7 +55,6 @@
|
|||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
onClick: () => actions.openModal("payroll"),
|
onClick: () => actions.openModal("payroll"),
|
||||||
disabled: noMembers,
|
|
||||||
},
|
},
|
||||||
"Run Payroll",
|
"Run Payroll",
|
||||||
),
|
),
|
||||||
@ -66,7 +64,6 @@
|
|||||||
type: "button",
|
type: "button",
|
||||||
className: "org-secondary-btn",
|
className: "org-secondary-btn",
|
||||||
onClick: () => actions.openModal("transfer"),
|
onClick: () => actions.openModal("transfer"),
|
||||||
disabled: noMembers,
|
|
||||||
},
|
},
|
||||||
"Send Funds",
|
"Send Funds",
|
||||||
),
|
),
|
||||||
@ -76,7 +73,6 @@
|
|||||||
type: "button",
|
type: "button",
|
||||||
className: "org-secondary-btn",
|
className: "org-secondary-btn",
|
||||||
onClick: () => actions.openModal("credit"),
|
onClick: () => actions.openModal("credit"),
|
||||||
disabled: noMembers,
|
|
||||||
},
|
},
|
||||||
"Credit Line",
|
"Credit Line",
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,6 +1,19 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
const OrgPortal = (window.OrgPortal = window.OrgPortal || {});
|
||||||
|
|
||||||
|
function cloneValue(value) {
|
||||||
|
return JSON.parse(JSON.stringify(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceObject(target, source) {
|
||||||
|
Object.keys(target).forEach((key) => delete target[key]);
|
||||||
|
Object.assign(target, cloneValue(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceArray(target, source) {
|
||||||
|
target.splice(0, target.length, ...cloneValue(source));
|
||||||
|
}
|
||||||
|
|
||||||
OrgPortal.data = {
|
OrgPortal.data = {
|
||||||
portalData: {
|
portalData: {
|
||||||
org: {
|
org: {
|
||||||
@ -10,6 +23,8 @@
|
|||||||
status: "Operational",
|
status: "Operational",
|
||||||
headquarters: "Georgetown Command Annex",
|
headquarters: "Georgetown Command Annex",
|
||||||
owner: "Jacob Schmidt",
|
owner: "Jacob Schmidt",
|
||||||
|
ownerUid: "uid-jacob-schmidt",
|
||||||
|
isDefault: false,
|
||||||
},
|
},
|
||||||
funds: 482750,
|
funds: 482750,
|
||||||
reputation: 72,
|
reputation: 72,
|
||||||
@ -112,7 +127,34 @@
|
|||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
actorName: "Jacob Schmidt",
|
actorName: "Jacob Schmidt",
|
||||||
|
actorUid: "uid-jacob-schmidt",
|
||||||
role: "Leader",
|
role: "Leader",
|
||||||
|
ceo: false,
|
||||||
|
},
|
||||||
|
applyLoginPayload(payload) {
|
||||||
|
replaceObject(this.portalData.org, payload.portalData.org || {});
|
||||||
|
this.portalData.funds = payload.portalData.funds || 0;
|
||||||
|
this.portalData.reputation = payload.portalData.reputation || 0;
|
||||||
|
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.members,
|
||||||
|
payload.portalData.members || [],
|
||||||
|
);
|
||||||
|
replaceArray(this.portalData.fleet, payload.portalData.fleet || []);
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.assets,
|
||||||
|
payload.portalData.assets || [],
|
||||||
|
);
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.activity,
|
||||||
|
payload.portalData.activity || [],
|
||||||
|
);
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.roadmap,
|
||||||
|
payload.portalData.roadmap || [],
|
||||||
|
);
|
||||||
|
|
||||||
|
replaceObject(this.session, payload.session || {});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -3,11 +3,54 @@
|
|||||||
const { portalData, session } = OrgPortal.data;
|
const { portalData, session } = OrgPortal.data;
|
||||||
|
|
||||||
class OrgPortalPermissions {
|
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() {
|
isOrgLeaderOrCeo() {
|
||||||
return (
|
return (
|
||||||
session.actorName === portalData.org.owner ||
|
this.isOrgOwner() ||
|
||||||
session.role === "Leader" ||
|
this.getNormalizedRole() === "LEADER" ||
|
||||||
session.role === "CEO"
|
(this.isDefaultOrg() && this.isSessionCeo())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,15 @@
|
|||||||
[this.getModal, this.setModal] = createSignal(null);
|
[this.getModal, this.setModal] = createSignal(null);
|
||||||
[this.getOrgDisbanded, this.setOrgDisbanded] = createSignal(false);
|
[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();
|
OrgPortal.store = new OrgPortalStore();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user