init
This commit is contained in:
77
settings/SettingsIntegrationsSection.js
Normal file
77
settings/SettingsIntegrationsSection.js
Normal file
@@ -0,0 +1,77 @@
|
||||
class SettingsIntegrationsSection extends Shadow {
|
||||
constructor(stripeDetails, basePath, callbacks, loading = false) {
|
||||
super()
|
||||
this.stripeDetails = stripeDetails
|
||||
this.basePath = basePath
|
||||
this.loading = loading
|
||||
this.onConnectStripe = callbacks.onConnectStripe
|
||||
}
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
this.backHeader("Integrations", () => window.navigateTo(this.basePath))
|
||||
|
||||
VStack(() => {}).height(1, px).background("var(--divider)").flexShrink(0)
|
||||
|
||||
if (this.loading) { LoadingCircle(); return }
|
||||
|
||||
VStack(() => {
|
||||
p("STRIPE")
|
||||
.margin(0).marginBottom(0.55, em)
|
||||
.fontSize(0.62, em).fontWeight("700").letterSpacing("0.07em")
|
||||
.color("var(--headertext)").opacity(0.35)
|
||||
|
||||
if (this.stripeDetails?.email) {
|
||||
HStack(() => {
|
||||
VStack(() => {})
|
||||
.width(8, px).height(8, px).borderRadius(50, pct)
|
||||
.background("#10b981").flexShrink(0)
|
||||
VStack(() => {
|
||||
p("Connected").margin(0).fontSize(0.9, em).fontWeight("600").color("var(--headertext)")
|
||||
p(this.stripeDetails.email)
|
||||
.margin(0).marginTop(0.15, em).fontSize(0.72, em)
|
||||
.color("var(--headertext)").opacity(0.42)
|
||||
})
|
||||
.gap(0).flex(1)
|
||||
})
|
||||
.gap(0.65, em).alignItems("center").paddingVertical(0.85, em)
|
||||
} else {
|
||||
VStack(() => {
|
||||
p("Stripe is not connected to this network.")
|
||||
.margin(0).fontSize(0.82, em).color("var(--headertext)").opacity(0.5)
|
||||
.marginBottom(1, em)
|
||||
button("Connect Stripe →")
|
||||
.paddingHorizontal(1.2, em).paddingVertical(0.65, em)
|
||||
.border("none").borderRadius(0.5, em)
|
||||
.background("var(--quillred)").color("white")
|
||||
.fontSize(0.85, em).fontWeight("600").cursor("pointer")
|
||||
.onTap(() => this.onConnectStripe())
|
||||
})
|
||||
.paddingVertical(0.85, em).gap(0)
|
||||
}
|
||||
})
|
||||
.paddingHorizontal(1, em).paddingTop(1, em)
|
||||
.flex(1).overflowY("auto")
|
||||
})
|
||||
.height(100, pct).overflow("hidden")
|
||||
}
|
||||
|
||||
backHeader(title, onBack) {
|
||||
HStack(() => {
|
||||
button("‹")
|
||||
.border("none").background("transparent")
|
||||
.color("var(--headertext)").fontSize(1.4, em)
|
||||
.lineHeight("1").paddingVertical(0.2, em).paddingRight(0.3, em)
|
||||
.cursor("pointer").flexShrink(0)
|
||||
.onTap(onBack)
|
||||
p(title)
|
||||
.margin(0).fontSize(1.05, em).fontWeight("700").color("var(--headertext)")
|
||||
.flex(1).overflow("hidden").whiteSpace("nowrap").textOverflow("ellipsis")
|
||||
})
|
||||
.gap(0.25, em).alignItems("center")
|
||||
.paddingHorizontal(0.85, em).paddingTop(1.25, em).paddingBottom(0.85, em)
|
||||
.flexShrink(0)
|
||||
}
|
||||
}
|
||||
|
||||
register(SettingsIntegrationsSection)
|
||||
250
settings/SettingsRolesSection.js
Normal file
250
settings/SettingsRolesSection.js
Normal file
@@ -0,0 +1,250 @@
|
||||
class SettingsRolesSection extends Shadow {
|
||||
newRoleName = ""
|
||||
confirmDeleteId = null
|
||||
|
||||
constructor(roles, allApps, activeSection, activeRoleId, roleApps, basePath, callbacks, loading = false) {
|
||||
super()
|
||||
this.roles = roles
|
||||
this.allApps = allApps
|
||||
this.activeSection = activeSection // "roles" | "role-detail"
|
||||
this.activeRoleId = activeRoleId
|
||||
this.roleApps = roleApps
|
||||
this.basePath = basePath
|
||||
this.loading = loading
|
||||
this.onDeleteRole = callbacks.onDeleteRole
|
||||
this.onCreateRole = callbacks.onCreateRole
|
||||
this.onToggleApp = callbacks.onToggleApp
|
||||
}
|
||||
|
||||
get activeRole() {
|
||||
return this.roles.find(r => String(r.id) === String(this.activeRoleId)) ?? null
|
||||
}
|
||||
|
||||
get activeRoleAppIds() {
|
||||
return this.roleApps[this.activeRoleId] ?? new Set()
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.activeSection === "role-detail") {
|
||||
this.renderRoleDetail()
|
||||
} else {
|
||||
this.renderRolesList()
|
||||
}
|
||||
}
|
||||
|
||||
renderRolesList() {
|
||||
VStack(() => {
|
||||
this.backHeader("Roles & Apps", () => window.navigateTo(this.basePath))
|
||||
|
||||
VStack(() => {}).height(1, px).background("var(--divider)").flexShrink(0)
|
||||
|
||||
if (this.loading) { LoadingCircle(); return }
|
||||
|
||||
VStack(() => {
|
||||
this.roles.forEach((role, i) => {
|
||||
const appCount = (this.roleApps[role.id] ?? new Set()).size
|
||||
const isConfirming = this.confirmDeleteId === role.id
|
||||
|
||||
HStack(() => {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
p(role.name)
|
||||
.margin(0).fontSize(0.92, em).fontWeight("600").color("var(--headertext)")
|
||||
if (role.is_default) {
|
||||
p("default")
|
||||
.margin(0).fontSize(0.62, em).fontWeight("600")
|
||||
.color("var(--quillred)")
|
||||
.background("rgba(159,28,41,0.1)")
|
||||
.paddingHorizontal(0.45, em).paddingVertical(0.1, em)
|
||||
.borderRadius(100, px)
|
||||
}
|
||||
})
|
||||
.gap(0.45, em).alignItems("center")
|
||||
p(`${appCount} app${appCount !== 1 ? "s" : ""} assigned`)
|
||||
.margin(0).marginTop(0.15, em).fontSize(0.72, em)
|
||||
.color("var(--headertext)").opacity(0.4)
|
||||
})
|
||||
.flex(1).gap(0)
|
||||
|
||||
if (isConfirming) {
|
||||
HStack(() => {
|
||||
button("Delete")
|
||||
.padding("0.25em 0.6em")
|
||||
.border("none").borderRadius(0.35, em)
|
||||
.background("var(--quillred)").color("white")
|
||||
.fontSize(0.72, em).fontWeight("600").cursor("pointer")
|
||||
.onTap(async (e) => {
|
||||
e.stopPropagation()
|
||||
await this.onDeleteRole(role.id)
|
||||
})
|
||||
button("✕")
|
||||
.padding("0.25em 0.45em")
|
||||
.border("none").borderRadius(0.35, em)
|
||||
.background("transparent").color("var(--headertext)")
|
||||
.opacity(0.4).fontSize(0.78, em).cursor("pointer")
|
||||
.onTap((e) => {
|
||||
e.stopPropagation()
|
||||
this.confirmDeleteId = null
|
||||
this.rerender()
|
||||
})
|
||||
})
|
||||
.gap(0.35, em).alignItems("center")
|
||||
} else {
|
||||
HStack(() => {
|
||||
button("⋯")
|
||||
.border("none").background("transparent")
|
||||
.color("var(--headertext)").opacity(0.35)
|
||||
.fontSize(1.1, em).cursor("pointer").padding("0.1em 0.4em")
|
||||
.onTap((e) => {
|
||||
e.stopPropagation()
|
||||
this.confirmDeleteId = role.id
|
||||
this.rerender()
|
||||
})
|
||||
p("›").margin(0).fontSize(1.1, em).color("var(--headertext)").opacity(0.3)
|
||||
})
|
||||
.gap(0.1, em).alignItems("center")
|
||||
}
|
||||
})
|
||||
.gap(0.75, em).alignItems("center")
|
||||
.paddingVertical(0.85, em)
|
||||
.cursor("pointer")
|
||||
.onTap(() => window.navigateTo(`${this.basePath}/roles/${role.id}`))
|
||||
|
||||
if (i < this.roles.length - 1) {
|
||||
VStack(() => {}).height(1, px).background("var(--divider)").marginLeft(0)
|
||||
}
|
||||
})
|
||||
|
||||
// New role input
|
||||
HStack(() => {
|
||||
p("+").margin(0).fontSize(1.1, em).color("var(--headertext)").opacity(0.35).flexShrink(0)
|
||||
input("New role name…", "100%")
|
||||
.border("none").background("transparent")
|
||||
.color("var(--headertext)").fontSize(0.88, em).outline("none").flex(1)
|
||||
.attr({ value: this.newRoleName })
|
||||
.onInput(e => { this.newRoleName = e.target.value })
|
||||
.onKeyDown(async (e) => {
|
||||
if (e.key === "Enter" && this.newRoleName.trim()) {
|
||||
await this.onCreateRole(this.newRoleName.trim())
|
||||
this.newRoleName = ""
|
||||
}
|
||||
})
|
||||
})
|
||||
.gap(0.65, em).alignItems("center")
|
||||
.paddingVertical(0.85, em)
|
||||
.borderTop("1px dashed var(--divider)")
|
||||
.marginTop(0.1, em)
|
||||
})
|
||||
.paddingHorizontal(1, em)
|
||||
.flex(1).overflowY("auto")
|
||||
})
|
||||
.height(100, pct).overflow("hidden")
|
||||
}
|
||||
|
||||
renderRoleDetail() {
|
||||
VStack(() => {
|
||||
this.backHeader(this.activeRole?.name ?? "Role", () => window.navigateTo(`${this.basePath}/roles`))
|
||||
|
||||
VStack(() => {}).height(1, px).background("var(--divider)").flexShrink(0)
|
||||
|
||||
if (this.loading) { LoadingCircle(); return }
|
||||
|
||||
VStack(() => {
|
||||
VStack(() => {
|
||||
p("APP ACCESS")
|
||||
.margin(0).marginBottom(0.55, em)
|
||||
.fontSize(0.62, em).fontWeight("700").letterSpacing("0.07em")
|
||||
.color("var(--headertext)").opacity(0.35)
|
||||
|
||||
this.allApps.forEach((app, i) => {
|
||||
const hasApp = this.activeRoleAppIds.has(app.id)
|
||||
const comingSoonApps = ["jobs", "politics", "files"]
|
||||
const isComingSoon = comingSoonApps.includes(app.name)
|
||||
|
||||
HStack(() => {
|
||||
p(app.name.charAt(0).toUpperCase() + app.name.slice(1))
|
||||
.margin(0).fontSize(0.9, em).color("var(--headertext)").flex(1)
|
||||
|
||||
HStack(() => {
|
||||
if (isComingSoon) {
|
||||
p("Coming soon")
|
||||
.margin(0).fontSize(0.9, em).color("var(--headertext)").opacity(0.45)
|
||||
.background("var(--darkaccent)").border("1px solid var(--divider)")
|
||||
.paddingHorizontal(0.5, em).paddingVertical(0.15, em).borderRadius(100, px)
|
||||
}
|
||||
|
||||
VStack(() => {})
|
||||
.width(1.15, em).height(1.15, em)
|
||||
.borderRadius(0.25, em)
|
||||
.background(hasApp ? "var(--quillred)" : "transparent")
|
||||
.border(`2px solid ${hasApp ? "var(--quillred)" : "var(--divider)"}`)
|
||||
.boxSizing("border-box")
|
||||
.justifyContent("center").alignItems("center")
|
||||
})
|
||||
.justifyContent("center").alignItems("center")
|
||||
.gap(1, em)
|
||||
})
|
||||
.paddingVertical(0.8, em)
|
||||
.gap(1, em).alignItems("center")
|
||||
.cursor(isComingSoon ? "default" : "pointer")
|
||||
.opacity(isComingSoon ? 0.5 : 1)
|
||||
.onTap(() => { if (!isComingSoon) this.onToggleApp(this.activeRoleId, app.id, !hasApp) })
|
||||
|
||||
if (i < this.allApps.length - 1) {
|
||||
VStack(() => {}).height(1, px).background("var(--divider)")
|
||||
}
|
||||
})
|
||||
})
|
||||
.paddingHorizontal(1, em)
|
||||
.paddingVertical(0.75, em)
|
||||
.marginBottom(1, em)
|
||||
|
||||
VStack(() => {}).height(1, px).background("var(--divider)").marginHorizontal(1, em)
|
||||
|
||||
VStack(() => {
|
||||
this.comingSoonRow("🔐", "Permissions")
|
||||
VStack(() => {}).height(1, px).background("var(--divider)")
|
||||
this.comingSoonRow("🔔", "Notifications")
|
||||
VStack(() => {}).height(1, px).background("var(--divider)")
|
||||
this.comingSoonRow("🎨", "Color & Badge")
|
||||
})
|
||||
.paddingHorizontal(1, em)
|
||||
.marginTop(1, em)
|
||||
.opacity(0.4)
|
||||
})
|
||||
.flex(1).overflowY("auto").paddingBottom(2, em)
|
||||
})
|
||||
.height(100, pct).overflow("hidden")
|
||||
}
|
||||
|
||||
backHeader(title, onBack) {
|
||||
HStack(() => {
|
||||
button("‹")
|
||||
.border("none").background("transparent")
|
||||
.color("var(--headertext)").fontSize(1.4, em)
|
||||
.lineHeight("1").paddingVertical(0.2, em).paddingRight(0.3, em)
|
||||
.cursor("pointer").flexShrink(0)
|
||||
.onTap(onBack)
|
||||
p(title)
|
||||
.margin(0).fontSize(1.05, em).fontWeight("700").color("var(--headertext)")
|
||||
.flex(1).overflow("hidden").whiteSpace("nowrap").textOverflow("ellipsis")
|
||||
})
|
||||
.gap(0.25, em).alignItems("center")
|
||||
.paddingHorizontal(0.85, em).paddingTop(1.25, em).paddingBottom(0.85, em)
|
||||
.flexShrink(0)
|
||||
}
|
||||
|
||||
comingSoonRow(icon, label) {
|
||||
HStack(() => {
|
||||
p(icon).margin(0).fontSize(1, em).flexShrink(0)
|
||||
p(label).margin(0).fontSize(0.9, em).color("var(--headertext)").flex(1)
|
||||
p("Coming soon")
|
||||
.margin(0).fontSize(0.9, em).color("var(--headertext)").opacity(0.45)
|
||||
.background("var(--darkaccent)").border("1px solid var(--divider)")
|
||||
.paddingHorizontal(0.5, em).paddingVertical(0.15, em).borderRadius(100, px)
|
||||
})
|
||||
.gap(0.75, em).alignItems("center").paddingVertical(0.85, em)
|
||||
}
|
||||
}
|
||||
|
||||
register(SettingsRolesSection)
|
||||
71
settings/desktop/DesktopIntegrationsSection.js
Normal file
71
settings/desktop/DesktopIntegrationsSection.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import "/_/code/components/LoadingCircle.js"
|
||||
|
||||
class DesktopIntegrationsSection extends Shadow {
|
||||
constructor(stripeDetails, onConnectStripe) {
|
||||
super()
|
||||
this.stripeDetails = stripeDetails
|
||||
this.onConnectStripe = onConnectStripe
|
||||
}
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
VStack(() => {
|
||||
p("Integrations")
|
||||
.margin(0).fontSize(1.35, em).fontWeight("700").color("var(--headertext)")
|
||||
p("Connect third-party services to your network")
|
||||
.margin(0).marginTop(0.2, em).fontSize(0.75, em).color("var(--headertext)").opacity(0.42)
|
||||
})
|
||||
.gap(0)
|
||||
})
|
||||
.paddingHorizontal(1.75, em).paddingTop(1.5, em).paddingBottom(1.25, em)
|
||||
.alignItems("flex-start").flexShrink(0)
|
||||
|
||||
VStack(() => {
|
||||
this.settingSection("💳", "Stripe", "Accept payments and manage subscriptions", () => {
|
||||
if (this.stripeDetails === null) {
|
||||
LoadingCircle()
|
||||
} else if (this.stripeDetails?.email) {
|
||||
HStack(() => {
|
||||
VStack(() => {}).width(8, px).height(8, px).borderRadius(50, pct).background("#10b981").flexShrink(0)
|
||||
p("Connected").margin(0).fontSize(0.82, em).fontWeight("600").color("#10b981")
|
||||
})
|
||||
.gap(0.35, em).alignItems("center").marginBottom(0.3, em)
|
||||
p(this.stripeDetails.email)
|
||||
.margin(0).fontSize(0.75, em).color("var(--headertext)").opacity(0.5)
|
||||
} else {
|
||||
p("Not connected")
|
||||
.margin(0).fontSize(0.82, em).color("var(--headertext)").opacity(0.4).marginBottom(0.65, em)
|
||||
button("Connect Stripe →")
|
||||
.paddingHorizontal(1, em).paddingVertical(0.5, em)
|
||||
.border("none").borderRadius(0.45, em)
|
||||
.background("var(--quillred)").color("white")
|
||||
.fontSize(0.82, em).fontWeight("600").cursor("pointer")
|
||||
.onClick((done) => { if (done) this.onConnectStripe() })
|
||||
}
|
||||
})
|
||||
})
|
||||
.paddingHorizontal(1.75, em).flex(1)
|
||||
})
|
||||
.height(100, pct).overflow("hidden")
|
||||
}
|
||||
|
||||
settingSection(icon, title, subtitle, renderContent) {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
p(icon).margin(0).fontSize(1, em).lineHeight("1").flexShrink(0)
|
||||
VStack(() => {
|
||||
p(title).margin(0).fontSize(0.92, em).fontWeight("700").color("var(--headertext)")
|
||||
p(subtitle).margin(0).marginTop(0.1, em).fontSize(0.72, em).color("var(--headertext)").opacity(0.42)
|
||||
})
|
||||
.gap(0)
|
||||
})
|
||||
.gap(0.65, em).alignItems("flex-start").marginBottom(0.85, em)
|
||||
renderContent()
|
||||
})
|
||||
.padding(1.1, em).background("var(--darkaccent)").border("1px solid var(--divider)")
|
||||
.borderRadius(0.65, em).marginBottom(0.85, em)
|
||||
}
|
||||
}
|
||||
|
||||
register(DesktopIntegrationsSection)
|
||||
243
settings/desktop/DesktopRolesSection.js
Normal file
243
settings/desktop/DesktopRolesSection.js
Normal file
@@ -0,0 +1,243 @@
|
||||
class DesktopRolesSection extends Shadow {
|
||||
constructor(roles, allApps, selectedRoleId, roleApps, confirmDeleteId, onSelectRole, onDeleteRole, onSetConfirmDelete, onCreateRole, onToggleApp) {
|
||||
super()
|
||||
this.roles = roles
|
||||
this.allApps = allApps
|
||||
this.selectedRoleId = selectedRoleId
|
||||
this.roleApps = roleApps
|
||||
this.confirmDeleteId = confirmDeleteId
|
||||
this.onSelectRole = onSelectRole
|
||||
this.onDeleteRole = onDeleteRole
|
||||
this.onSetConfirmDelete = onSetConfirmDelete
|
||||
this.onCreateRole = onCreateRole
|
||||
this.onToggleApp = onToggleApp
|
||||
this.newRoleName = ""
|
||||
}
|
||||
|
||||
get selectedRole() {
|
||||
return this.roles.find(r => r.id === this.selectedRoleId) ?? null
|
||||
}
|
||||
|
||||
get selectedRoleAppIds() {
|
||||
return this.roleApps[this.selectedRoleId] ?? new Set()
|
||||
}
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
VStack(() => {
|
||||
p("Roles & Apps")
|
||||
.margin(0).fontSize(1.35, em).fontWeight("700").color("var(--headertext)")
|
||||
p("Control which apps each role can access")
|
||||
.margin(0).marginTop(0.2, em).fontSize(0.75, em)
|
||||
.color("var(--headertext)").opacity(0.42)
|
||||
})
|
||||
.gap(0).flex(1)
|
||||
})
|
||||
.paddingHorizontal(1.75, em).paddingTop(1.5, em).paddingBottom(1.1, em)
|
||||
.alignItems("flex-start").flexShrink(0)
|
||||
|
||||
HStack(() => {
|
||||
// ── Role list ─────────────────────────────────────────
|
||||
VStack(() => {
|
||||
p("ROLES")
|
||||
.margin(0).marginBottom(0.5, em)
|
||||
.fontSize(0.62, em).fontWeight("700").letterSpacing("0.07em")
|
||||
.color("var(--headertext)").opacity(0.35)
|
||||
|
||||
this.roles.forEach(role => {
|
||||
const isSelected = this.selectedRoleId === role.id
|
||||
const isConfirming = this.confirmDeleteId === role.id
|
||||
const appCount = (this.roleApps[role.id] ?? new Set()).size
|
||||
|
||||
HStack(() => {
|
||||
VStack(() => {
|
||||
p(role.name)
|
||||
.margin(0).fontSize(0.88, em)
|
||||
.fontWeight(isSelected ? "700" : "500")
|
||||
.color("var(--headertext)")
|
||||
.overflow("hidden").whiteSpace("nowrap").textOverflow("ellipsis")
|
||||
p(`${appCount} app${appCount !== 1 ? "s" : ""}`)
|
||||
.margin(0).marginTop(0.12, em).fontSize(0.68, em)
|
||||
.color("var(--headertext)").opacity(0.38)
|
||||
})
|
||||
.flex(1).minWidth(0).gap(0)
|
||||
|
||||
if (isConfirming) {
|
||||
HStack(() => {
|
||||
button("Delete")
|
||||
.padding("0.2em 0.5em").border("none").borderRadius(0.3, em)
|
||||
.background("var(--quillred)").color("white")
|
||||
.fontSize(0.68, em).fontWeight("600").cursor("pointer")
|
||||
.onClick((done) => { if (done) this.onDeleteRole(role.id) })
|
||||
button("✕")
|
||||
.padding("0.2em 0.45em").border("none").borderRadius(0.3, em)
|
||||
.background("transparent").color("var(--headertext)")
|
||||
.opacity(0.4).fontSize(0.72, em).cursor("pointer")
|
||||
.onClick((done) => { if (done) this.onSetConfirmDelete(null) })
|
||||
})
|
||||
.gap(0.3, em).alignItems("center")
|
||||
} else {
|
||||
button("⋯")
|
||||
.border("none").background("transparent")
|
||||
.color("var(--headertext)").opacity(0.3)
|
||||
.fontSize(1, em).cursor("pointer").padding("0.1em 0.35em")
|
||||
.borderRadius(0.3, em)
|
||||
.onClick((done) => { if (done) this.onSetConfirmDelete(role.id) })
|
||||
}
|
||||
})
|
||||
.paddingHorizontal(0.75, em).paddingVertical(0.6, em)
|
||||
.borderRadius(0.5, em)
|
||||
.background(isSelected ? "var(--app)" : "transparent")
|
||||
.border(`1px solid ${isSelected ? "var(--quillred)" : "transparent"}`)
|
||||
.cursor("pointer").alignItems("center")
|
||||
.onClick((done) => { if (done) this.onSelectRole(role.id) })
|
||||
})
|
||||
|
||||
// New role input
|
||||
HStack(() => {
|
||||
p("+").margin(0).fontSize(1, em).color("var(--headertext)").opacity(0.4).flexShrink(0)
|
||||
input("New role name…", "100%")
|
||||
.border("none").background("transparent")
|
||||
.color("var(--headertext)").fontSize(0.85, em).outline("none").flex(1)
|
||||
.onInput(e => { this.newRoleName = e.target.value })
|
||||
.onKeyDown(async (e) => {
|
||||
if (e.key === "Enter" && this.newRoleName.trim()) {
|
||||
await this.onCreateRole(this.newRoleName.trim())
|
||||
this.newRoleName = ""
|
||||
}
|
||||
})
|
||||
})
|
||||
.gap(0.5, em).alignItems("center")
|
||||
.paddingHorizontal(0.75, em).paddingVertical(0.55, em)
|
||||
.borderRadius(0.5, em).border("1px dashed var(--divider)")
|
||||
.marginTop(0.35, em).cursor("text")
|
||||
})
|
||||
.width(220, px).flexShrink(0).paddingHorizontal(1.75, em).gap(0.25, em)
|
||||
|
||||
VStack(() => {}).width(1, px).background("var(--divider)").alignSelf("stretch").flexShrink(0)
|
||||
|
||||
// ── Role detail ───────────────────────────────────────
|
||||
VStack(() => {
|
||||
if (!this.selectedRole) {
|
||||
VStack(() => {
|
||||
p("Select a role to configure it")
|
||||
.margin(0).fontSize(0.88, em)
|
||||
.color("var(--headertext)").opacity(0.35).textAlign("center")
|
||||
})
|
||||
.flex(1).justifyContent("center").alignItems("center")
|
||||
} else {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
p(this.selectedRole.name)
|
||||
.margin(0).fontSize(1.1, em).fontWeight("700").color("var(--headertext)")
|
||||
if (this.selectedRole.is_default) {
|
||||
p("default")
|
||||
.margin(0).fontSize(0.65, em).fontWeight("600")
|
||||
.color("var(--quillred)")
|
||||
.background("rgba(159,28,41,0.1)")
|
||||
.paddingHorizontal(0.55, em).paddingVertical(0.15, em)
|
||||
.borderRadius(100, px)
|
||||
}
|
||||
})
|
||||
.gap(0.65, em).alignItems("center").marginBottom(1.25, em)
|
||||
|
||||
this.settingSection("📱", "App Access", "Choose which apps this role can access", () => {
|
||||
VStack(() => {
|
||||
this.allApps.forEach(app => {
|
||||
const hasApp = this.selectedRoleAppIds.has(app.id)
|
||||
const comingSoonApps = ["tasks", "jobs", "politics", "files"]
|
||||
const isComingSoon = comingSoonApps.includes(app.name)
|
||||
|
||||
HStack(() => {
|
||||
VStack(() => {
|
||||
if (hasApp) {
|
||||
p("✓").margin(0).fontSize(0.6, em).fontWeight("800").color("white").lineHeight("1")
|
||||
}
|
||||
})
|
||||
.width(1.05, em).height(1.05, em).borderRadius(0.25, em)
|
||||
.background(hasApp ? "var(--quillred)" : "transparent")
|
||||
.border(`1.5px solid ${hasApp ? "var(--quillred)" : "var(--divider)"}`)
|
||||
.justifyContent("center").alignItems("center")
|
||||
.boxSizing("border-box").flexShrink(0)
|
||||
|
||||
p(app.name.charAt(0).toUpperCase() + app.name.slice(1))
|
||||
.margin(0).fontSize(0.88, em).color("var(--headertext)")
|
||||
|
||||
if (isComingSoon) {
|
||||
p("coming soon")
|
||||
.margin(0).fontSize(0.62, em).fontWeight("600")
|
||||
.color("var(--headertext)").opacity(0.25)
|
||||
.background("var(--divider)")
|
||||
.paddingHorizontal(0.45, em).paddingVertical(0.12, em).borderRadius(100, px)
|
||||
}
|
||||
})
|
||||
.gap(0.75, em).alignItems("center")
|
||||
.paddingVertical(0.5, em).paddingHorizontal(0.75, em)
|
||||
.borderRadius(0.45, em)
|
||||
.background("var(--darkaccent)").border("1px solid var(--divider)")
|
||||
.cursor(isComingSoon ? "default" : "pointer")
|
||||
.opacity(isComingSoon ? 0.5 : 1)
|
||||
.onClick((done) => { if (!isComingSoon && done) this.onToggleApp(this.selectedRoleId, app.id, !hasApp) })
|
||||
})
|
||||
})
|
||||
.gap(0.45, em)
|
||||
})
|
||||
|
||||
this.comingSoonSection("🔐", "Permissions", "Fine-grained action-level permissions per app")
|
||||
this.comingSoonSection("🔔", "Notifications", "Configure default notification settings")
|
||||
this.comingSoonSection("🎨", "Color & Badge", "Assign a display color and badge label")
|
||||
})
|
||||
.paddingHorizontal(1.75, em).paddingBottom(1.75, em).paddingTop(0.5, em).gap(0)
|
||||
}
|
||||
})
|
||||
.flex(1).minWidth(0).overflowY("auto").height(100, pct)
|
||||
})
|
||||
.flex(1).minHeight(0).alignItems("flex-start").overflow("hidden")
|
||||
})
|
||||
.height(100, pct).overflow("hidden")
|
||||
}
|
||||
|
||||
settingSection(icon, title, subtitle, renderContent) {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
p(icon).margin(0).fontSize(1, em).lineHeight("1").flexShrink(0)
|
||||
VStack(() => {
|
||||
p(title).margin(0).fontSize(0.92, em).fontWeight("700").color("var(--headertext)")
|
||||
p(subtitle).margin(0).marginTop(0.1, em).fontSize(0.72, em).color("var(--headertext)").opacity(0.42)
|
||||
})
|
||||
.gap(0)
|
||||
})
|
||||
.gap(0.65, em).alignItems("flex-start").marginBottom(0.85, em)
|
||||
renderContent()
|
||||
})
|
||||
.padding(1.1, em).background("var(--darkaccent)").border("1px solid var(--divider)")
|
||||
.borderRadius(0.65, em).marginBottom(0.85, em)
|
||||
}
|
||||
|
||||
comingSoonSection(icon, title, subtitle) {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
p(icon).margin(0).fontSize(1, em).lineHeight("1").flexShrink(0).opacity(0.35)
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
p(title).margin(0).fontSize(0.92, em).fontWeight("700").color("var(--headertext)").opacity(0.35)
|
||||
p("coming soon")
|
||||
.margin(0).fontSize(0.62, em).fontWeight("600")
|
||||
.color("var(--headertext)").opacity(0.25)
|
||||
.background("var(--divider)")
|
||||
.paddingHorizontal(0.45, em).paddingVertical(0.12, em).borderRadius(100, px)
|
||||
})
|
||||
.gap(0.55, em).alignItems("center")
|
||||
p(subtitle).margin(0).marginTop(0.1, em).fontSize(0.72, em).color("var(--headertext)").opacity(0.28)
|
||||
})
|
||||
.gap(0)
|
||||
})
|
||||
.gap(0.65, em).alignItems("flex-start")
|
||||
})
|
||||
.padding(1.1, em).border("1px solid var(--divider)").borderRadius(0.65, em)
|
||||
.marginBottom(0.85, em).opacity(0.6)
|
||||
}
|
||||
}
|
||||
|
||||
register(DesktopRolesSection)
|
||||
95
settings/desktop/DesktopSettingsSidebar.js
Normal file
95
settings/desktop/DesktopSettingsSidebar.js
Normal file
@@ -0,0 +1,95 @@
|
||||
class DesktopSettingsSidebar extends Shadow {
|
||||
constructor(activeSection, onSectionChange) {
|
||||
super()
|
||||
this.activeSection = activeSection
|
||||
this.onSectionChange = onSectionChange
|
||||
}
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
VStack(() => {
|
||||
p("Settings")
|
||||
.fontFamily("Laandbrau")
|
||||
.margin(0).fontSize(1.8, em).fontWeight("700").color("var(--headertext)")
|
||||
p(global.currentNetwork.name)
|
||||
.margin(0).marginTop(0.2, em).fontSize(0.72, em)
|
||||
.color("var(--headertext)").opacity(0.38)
|
||||
})
|
||||
.marginTop(30, px)
|
||||
.paddingHorizontal(1.1, em).paddingTop(1.2, em).paddingBottom(0.9, em)
|
||||
|
||||
VStack(() => {}).height(1, px).background("var(--divider)").marginHorizontal(1, em)
|
||||
|
||||
VStack(() => {
|
||||
this.sectionLabel("MANAGE")
|
||||
this.navItem("🎭", "Roles & Apps", "roles")
|
||||
})
|
||||
.paddingTop(0.75, em)
|
||||
|
||||
VStack(() => {}).height(1, px).background("var(--divider)").marginHorizontal(1, em).marginVertical(0.5, em)
|
||||
|
||||
VStack(() => {
|
||||
this.sectionLabel("INTEGRATIONS")
|
||||
this.navItem("💳", "Stripe", "integrations")
|
||||
})
|
||||
|
||||
VStack(() => {}).flex(1)
|
||||
|
||||
VStack(() => {
|
||||
p("NETWORK")
|
||||
.margin(0).marginBottom(0.4, em)
|
||||
.fontSize(0.6, em).fontWeight("700").letterSpacing("0.07em")
|
||||
.color("var(--headertext)").opacity(0.35)
|
||||
|
||||
VStack(() => {
|
||||
this.infoRow("ID", String(global.currentNetwork.id))
|
||||
this.infoRow("Slug", global.currentNetwork.abbreviation)
|
||||
})
|
||||
.background("var(--darkaccent)").border("1px solid var(--divider)")
|
||||
.borderRadius(0.5, em).gap(0)
|
||||
})
|
||||
.paddingHorizontal(0.75, em).paddingBottom(1.1, em).flexShrink(0)
|
||||
})
|
||||
.width(230, px).height(100, pct)
|
||||
.borderRight("1px solid var(--divider)")
|
||||
.flexShrink(0).overflowY("auto").boxSizing("border-box")
|
||||
}
|
||||
|
||||
navItem(icon, label, sectionId) {
|
||||
const isActive = this.activeSection === sectionId
|
||||
HStack(() => {
|
||||
p(icon).margin(0).fontSize(0.9, em).lineHeight("1").flexShrink(0)
|
||||
p(label).margin(0).fontSize(0.88, em)
|
||||
.fontWeight(isActive ? "600" : "400")
|
||||
.color("var(--headertext)").opacity(isActive ? 1 : 0.65)
|
||||
})
|
||||
.gap(0.62, em).paddingHorizontal(0.85, em).paddingVertical(0.45, em)
|
||||
.marginHorizontal(0.4, em).borderRadius(0.45, em)
|
||||
.background(isActive ? "var(--app)" : "transparent")
|
||||
.cursor("pointer").alignItems("center")
|
||||
.width("calc(100% - 0.8em)").boxSizing("border-box")
|
||||
.onClick((done) => { if (done) this.onSectionChange(sectionId) })
|
||||
}
|
||||
|
||||
sectionLabel(text) {
|
||||
p(text)
|
||||
.margin(0).marginBottom(0.28, em).paddingHorizontal(1.1, em)
|
||||
.fontSize(0.62, em).fontWeight("700").letterSpacing("0.07em")
|
||||
.color("var(--headertext)").opacity(0.35)
|
||||
}
|
||||
|
||||
infoRow(label, value) {
|
||||
HStack(() => {
|
||||
p(label)
|
||||
.margin(0).fontSize(0.68, em).color("var(--headertext)").opacity(0.4)
|
||||
.width(4, em).flexShrink(0)
|
||||
p(value)
|
||||
.margin(0).fontSize(0.72, em).fontWeight("600").color("var(--headertext)")
|
||||
})
|
||||
.paddingVertical(0.3, em).paddingHorizontal(0.65, em)
|
||||
.borderBottom("1px solid var(--divider)")
|
||||
.alignItems("center").gap(0.4, em)
|
||||
}
|
||||
}
|
||||
|
||||
register(DesktopSettingsSidebar)
|
||||
149
settings/desktop/settings.js
Normal file
149
settings/desktop/settings.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import server from "/@server/server.js"
|
||||
import env from "/_/code/env.js"
|
||||
import "/_/code/components/LoadingCircle.js"
|
||||
import "./DesktopSettingsSidebar.js"
|
||||
import "./DesktopRolesSection.js"
|
||||
import "./DesktopIntegrationsSection.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 {
|
||||
activeSection = "roles"
|
||||
loaded = false
|
||||
|
||||
roles = []
|
||||
allApps = []
|
||||
selectedRoleId = null
|
||||
roleApps = {}
|
||||
confirmDeleteId = null
|
||||
stripeDetails = null
|
||||
|
||||
render() {
|
||||
HStack(() => {
|
||||
DesktopSettingsSidebar(this.activeSection, (section) => {
|
||||
this.activeSection = section
|
||||
this.rerender()
|
||||
})
|
||||
|
||||
VStack(() => {
|
||||
if (!this.loaded) {
|
||||
VStack(() => LoadingCircle())
|
||||
.flex(1)
|
||||
.justifyContent("center")
|
||||
.alignItems("center")
|
||||
} else if (this.activeSection === "roles")
|
||||
DesktopRolesSection(
|
||||
this.roles,
|
||||
this.allApps,
|
||||
this.selectedRoleId,
|
||||
this.roleApps,
|
||||
this.confirmDeleteId,
|
||||
(id) => { this.selectedRoleId = id; this.confirmDeleteId = null; this.rerender() },
|
||||
(id) => this.deleteRole(id),
|
||||
(id) => { this.confirmDeleteId = id; this.rerender() },
|
||||
(name) => this.createRole(name),
|
||||
(roleId, appId, add) => this.toggleRoleApp(roleId, appId, add)
|
||||
)
|
||||
else if (this.activeSection === "integrations")
|
||||
DesktopIntegrationsSection(this.stripeDetails, () => this.handleConnectStripe())
|
||||
})
|
||||
.flex(1).height(100, pct).overflowY("auto")
|
||||
})
|
||||
.height(100, pct).width(100, pct).overflow("hidden")
|
||||
.onAppear(async () => {
|
||||
if (this.loaded) return
|
||||
await this.loadRoles()
|
||||
this.stripeDetails = await server.getStripeProfile(global.currentNetwork.id)
|
||||
this.rerender()
|
||||
})
|
||||
}
|
||||
|
||||
// ── Server actions ────────────────────────────────────────────────
|
||||
|
||||
async loadRoles() {
|
||||
const [roles, apps] = await Promise.all([
|
||||
server.getRoles(global.currentNetwork.id),
|
||||
server.getAllApps()
|
||||
])
|
||||
|
||||
this.roles = Array.isArray(roles) ? roles : []
|
||||
this.allApps = Array.isArray(apps) ? apps : []
|
||||
this.selectedRoleId = this.roles[0]?.id ?? null
|
||||
|
||||
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.selectedRoleId = result.role.id
|
||||
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]
|
||||
this.selectedRoleId = this.roles[0]?.id ?? null
|
||||
this.confirmDeleteId = null
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
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("app-menu")?.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)
|
||||
3
settings/icons/settings.svg
Normal file
3
settings/icons/settings.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.3 KiB |
3
settings/icons/settingslight.svg
Normal file
3
settings/icons/settingslight.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.3 KiB |
3
settings/icons/settingslightselected.svg
Normal file
3
settings/icons/settingslightselected.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.2 KiB |
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