init
This commit is contained in:
216
settings/settings.js
Normal file
216
settings/settings.js
Normal file
@@ -0,0 +1,216 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user