default ui is not crap
@@ -42,6 +42,7 @@ Please don't hitch your wagon to this star just yet... I'm coding this in the op
|
||||
- [x] Gen/Regen an invite link to allow users to join a team
|
||||
- [x] Team administrators and owners can accept pending invites
|
||||
- [x] Team administrators and owners can administer the permissions (roles) of other team members on the Accounts page
|
||||
- [ ] Team owners can remove users from team
|
||||
|
||||
### Plans and Pricing
|
||||
- [x] Manage multiple Plans each with specific Feature flags and Plan limits
|
||||
@@ -60,8 +61,7 @@ Please don't hitch your wagon to this star just yet... I'm coding this in the op
|
||||
- [ ] Knowledge base with FAQs and tutorials
|
||||
|
||||
### Look and Feel, Design System and Customisation
|
||||
- [x] Very Crap default UI
|
||||
- [ ] Not Crap UI
|
||||
- [x] Default UI isn't too crap
|
||||
- [x] Integrated Design system including theming (Tailwind + daisyUI)
|
||||
- [ ] Branding options (logo, color scheme, etc.)
|
||||
|
||||
|
||||
BIN
assets/images/landing_config_environment.jpeg
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
assets/images/landing_db_schema_management.jpeg
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
assets/images/landing_state_management.jpeg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
assets/images/landing_stripe_integration.jpeg
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
assets/images/landing_style_system.jpeg
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
assets/images/landing_user_management.jpeg
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
assets/images/saas_landing_main.jpeg
Normal file
|
After Width: | Height: | Size: 57 KiB |
@@ -32,7 +32,7 @@
|
||||
<li v-if="!user"><NuxtLink to="/signin">Sign In</NuxtLink></li>
|
||||
</ul>
|
||||
</div>
|
||||
<NuxtLink to="/" class="btn btn-ghost normal-case text-xl">Nuxt3 SAAS Bootstrap</NuxtLink>
|
||||
<NuxtLink to="/" class="btn btn-ghost normal-case text-xl">Nuxt3 SAAS Boilerplate</NuxtLink>
|
||||
</div>
|
||||
<div class="navbar-center hidden lg:flex">
|
||||
<ul class="menu menu-horizontal px-1">
|
||||
|
||||
@@ -7,7 +7,7 @@ export default defineNuxtConfig({
|
||||
typescript: {
|
||||
shim: false
|
||||
},
|
||||
modules: ['@nuxtjs/supabase', '@pinia/nuxt', '@nuxtjs/tailwindcss'],
|
||||
modules: ['@nuxtjs/supabase', '@pinia/nuxt', '@nuxtjs/tailwindcss', 'nuxt-icon'],
|
||||
imports: {
|
||||
dirs: ['./stores'],
|
||||
},
|
||||
|
||||
495
package-lock.json
generated
@@ -24,6 +24,7 @@
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/node": "^18.15.11",
|
||||
"nuxt": "^3.1.1",
|
||||
"nuxt-icon": "^0.3.3",
|
||||
"prisma": "^4.9.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.3"
|
||||
@@ -967,6 +968,27 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/types": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
||||
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@iconify/vue": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-4.1.1.tgz",
|
||||
"integrity": "sha512-RL85Bm/DAe8y6rT6pux7D2FJSiUEM/TPfyK7GrbAOfTSwrhvwJW+S5yijdGcmtXouA8MtuH9C7l4hiSE4mLMjg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@iconify/types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/cyberalien"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/@ioredis/commands": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
|
||||
@@ -3113,18 +3135,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/c12": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-1.2.0.tgz",
|
||||
"integrity": "sha512-CMznkE0LpNEuD8ILp5QvsQVP+YvcpJnrI/zFeFnosU2PyDtx1wT7tXfZ8S3Tl3l9MTTXbKeuhDYKwgvnAPOx3w==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-1.4.1.tgz",
|
||||
"integrity": "sha512-0x7pWfLZpZsgtyotXtuepJc0rZYE0Aw8PwNAXs0jSG9zq6Sl5xmbWnFqfmRY01ieZLHNbvneSFm9/x88CvzAuw==",
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.3",
|
||||
"defu": "^6.1.2",
|
||||
"dotenv": "^16.0.3",
|
||||
"giget": "^1.1.2",
|
||||
"jiti": "^1.17.2",
|
||||
"jiti": "^1.18.2",
|
||||
"mlly": "^1.2.0",
|
||||
"ohash": "^1.1.1",
|
||||
"pathe": "^1.1.0",
|
||||
"perfect-debounce": "^0.1.3",
|
||||
"pkg-types": "^1.0.2",
|
||||
"rc9": "^2.0.1"
|
||||
"rc9": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cac": {
|
||||
@@ -6828,6 +6853,202 @@
|
||||
"node": "^14.16.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-config-schema": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/nuxt-config-schema/-/nuxt-config-schema-0.4.6.tgz",
|
||||
"integrity": "sha512-kHLWJFynj5QrxVZ1MjY2xmDaTSN1BCMLGExA+hMMLoCb3wn9TJlDVqnE/nSdUJPMRkNn/NQ5WP9NLA9vlAXRUw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "^3.4.2",
|
||||
"defu": "^6.1.2",
|
||||
"jiti": "^1.18.2",
|
||||
"pathe": "^1.0.0",
|
||||
"untyped": "^1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-config-schema/node_modules/@nuxt/kit": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.4.2.tgz",
|
||||
"integrity": "sha512-bFUpkyG2ZF6RYqiW+tXnWssccHQQqMF4kZJJLP/0eKXf+Fkt/Is0R7IY768jy8ylnyqeMBbmpg4Zv5gSZjfZQw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@nuxt/schema": "3.4.2",
|
||||
"c12": "^1.4.1",
|
||||
"consola": "^3.1.0",
|
||||
"defu": "^6.1.2",
|
||||
"globby": "^13.1.4",
|
||||
"hash-sum": "^2.0.0",
|
||||
"ignore": "^5.2.4",
|
||||
"jiti": "^1.18.2",
|
||||
"knitwork": "^1.0.0",
|
||||
"lodash.template": "^4.5.0",
|
||||
"mlly": "^1.2.0",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.2",
|
||||
"scule": "^1.0.0",
|
||||
"semver": "^7.5.0",
|
||||
"unctx": "^2.3.0",
|
||||
"unimport": "^3.0.6",
|
||||
"untyped": "^1.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-config-schema/node_modules/@nuxt/schema": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.4.2.tgz",
|
||||
"integrity": "sha512-DXB/fyjrAssFt9KGXyS+ZSfm1A0NYKhEoc01wyz1lGo//oETzUh3MmwE6X3x65NPqDlYZ6Mnj+IdftRRophv5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"defu": "^6.1.2",
|
||||
"hookable": "^5.5.3",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.2",
|
||||
"postcss-import-resolver": "^2.0.0",
|
||||
"std-env": "^3.3.2",
|
||||
"ufo": "^1.1.1",
|
||||
"unimport": "^3.0.6",
|
||||
"untyped": "^1.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-config-schema/node_modules/consola": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-3.1.0.tgz",
|
||||
"integrity": "sha512-rrrJE6rP0qzl/Srg+C9x/AE5Kxfux7reVm1Wh0wCjuXvih6DqZgqDZe8auTD28fzJ9TF0mHlSDrPpWlujQRo1Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nuxt-config-schema/node_modules/magic-string": {
|
||||
"version": "0.30.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
|
||||
"integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.13"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-config-schema/node_modules/unimport": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/unimport/-/unimport-3.0.6.tgz",
|
||||
"integrity": "sha512-GYxGJ1Bri1oqx8VFDjdgooGzeK7jBk3bvhXmamTIpu3nONOcUMGwZbX7X0L5RA7OWMXpR4vzpSQP7pXUzJg1/Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.2",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"fast-glob": "^3.2.12",
|
||||
"local-pkg": "^0.4.3",
|
||||
"magic-string": "^0.30.0",
|
||||
"mlly": "^1.2.0",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.2",
|
||||
"scule": "^1.0.0",
|
||||
"strip-literal": "^1.0.1",
|
||||
"unplugin": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-icon": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/nuxt-icon/-/nuxt-icon-0.3.3.tgz",
|
||||
"integrity": "sha512-KdhJAigBGTP8/YIFZ3orwetk40AgLq6VQ5HRYuDLmv5hiDptor9Ro+WIdZggHw7nciRxZvDdQkEwi9B5G/jrkQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^4.1.0",
|
||||
"@nuxt/kit": "^3.3.1",
|
||||
"nuxt-config-schema": "^0.4.5"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-icon/node_modules/@nuxt/kit": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.4.2.tgz",
|
||||
"integrity": "sha512-bFUpkyG2ZF6RYqiW+tXnWssccHQQqMF4kZJJLP/0eKXf+Fkt/Is0R7IY768jy8ylnyqeMBbmpg4Zv5gSZjfZQw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@nuxt/schema": "3.4.2",
|
||||
"c12": "^1.4.1",
|
||||
"consola": "^3.1.0",
|
||||
"defu": "^6.1.2",
|
||||
"globby": "^13.1.4",
|
||||
"hash-sum": "^2.0.0",
|
||||
"ignore": "^5.2.4",
|
||||
"jiti": "^1.18.2",
|
||||
"knitwork": "^1.0.0",
|
||||
"lodash.template": "^4.5.0",
|
||||
"mlly": "^1.2.0",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.2",
|
||||
"scule": "^1.0.0",
|
||||
"semver": "^7.5.0",
|
||||
"unctx": "^2.3.0",
|
||||
"unimport": "^3.0.6",
|
||||
"untyped": "^1.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-icon/node_modules/@nuxt/schema": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.4.2.tgz",
|
||||
"integrity": "sha512-DXB/fyjrAssFt9KGXyS+ZSfm1A0NYKhEoc01wyz1lGo//oETzUh3MmwE6X3x65NPqDlYZ6Mnj+IdftRRophv5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"defu": "^6.1.2",
|
||||
"hookable": "^5.5.3",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.2",
|
||||
"postcss-import-resolver": "^2.0.0",
|
||||
"std-env": "^3.3.2",
|
||||
"ufo": "^1.1.1",
|
||||
"unimport": "^3.0.6",
|
||||
"untyped": "^1.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-icon/node_modules/consola": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-3.1.0.tgz",
|
||||
"integrity": "sha512-rrrJE6rP0qzl/Srg+C9x/AE5Kxfux7reVm1Wh0wCjuXvih6DqZgqDZe8auTD28fzJ9TF0mHlSDrPpWlujQRo1Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nuxt-icon/node_modules/magic-string": {
|
||||
"version": "0.30.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
|
||||
"integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.13"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-icon/node_modules/unimport": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/unimport/-/unimport-3.0.6.tgz",
|
||||
"integrity": "sha512-GYxGJ1Bri1oqx8VFDjdgooGzeK7jBk3bvhXmamTIpu3nONOcUMGwZbX7X0L5RA7OWMXpR4vzpSQP7pXUzJg1/Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.2",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"fast-glob": "^3.2.12",
|
||||
"local-pkg": "^0.4.3",
|
||||
"magic-string": "^0.30.0",
|
||||
"mlly": "^1.2.0",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.2",
|
||||
"scule": "^1.0.0",
|
||||
"strip-literal": "^1.0.1",
|
||||
"unplugin": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -6863,9 +7084,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ohash": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.0.0.tgz",
|
||||
"integrity": "sha512-kxSyzq6tt+6EE/xCnD1XaFhCCjUNUaz3X30rJp6mnjGLXAAvuPFqohMdv0aScWzajR45C29HyBaXZ8jXBwnh9A=="
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.2.tgz",
|
||||
"integrity": "sha512-9CIOSq5945rI045GFtcO3uudyOkYVY1nyfFxVQp+9BRgslr8jPNiSSrsFGg/BNTUFOLqx0P5tng6G32brIPw0w=="
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
@@ -7086,8 +7307,7 @@
|
||||
"node_modules/perfect-debounce": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-0.1.3.tgz",
|
||||
"integrity": "sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ=="
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
@@ -8065,9 +8285,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rc9": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.0.1.tgz",
|
||||
"integrity": "sha512-9EfjLgNmzP9255YX8bGnILQcmdtOXKtUlFTu8bOZPJVtaUDZ2imswcUdpK51tMjTRQyB7r5RebNijrzuyGXcVA==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.0.tgz",
|
||||
"integrity": "sha512-ROO9bv8PPqngWKoiUZU3JDQ4sugpdRs9DfwHnzDSxK25XtQn6BEHL6EOd/OtKuDT2qodrtNR+0WkPT6l0jxH5Q==",
|
||||
"dependencies": {
|
||||
"defu": "^6.1.2",
|
||||
"destr": "^1.2.2",
|
||||
@@ -8578,9 +8798,9 @@
|
||||
"integrity": "sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz",
|
||||
"integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==",
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
|
||||
"integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
@@ -9605,9 +9825,9 @@
|
||||
"integrity": "sha512-kuZwRKV615lEw/Xx3Iz56FKk3nOeOVGaVmw0eg+x4Mne28lCotNFbBhDW7dEBCBKyKbRQiCadEZeNAFPVC5cgw=="
|
||||
},
|
||||
"node_modules/unctx": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/unctx/-/unctx-2.2.0.tgz",
|
||||
"integrity": "sha512-th8S0zg9m35lirV7FYI6AYMKHfmLoEGC87yjuS4MGLS/OZ3Pv1Qx+HyXtnlwteL2YL47xN1ADDKoFWYw3VZoEA==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/unctx/-/unctx-2.3.0.tgz",
|
||||
"integrity": "sha512-xs79V1T5JEQ/5aQ3j4ipbQEaReMosMz/ktOdsZMEtKv1PfbdRrKY/PaU0CxdspkX3zEink2keQU4nRzAXgui1A==",
|
||||
"dependencies": {
|
||||
"acorn": "^8.8.2",
|
||||
"estree-walker": "^3.0.3",
|
||||
@@ -11579,6 +11799,21 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@iconify/types": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
||||
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
|
||||
"dev": true
|
||||
},
|
||||
"@iconify/vue": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-4.1.1.tgz",
|
||||
"integrity": "sha512-RL85Bm/DAe8y6rT6pux7D2FJSiUEM/TPfyK7GrbAOfTSwrhvwJW+S5yijdGcmtXouA8MtuH9C7l4hiSE4mLMjg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@iconify/types": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@ioredis/commands": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
|
||||
@@ -13315,18 +13550,21 @@
|
||||
"dev": true
|
||||
},
|
||||
"c12": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-1.2.0.tgz",
|
||||
"integrity": "sha512-CMznkE0LpNEuD8ILp5QvsQVP+YvcpJnrI/zFeFnosU2PyDtx1wT7tXfZ8S3Tl3l9MTTXbKeuhDYKwgvnAPOx3w==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-1.4.1.tgz",
|
||||
"integrity": "sha512-0x7pWfLZpZsgtyotXtuepJc0rZYE0Aw8PwNAXs0jSG9zq6Sl5xmbWnFqfmRY01ieZLHNbvneSFm9/x88CvzAuw==",
|
||||
"requires": {
|
||||
"chokidar": "^3.5.3",
|
||||
"defu": "^6.1.2",
|
||||
"dotenv": "^16.0.3",
|
||||
"giget": "^1.1.2",
|
||||
"jiti": "^1.17.2",
|
||||
"jiti": "^1.18.2",
|
||||
"mlly": "^1.2.0",
|
||||
"ohash": "^1.1.1",
|
||||
"pathe": "^1.1.0",
|
||||
"perfect-debounce": "^0.1.3",
|
||||
"pkg-types": "^1.0.2",
|
||||
"rc9": "^2.0.1"
|
||||
"rc9": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"cac": {
|
||||
@@ -16132,6 +16370,188 @@
|
||||
"vue-router": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"nuxt-config-schema": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/nuxt-config-schema/-/nuxt-config-schema-0.4.6.tgz",
|
||||
"integrity": "sha512-kHLWJFynj5QrxVZ1MjY2xmDaTSN1BCMLGExA+hMMLoCb3wn9TJlDVqnE/nSdUJPMRkNn/NQ5WP9NLA9vlAXRUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@nuxt/kit": "^3.4.2",
|
||||
"defu": "^6.1.2",
|
||||
"jiti": "^1.18.2",
|
||||
"pathe": "^1.0.0",
|
||||
"untyped": "^1.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/kit": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.4.2.tgz",
|
||||
"integrity": "sha512-bFUpkyG2ZF6RYqiW+tXnWssccHQQqMF4kZJJLP/0eKXf+Fkt/Is0R7IY768jy8ylnyqeMBbmpg4Zv5gSZjfZQw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@nuxt/schema": "3.4.2",
|
||||
"c12": "^1.4.1",
|
||||
"consola": "^3.1.0",
|
||||
"defu": "^6.1.2",
|
||||
"globby": "^13.1.4",
|
||||
"hash-sum": "^2.0.0",
|
||||
"ignore": "^5.2.4",
|
||||
"jiti": "^1.18.2",
|
||||
"knitwork": "^1.0.0",
|
||||
"lodash.template": "^4.5.0",
|
||||
"mlly": "^1.2.0",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.2",
|
||||
"scule": "^1.0.0",
|
||||
"semver": "^7.5.0",
|
||||
"unctx": "^2.3.0",
|
||||
"unimport": "^3.0.6",
|
||||
"untyped": "^1.3.2"
|
||||
}
|
||||
},
|
||||
"@nuxt/schema": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.4.2.tgz",
|
||||
"integrity": "sha512-DXB/fyjrAssFt9KGXyS+ZSfm1A0NYKhEoc01wyz1lGo//oETzUh3MmwE6X3x65NPqDlYZ6Mnj+IdftRRophv5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"defu": "^6.1.2",
|
||||
"hookable": "^5.5.3",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.2",
|
||||
"postcss-import-resolver": "^2.0.0",
|
||||
"std-env": "^3.3.2",
|
||||
"ufo": "^1.1.1",
|
||||
"unimport": "^3.0.6",
|
||||
"untyped": "^1.3.2"
|
||||
}
|
||||
},
|
||||
"consola": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-3.1.0.tgz",
|
||||
"integrity": "sha512-rrrJE6rP0qzl/Srg+C9x/AE5Kxfux7reVm1Wh0wCjuXvih6DqZgqDZe8auTD28fzJ9TF0mHlSDrPpWlujQRo1Q==",
|
||||
"dev": true
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.30.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
|
||||
"integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.13"
|
||||
}
|
||||
},
|
||||
"unimport": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/unimport/-/unimport-3.0.6.tgz",
|
||||
"integrity": "sha512-GYxGJ1Bri1oqx8VFDjdgooGzeK7jBk3bvhXmamTIpu3nONOcUMGwZbX7X0L5RA7OWMXpR4vzpSQP7pXUzJg1/Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^5.0.2",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"fast-glob": "^3.2.12",
|
||||
"local-pkg": "^0.4.3",
|
||||
"magic-string": "^0.30.0",
|
||||
"mlly": "^1.2.0",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.2",
|
||||
"scule": "^1.0.0",
|
||||
"strip-literal": "^1.0.1",
|
||||
"unplugin": "^1.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nuxt-icon": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/nuxt-icon/-/nuxt-icon-0.3.3.tgz",
|
||||
"integrity": "sha512-KdhJAigBGTP8/YIFZ3orwetk40AgLq6VQ5HRYuDLmv5hiDptor9Ro+WIdZggHw7nciRxZvDdQkEwi9B5G/jrkQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@iconify/vue": "^4.1.0",
|
||||
"@nuxt/kit": "^3.3.1",
|
||||
"nuxt-config-schema": "^0.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/kit": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.4.2.tgz",
|
||||
"integrity": "sha512-bFUpkyG2ZF6RYqiW+tXnWssccHQQqMF4kZJJLP/0eKXf+Fkt/Is0R7IY768jy8ylnyqeMBbmpg4Zv5gSZjfZQw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@nuxt/schema": "3.4.2",
|
||||
"c12": "^1.4.1",
|
||||
"consola": "^3.1.0",
|
||||
"defu": "^6.1.2",
|
||||
"globby": "^13.1.4",
|
||||
"hash-sum": "^2.0.0",
|
||||
"ignore": "^5.2.4",
|
||||
"jiti": "^1.18.2",
|
||||
"knitwork": "^1.0.0",
|
||||
"lodash.template": "^4.5.0",
|
||||
"mlly": "^1.2.0",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.2",
|
||||
"scule": "^1.0.0",
|
||||
"semver": "^7.5.0",
|
||||
"unctx": "^2.3.0",
|
||||
"unimport": "^3.0.6",
|
||||
"untyped": "^1.3.2"
|
||||
}
|
||||
},
|
||||
"@nuxt/schema": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.4.2.tgz",
|
||||
"integrity": "sha512-DXB/fyjrAssFt9KGXyS+ZSfm1A0NYKhEoc01wyz1lGo//oETzUh3MmwE6X3x65NPqDlYZ6Mnj+IdftRRophv5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"defu": "^6.1.2",
|
||||
"hookable": "^5.5.3",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.2",
|
||||
"postcss-import-resolver": "^2.0.0",
|
||||
"std-env": "^3.3.2",
|
||||
"ufo": "^1.1.1",
|
||||
"unimport": "^3.0.6",
|
||||
"untyped": "^1.3.2"
|
||||
}
|
||||
},
|
||||
"consola": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-3.1.0.tgz",
|
||||
"integrity": "sha512-rrrJE6rP0qzl/Srg+C9x/AE5Kxfux7reVm1Wh0wCjuXvih6DqZgqDZe8auTD28fzJ9TF0mHlSDrPpWlujQRo1Q==",
|
||||
"dev": true
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.30.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
|
||||
"integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.13"
|
||||
}
|
||||
},
|
||||
"unimport": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/unimport/-/unimport-3.0.6.tgz",
|
||||
"integrity": "sha512-GYxGJ1Bri1oqx8VFDjdgooGzeK7jBk3bvhXmamTIpu3nONOcUMGwZbX7X0L5RA7OWMXpR4vzpSQP7pXUzJg1/Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^5.0.2",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"fast-glob": "^3.2.12",
|
||||
"local-pkg": "^0.4.3",
|
||||
"magic-string": "^0.30.0",
|
||||
"mlly": "^1.2.0",
|
||||
"pathe": "^1.1.0",
|
||||
"pkg-types": "^1.0.2",
|
||||
"scule": "^1.0.0",
|
||||
"strip-literal": "^1.0.1",
|
||||
"unplugin": "^1.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -16158,9 +16578,9 @@
|
||||
}
|
||||
},
|
||||
"ohash": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.0.0.tgz",
|
||||
"integrity": "sha512-kxSyzq6tt+6EE/xCnD1XaFhCCjUNUaz3X30rJp6mnjGLXAAvuPFqohMdv0aScWzajR45C29HyBaXZ8jXBwnh9A=="
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.2.tgz",
|
||||
"integrity": "sha512-9CIOSq5945rI045GFtcO3uudyOkYVY1nyfFxVQp+9BRgslr8jPNiSSrsFGg/BNTUFOLqx0P5tng6G32brIPw0w=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.4.1",
|
||||
@@ -16326,8 +16746,7 @@
|
||||
"perfect-debounce": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-0.1.3.tgz",
|
||||
"integrity": "sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ=="
|
||||
},
|
||||
"picocolors": {
|
||||
"version": "1.0.0",
|
||||
@@ -16937,9 +17356,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"rc9": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.0.1.tgz",
|
||||
"integrity": "sha512-9EfjLgNmzP9255YX8bGnILQcmdtOXKtUlFTu8bOZPJVtaUDZ2imswcUdpK51tMjTRQyB7r5RebNijrzuyGXcVA==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.0.tgz",
|
||||
"integrity": "sha512-ROO9bv8PPqngWKoiUZU3JDQ4sugpdRs9DfwHnzDSxK25XtQn6BEHL6EOd/OtKuDT2qodrtNR+0WkPT6l0jxH5Q==",
|
||||
"requires": {
|
||||
"defu": "^6.1.2",
|
||||
"destr": "^1.2.2",
|
||||
@@ -17300,9 +17719,9 @@
|
||||
"integrity": "sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz",
|
||||
"integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==",
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
|
||||
"integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
@@ -18069,9 +18488,9 @@
|
||||
"integrity": "sha512-kuZwRKV615lEw/Xx3Iz56FKk3nOeOVGaVmw0eg+x4Mne28lCotNFbBhDW7dEBCBKyKbRQiCadEZeNAFPVC5cgw=="
|
||||
},
|
||||
"unctx": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/unctx/-/unctx-2.2.0.tgz",
|
||||
"integrity": "sha512-th8S0zg9m35lirV7FYI6AYMKHfmLoEGC87yjuS4MGLS/OZ3Pv1Qx+HyXtnlwteL2YL47xN1ADDKoFWYw3VZoEA==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/unctx/-/unctx-2.3.0.tgz",
|
||||
"integrity": "sha512-xs79V1T5JEQ/5aQ3j4ipbQEaReMosMz/ktOdsZMEtKv1PfbdRrKY/PaU0CxdspkX3zEink2keQU4nRzAXgui1A==",
|
||||
"requires": {
|
||||
"acorn": "^8.8.2",
|
||||
"estree-walker": "^3.0.3",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/node": "^18.15.11",
|
||||
"nuxt": "^3.1.1",
|
||||
"nuxt-icon": "^0.3.3",
|
||||
"prisma": "^4.9.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.3"
|
||||
|
||||
@@ -24,41 +24,68 @@
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<h3>Account</h3>
|
||||
<p>Name: {{ activeMembership?.account.name }} <span v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)"><input v-model="newAccountName" placeholder="Enter New Name"/><button @click.prevent="accountStore.changeAccountName(newAccountName)">Change Name</button></span></p>
|
||||
<p>Current Period Ends: {{ formatDate(activeMembership?.account.current_period_ends) }}</p>
|
||||
<p>Permitted Features: {{ activeMembership?.account.features }}</p>
|
||||
<p>Maximum Notes: {{ activeMembership?.account.max_notes }}</p>
|
||||
<p>Maximum Members: {{ activeMembership?.account.max_members }}</p>
|
||||
<p>Access Level: {{ activeMembership?.access }} <span v-if="activeMembership && activeMembership.access === ACCOUNT_ACCESS.ADMIN"><button @click.prevent="accountStore.claimOwnershipOfAccount()">Claim Ownership of Account</button></span></p>
|
||||
<p>Plan: {{ activeMembership?.account.plan_name }}</p>
|
||||
<p>Join Link: <pre>{{ joinURL() }}</pre> <span v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)"><button @click.prevent="accountStore.rotateJoinPassword()">Generate New Join Link</button></span></p>
|
||||
|
||||
<h4>Members</h4>
|
||||
<ul>
|
||||
<li v-for="accountMember in activeAccountMembers">
|
||||
{{ accountMember.user.display_name }}
|
||||
({{ accountMember.user.email }})
|
||||
[{{ accountMember.access }}]
|
||||
<span v-if="accountMember.pending">(pending)</span>
|
||||
<span v-if="accountMember.pending && activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)"><button @click.prevent="accountStore.acceptPendingMembership(accountMember.id)">Accept Pending Membership</button></span>
|
||||
<span v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN) && accountMember.access === ACCOUNT_ACCESS.READ_ONLY && !accountMember.pending"><button @click.prevent="accountStore.changeUserAccessWithinAccount(accountMember.user.id, ACCOUNT_ACCESS.READ_WRITE)">Promote to Read/Write</button></span>
|
||||
<span v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN) && accountMember.access === ACCOUNT_ACCESS.READ_WRITE && !accountMember.pending"><button @click.prevent="accountStore.changeUserAccessWithinAccount(accountMember.user.id, ACCOUNT_ACCESS.ADMIN)">Promote to Admin</button></span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<template v-if="config.public.debugMode">
|
||||
<p>******* Debug *******</p>
|
||||
<p>Account ID: {{ activeMembership?.account.id }}</p>
|
||||
<p>Plan Id: {{ activeMembership?.account.plan_id }}</p>
|
||||
<p>Stripe Subscription Id: {{ activeMembership?.account.stripe_subscription_id }}</p>
|
||||
<p>Stripe Customer Id: {{ activeMembership?.account.stripe_customer_id }}</p>
|
||||
<p>Membership Id: {{ activeMembership?.id }}</p>
|
||||
<p>User Id: {{ activeMembership?.user_id }}</p>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<div class="container mx-auto p-6">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-6xl mb-4">Account Information</h2>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="font-bold w-32">Account Name:</span>
|
||||
<span>{{ activeMembership?.account.name }}</span>
|
||||
<template v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)">
|
||||
<input v-model="newAccountName" type="text" class="p-2 border border-gray-400 rounded w-1/3" placeholder="Enter new name">
|
||||
<button @click.prevent="accountStore.changeAccountName(newAccountName)" class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded">Change Name</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="font-bold w-32">Current Period Ends:</span>
|
||||
<span>{{ formatDate(activeMembership?.account.current_period_ends) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="font-bold w-32">Permitted Features:</span>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span v-for="feature in activeMembership?.account.features" class="bg-gray-200 text-gray-700 font-semibold py-1 px-2 rounded-full">{{ feature }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="font-bold w-32">Maximum Notes:</span>
|
||||
<span>{{ activeMembership?.account.max_notes }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="font-bold w-32">Access Level:</span>
|
||||
<span class="bg-green-500 text-white font-semibold py-1 px-2 rounded-full">{{ activeMembership?.access }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="font-bold w-32">Plan:</span>
|
||||
<span>{{ activeMembership?.account.plan_name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="font-bold w-32">Join Link:</span>
|
||||
<div class="flex gap-2 items-center">
|
||||
<input disabled type="text" class="p-2 border border-gray-400 rounded w-full" :value="joinURL()">
|
||||
<button @click.prevent="accountStore.rotateJoinPassword()" v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">ReGen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<h2 class="text-lg font-bold">Members</h2>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div v-for="accountMember in activeAccountMembers" class="flex gap-4 items-center">
|
||||
<span>{{ accountMember.user.display_name }}</span>
|
||||
<span class="bg-green-500 text-white font-semibold py-1 px-2 rounded-full">{{ accountMember.access }}</span>
|
||||
<span class="text-gray-500">({{ accountMember.user.email }})</span>
|
||||
<button @click.prevent="" v-if="activeMembership && activeMembership.access === ACCOUNT_ACCESS.OWNER && accountMember.access !== ACCOUNT_ACCESS.OWNER" class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
@@ -19,22 +19,27 @@
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-primary text-2xl">Notes Dashboard</h1>
|
||||
|
||||
<div class="flex flex-wrap">
|
||||
<div class="card w-96 bg-base-100 shadow-xl" v-for="note in notes">
|
||||
<div class="card-body">
|
||||
<p>{{ note.note_text }}</p>
|
||||
<div class="card-actions justify-end">
|
||||
<button class="btn btn-primary btn-sm" @click.prevent="notesStore.deleteNote(note.id)">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:py-16 lg:px-8">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-6xl mb-4">Notes Dashboard</h2>
|
||||
</div>
|
||||
|
||||
<div class="w-full max-w-md mb-8">
|
||||
<div class="flex flex-row">
|
||||
<input v-model="newNoteText" type="text" class="w-full rounded-l-md py-2 px-4 border-gray-400 border-2 focus:outline-none focus:border-blue-500" placeholder="Add a note...">
|
||||
<button @click.prevent="addNote()" type="button" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-r-md focus:outline-none focus:shadow-outline-blue">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-500">
|
||||
<input type="text" class="input w-full max-w-xs" placeholder="Enter a Note" v-model="newNoteText"/>
|
||||
<button @click.prevent="addNote()" class="btn btn-primary">Add</button>
|
||||
|
||||
<div class="w-full max-w-md">
|
||||
<div v-for="note in notes" class="bg-white rounded-lg shadow-lg text-center px-6 py-8 md:mx-4 md:my-4">
|
||||
<p class="text-gray-600 mb-4">{{ note.note_text }}</p>
|
||||
<button @click.prevent="notesStore.deleteNote(note.id)" class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
170
pages/index.vue
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
const user = useSupabaseUser()
|
||||
const user = useSupabaseUser()
|
||||
watchEffect(() => {
|
||||
if (user.value) {
|
||||
navigateTo('/dashboard', {replace: true})
|
||||
@@ -7,8 +7,166 @@
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<h3>Index</h3>
|
||||
Nuxt 3 (SAAS) Boilerplate is very nice, why don't you <NuxtLink to="/signup">Get Started</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
<div class="container mx-auto">
|
||||
<!-- Hero section -->
|
||||
<section class="bg-gray-100 py-20">
|
||||
<div class="container mx-auto">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-16">
|
||||
<div>
|
||||
<h1 class="text-5xl font-bold mb-4">
|
||||
Build Your Next SAAS Faster
|
||||
</h1>
|
||||
<p class="text-gray-700 text-lg mb-8">
|
||||
With Nuxt 3 SAAS Boilerplate, you can easily get started building your
|
||||
next web application. Our pre-configured tech stack and
|
||||
industry leading features make it easy to get up and running in no time. Look! this guy is working so fast,
|
||||
his hands are just a blur.. you could be this fast.
|
||||
</p>
|
||||
<a href="#" class="inline-block py-3 px-6 bg-blue-600 text-white rounded-lg hover:bg-blue-700">Get Started</a>
|
||||
</div>
|
||||
<div>
|
||||
<img src="~/assets/images/saas_landing_main.jpeg"
|
||||
alt="Hero Image" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-12">
|
||||
<div class="container px-4 mx-auto">
|
||||
<div class="flex flex-col md:flex-row items-center justify-center md:justify-between mb-8">
|
||||
<h2 class="text-3xl font-bold mb-4 md:mb-0">Features</h2>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row items-center mb-16">
|
||||
|
||||
<div class="md:w-full">
|
||||
<h3 class="text-xl font-bold mb-4">Tech Stack</h3>
|
||||
<ul class="grid grid-cols-3 gap-10 list-none">
|
||||
<li>
|
||||
<Icon name="skill-icons:nuxtjs-dark" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Nuxt 3</h3>
|
||||
<p class="mt-2 text-base text-gray-500">The Progressive Vue.js Framework</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="skill-icons:supabase-dark" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Supabase</h3>
|
||||
<p class="mt-2 text-base text-gray-500">Auth including OAuth + Postgresql instance</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="skill-icons:postgresql-dark" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">PostgreSQL</h3>
|
||||
<p class="mt-2 text-base text-gray-500">The Progressive JavaScript Framework</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="logos:prisma" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Prisma</h3>
|
||||
<p class="mt-2 text-base text-gray-500">Schema management + Strongly typed client</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="simple-icons:trpc" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">TRPC</h3>
|
||||
<p class="mt-2 text-base text-gray-500">Server/Client communication with Strong types, SSR compatible</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="skill-icons:vuejs-dark" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Pinia</h3>
|
||||
<p class="mt-2 text-base text-gray-500">State Store</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="logos:stripe" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Stripe</h3>
|
||||
<p class="mt-2 text-base text-gray-500">Payments including Webhook integration</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="skill-icons:tailwindcss-dark" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Tailwind</h3>
|
||||
<p class="mt-2 text-base text-gray-500">A utility-first CSS framework</p>
|
||||
</li>
|
||||
<li>
|
||||
<Icon name="skill-icons:vuejs-dark" class="h-12 w-12 mb-2" />
|
||||
<h3 class="text-xl font-medium text-gray-900">Vue.js</h3>
|
||||
<p class="mt-2 text-base text-gray-500">The Progressive JavaScript Framework</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- User Management (text left) -->
|
||||
<div class="flex flex-col md:flex-row-reverse items-center mb-16">
|
||||
<div class="md:w-1/2 md:ml-8 mb-8 md:mb-0">
|
||||
<img src="~/assets/images/landing_user_management.jpeg" alt="User Management"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:ml-4">
|
||||
</div>
|
||||
<div class="md:w-1/2">
|
||||
<h3 class="text-xl font-bold mb-4">User Management</h3>
|
||||
<p class="mb-4">Our Nuxt 3 SAAS Boilerplate project includes robust user management features, including
|
||||
authentication with social login (oauth) or email/password, management of user roles and permissions, and
|
||||
multi-user/team accounts that permit multiple users to share plan features including a team administration
|
||||
facility and user roles within team. This is a great feature for businesses or community groups who want to
|
||||
share the cost of the plan.</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- DB Schema (text right)-->
|
||||
<div class="flex flex-col md:flex-row items-center mb-16">
|
||||
<div class="md:w-1/2 md:mr-8 mb-8 md:mb-0">
|
||||
<img src="~/assets/images/landing_db_schema_management.jpeg" alt="DB Schema Management"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:mr-4">
|
||||
</div>
|
||||
<div class="md:w-1/2">
|
||||
<h3 class="text-xl font-bold mb-4">DB Schema Management</h3>
|
||||
<p class="mb-4">We use Prisma for schema management to make sure you can easily
|
||||
manage and keep track of your database schema. We also utilise Prisma based strong types which, with some help from TRPC, penetrate the entire stack all
|
||||
the way to the web front end. This ensures that you can move fast with your feature development, alter schema and have those
|
||||
type changes instantly available and validated everywhere.</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Config (text left) -->
|
||||
<div class="flex flex-col md:flex-row-reverse items-center mb-16">
|
||||
<div class="md:w-1/2 md:ml-8 mb-8 md:mb-0">
|
||||
<img src="~/assets/images/landing_config_environment.jpeg" alt="Config and Environment"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:ml-4">
|
||||
</div>
|
||||
<div class="md:w-1/2">
|
||||
<h3 class="text-xl font-bold mb-4">Config and Environment</h3>
|
||||
<p class="mb-4">Nuxt 3 SAAS Boilerplate includes an approach to config and environment
|
||||
management that enables customisation and management of api keys.</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- State Management (text right)-->
|
||||
<div class="flex flex-col md:flex-row items-center mb-16">
|
||||
<div class="md:w-1/2 md:mr-8 mb-8 md:mb-0">
|
||||
<img src="~/assets/images/landing_state_management.jpeg" alt="State Management"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:mr-4">
|
||||
</div>
|
||||
<div class="md:w-1/2">
|
||||
<h3 class="text-xl font-bold mb-4">State Management</h3>
|
||||
<p class="mb-4">Nuxt 3 SAAS Boilerplate includes multi modal state management that supports both Single Page Application (SPA)
|
||||
pages such as dashboards and Server Side Rendered (SSR) style pages for public content that are crawlable by Search
|
||||
engines like google and facilitate excellent Search Engine Optimisation (SEO).</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stripe (text left) -->
|
||||
<div class="flex flex-col md:flex-row-reverse items-center mb-16">
|
||||
<div class="md:w-1/2 md:ml-8 mb-8 md:mb-0">
|
||||
<img src="~/assets/images/landing_stripe_integration.jpeg" alt="Stripe Integration"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:ml-4">
|
||||
</div>
|
||||
<div class="md:w-1/2">
|
||||
<h3 class="text-xl font-bold mb-4">Stripe Integration</h3>
|
||||
<p class="mb-4">Nuxt 3 SAAS Boilerplate includes Stripe integration for subscription payments including
|
||||
Subscription based support for multi pricing and multiple plans.</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tailwind (text right)-->
|
||||
<div class="flex flex-col md:flex-row items-center mb-16">
|
||||
<div class="md:w-1/2 md:mr-8 mb-8 md:mb-0">
|
||||
<img src="~/assets/images/landing_style_system.jpeg" alt="Style System"
|
||||
class="w-full rounded-lg shadow-lg mb-4 md:mb-0 md:mr-4">
|
||||
</div>
|
||||
<div class="md:w-1/2">
|
||||
<h3 class="text-xl font-bold mb-4">Style System</h3>
|
||||
<p class="mb-4">Nuxt 3 SAAS Boilerplate includes Tailwind integration for site styling including a themable UI components with daisyUI</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div></template>
|
||||
|
||||
@@ -10,44 +10,75 @@
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<h3>Pricing</h3>
|
||||
<p>Current Plan: {{ activeMembership?.account.plan_name }}</p>
|
||||
<div>
|
||||
<label for="submit">Free Trial (1 Month)</label>
|
||||
<ul>
|
||||
<li>10 Notes</li>
|
||||
<li>Single User</li>
|
||||
</ul>
|
||||
<NuxtLink v-if="activeMembership && (activeMembership?.account.plan_name === 'Free Trial')" to="/dashboard"> Continue to Dashboard</NuxtLink>
|
||||
<div class="flex flex-col items-center max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:py-16 lg:px-8">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-6xl mb-4">Flexible Pricing</h2>
|
||||
<p class="text-xl text-gray-500">Get started with the best boiler for your SAAS plate</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col md:flex-row justify-center items-center">
|
||||
<!-- Free Plan -->
|
||||
<div class="bg-white rounded-lg shadow-lg text-center px-6 py-8 md:mx-4 md:my-4 md:flex-1">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-4">Free Plan</h3>
|
||||
<p class="text-gray-600 mb-4">Single user, 10 notes</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mb-4">$0<span class="text-gray-600 text-lg">/mo</span></p>
|
||||
</div>
|
||||
|
||||
<!-- Personal Plan -->
|
||||
<div class="bg-white rounded-lg shadow-lg text-center px-6 py-8 md:mx-4 md:my-4 md:flex-1">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-4">Personal Plan</h3>
|
||||
<p class="text-gray-600 mb-4">Single user, 100 notes</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mb-4">$15<span class="text-gray-600 text-lg">/mo</span></p>
|
||||
|
||||
<!-- logged in user gets a subscribe button-->
|
||||
<form
|
||||
action="/create-checkout-session"
|
||||
method="POST"
|
||||
v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access !== ACCOUNT_ACCESS.ADMIN) && (activeMembership?.account.plan_name !== 'Individual Plan')">
|
||||
<input type="hidden" name="price_id" value="price_1MpOiwJfLn4RhYiLqfy6U8ZR" />
|
||||
<input type="hidden" name="account_id" :value="activeMembership?.account_id" />
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
<!-- anon user gets a link to sign up -->
|
||||
<button
|
||||
v-if="!activeMembership"
|
||||
@click.prevent="navigateTo('/signup')"
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Sign Up
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Team Plan -->
|
||||
<div class="bg-white rounded-lg shadow-lg text-center px-6 py-8 md:mx-4 md:my-4 md:flex-1">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-4">Team Plan</h3>
|
||||
<p class="text-gray-600 mb-4">10 users, 200 notes</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mb-4">$25<span class="text-gray-600 text-lg">/mo</span></p>
|
||||
|
||||
<!-- logged in user gets a subscribe button-->
|
||||
<form
|
||||
action="/create-checkout-session"
|
||||
method="POST"
|
||||
v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access !== ACCOUNT_ACCESS.ADMIN) && (activeMembership?.account.plan_name !== 'Individual Plan')">
|
||||
<input type="hidden" name="price_id" value="price_1MpOjtJfLn4RhYiLsjzAso90" />
|
||||
<input type="hidden" name="account_id" :value="activeMembership?.account_id" />
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
<!-- anon user gets a link to sign up -->
|
||||
<button
|
||||
v-if="!activeMembership"
|
||||
@click.prevent="navigateTo('/signup')"
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||
Sign Up
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form action="/create-checkout-session" method="POST">
|
||||
<label for="submit">Individual Plan, Normal Price</label>
|
||||
<ul>
|
||||
<li>100 Notes</li>
|
||||
<li>Single User</li>
|
||||
</ul>
|
||||
|
||||
<input type="hidden" name="price_id" value="price_1MpOiwJfLn4RhYiLqfy6U8ZR" />
|
||||
<input type="hidden" name="account_id" :value="activeMembership?.account_id" />
|
||||
|
||||
<span v-if="!activeMembership"> <NuxtLink to="/signup">Sign Up</NuxtLink> </span>
|
||||
<button type="submit" v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access !== ACCOUNT_ACCESS.ADMIN) && (activeMembership?.account.plan_name !== 'Individual Plan')">Checkout</button>
|
||||
</form>
|
||||
|
||||
<form action="/create-checkout-session" method="POST">
|
||||
<label for="submit">Team Plan, Normal Price</label>
|
||||
<ul>
|
||||
<li>200 Notes</li>
|
||||
<li>Up to 10 Team Members</li>
|
||||
</ul>
|
||||
|
||||
<input type="hidden" name="price_id" value="price_1MpOjtJfLn4RhYiLsjzAso90" />
|
||||
<input type="hidden" name="account_id" :value="activeMembership?.account_id" />
|
||||
|
||||
<span v-if="!activeMembership"> <NuxtLink to="/signup">Sign Up</NuxtLink> </span>
|
||||
<button type="submit" v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN) && (activeMembership?.account.plan_name !== 'Team Plan')">Checkout</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||