217 lines
7.5 KiB
JavaScript
217 lines
7.5 KiB
JavaScript
import server from "/@server/server.js"
|
||
import env from "/_/code/env.js"
|
||
import "./SettingsRolesSection.js"
|
||
import "./SettingsIntegrationsSection.js"
|
||
import "/_/code/components/LoadingCircle.js"
|
||
|
||
css(`
|
||
settings- {
|
||
font-family: 'Arial';
|
||
scrollbar-width: none;
|
||
-ms-overflow-style: none;
|
||
}
|
||
settings- input::placeholder {
|
||
color: var(--headertext);
|
||
opacity: 0.35;
|
||
}
|
||
`)
|
||
|
||
class Settings extends Shadow {
|
||
roles = []
|
||
allApps = []
|
||
roleApps = {}
|
||
stripeDetails = null
|
||
loaded = false
|
||
|
||
// ── URL-derived routing ───────────────────────────────────────────
|
||
|
||
get basePath() {
|
||
return window.location.pathname
|
||
.replace(/\/roles(\/[^/]*)?$/, '')
|
||
.replace(/\/integrations$/, '')
|
||
.replace(/\/$/, '') || '/'
|
||
}
|
||
|
||
get section() {
|
||
const path = window.location.pathname
|
||
if (/\/roles\/[^/]+/.test(path)) return 'role-detail'
|
||
if (/\/roles$/.test(path)) return 'roles'
|
||
if (/\/integrations$/.test(path)) return 'integrations'
|
||
return 'home'
|
||
}
|
||
|
||
get activeRoleId() {
|
||
const match = window.location.pathname.match(/\/roles\/([^/]+)$/)
|
||
return match ? match[1] : null
|
||
}
|
||
|
||
// ── Render ────────────────────────────────────────────────────────
|
||
|
||
render() {
|
||
const loading = !this.loaded || global.appRefreshing
|
||
|
||
VStack(() => {
|
||
if (this.section === 'roles' || this.section === 'role-detail') {
|
||
SettingsRolesSection(
|
||
this.roles,
|
||
this.allApps,
|
||
this.section,
|
||
this.activeRoleId,
|
||
this.roleApps,
|
||
this.basePath,
|
||
{
|
||
onDeleteRole: (roleId) => this.deleteRole(roleId),
|
||
onCreateRole: (name) => this.createRole(name),
|
||
onToggleApp: (roleId, appId, add) => this.toggleRoleApp(roleId, appId, add),
|
||
},
|
||
loading
|
||
)
|
||
} else if (this.section === 'integrations') {
|
||
SettingsIntegrationsSection(
|
||
this.stripeDetails,
|
||
this.basePath,
|
||
{ onConnectStripe: () => this.handleConnectStripe() },
|
||
loading
|
||
)
|
||
} else {
|
||
if (loading) LoadingCircle()
|
||
else this.renderHome()
|
||
}
|
||
})
|
||
.height(100, pct)
|
||
.width(100, pct)
|
||
.boxSizing("border-box")
|
||
.overflow("hidden")
|
||
.onNavigate(() => {
|
||
this.rerender()
|
||
})
|
||
.onAppear(async () => {
|
||
if (this.loaded) return
|
||
await this.loadData()
|
||
})
|
||
}
|
||
|
||
// ── Home ──────────────────────────────────────────────────────────
|
||
|
||
renderHome() {
|
||
VStack(() => {
|
||
VStack(() => {}).height(1, px).background("var(--divider)").flexShrink(0)
|
||
|
||
VStack(() => {
|
||
this.menuItem("🎭", "Roles & Apps", `${this.roles.length} roles`, "roles")
|
||
|
||
VStack(() => {})
|
||
.height(1, px)
|
||
.background("var(--divider)")
|
||
.marginLeft(3.5, em)
|
||
|
||
this.menuItem("💳", "Stripe", this.stripeDetails?.email ? "Connected" : "Not connected", "integrations")
|
||
})
|
||
.marginTop(0.5, em)
|
||
.paddingHorizontal(1, em)
|
||
.flexShrink(0)
|
||
})
|
||
.height(100, pct)
|
||
.overflowY("auto")
|
||
}
|
||
|
||
menuItem(icon, label, subtitle, section) {
|
||
HStack(() => {
|
||
VStack(() => {
|
||
p(icon).margin(0).fontSize(1.1, em).lineHeight("1").color("var(--headertext)")
|
||
})
|
||
.width(2.2, em).height(2.2, em).borderRadius(0.45, em)
|
||
.background("var(--darkaccent)").border("1px solid var(--divider)")
|
||
.justifyContent("center").alignItems("center").flexShrink(0)
|
||
|
||
VStack(() => {
|
||
p(label).margin(0).fontSize(0.92, em).fontWeight("600").color("var(--headertext)")
|
||
p(subtitle).margin(0).marginTop(0.15, em).fontSize(0.72, em).color("var(--headertext)").opacity(0.42)
|
||
})
|
||
.flex(1).gap(0)
|
||
|
||
p("›").margin(0).fontSize(1.1, em).color("var(--headertext)").opacity(0.3)
|
||
})
|
||
.gap(0.75, em).alignItems("center")
|
||
.paddingVertical(0.85, em)
|
||
.cursor("pointer")
|
||
.onTap(() => window.navigateTo(`${this.basePath}/${section}`))
|
||
}
|
||
|
||
// ── Server actions ────────────────────────────────────────────────
|
||
|
||
async loadData() {
|
||
const [roles, apps, stripe] = await Promise.all([
|
||
server.getRoles(global.currentNetwork.id),
|
||
server.getAllApps(),
|
||
server.getStripeProfile(global.currentNetwork.id)
|
||
])
|
||
this.roles = Array.isArray(roles) ? roles : []
|
||
this.allApps = Array.isArray(apps) ? apps : []
|
||
this.stripeDetails = stripe
|
||
|
||
await Promise.all(this.roles.map(async role => {
|
||
const roleApps = await server.getRoleApps(role.id)
|
||
this.roleApps[role.id] = new Set(Array.isArray(roleApps) ? roleApps.map(a => a.id) : [])
|
||
}))
|
||
|
||
this.loaded = true
|
||
this.rerender()
|
||
}
|
||
|
||
async createRole(name) {
|
||
const result = await server.createRole(name, global.currentNetwork.id)
|
||
if (!result?.error && result?.role) {
|
||
this.roles.push(result.role)
|
||
this.roleApps[result.role.id] = new Set()
|
||
this.rerender()
|
||
}
|
||
}
|
||
|
||
async deleteRole(roleId) {
|
||
await server.deleteRole(roleId, global.currentNetwork.id)
|
||
this.roles = this.roles.filter(r => r.id !== roleId)
|
||
delete this.roleApps[roleId]
|
||
window.navigateTo(`${this.basePath}/roles`)
|
||
}
|
||
|
||
async toggleRoleApp(roleId, appId, add) {
|
||
if (add) {
|
||
await server.addRoleApp(roleId, appId)
|
||
if (!this.roleApps[roleId]) this.roleApps[roleId] = new Set()
|
||
this.roleApps[roleId].add(appId)
|
||
} else {
|
||
await server.removeRoleApp(roleId, appId)
|
||
this.roleApps[roleId]?.delete(appId)
|
||
}
|
||
|
||
if (roleId == global.currentNetwork.role?.id) {
|
||
const appName = this.allApps.find(a => a.id === appId)?.name
|
||
if (appName) {
|
||
if (add) {
|
||
global.currentNetwork.apps.push(appName)
|
||
} else {
|
||
global.currentNetwork.apps = global.currentNetwork.apps.filter(a => a !== appName)
|
||
}
|
||
document.querySelector("appmenu-")?.rerender()
|
||
}
|
||
}
|
||
|
||
this.rerender()
|
||
}
|
||
|
||
handleConnectStripe() {
|
||
const state = btoa(JSON.stringify({ returnTo: window.location.href, networkId: global.currentNetwork.id }))
|
||
const params = new URLSearchParams({
|
||
response_type: "code",
|
||
client_id: env.client_id,
|
||
scope: "read_write",
|
||
redirect_uri: `${env.baseURL}/stripe/onboardingcomplete`,
|
||
state,
|
||
})
|
||
window.location.href = `https://connect.stripe.com/oauth/authorize?${params}`
|
||
}
|
||
}
|
||
|
||
register(Settings)
|