default ui is not crap

This commit is contained in:
Michael Dausmann
2023-04-22 17:40:50 +10:00
parent bef6cb4bfc
commit a5f894d59a
16 changed files with 777 additions and 136 deletions

View File

@@ -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.)

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -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">

View File

@@ -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
View File

@@ -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",

View File

@@ -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"

View File

@@ -24,41 +24,68 @@
</script>
<template>
<div>
<h3>Account</h3>
<p>Name: {{ activeMembership?.account.name }}&nbsp;<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>&nbsp;<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>

View File

@@ -19,22 +19,27 @@
});
</script>
<template>
<div class="flex flex-col">
<h1 class="text-primary text-2xl">Notes Dashboard</h1>
<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="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 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="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 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>
</div>
</template>

View File

@@ -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 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>
<h3>Index</h3>
Nuxt 3 (SAAS) Boilerplate is very nice, why don't you <NuxtLink to="/signup">Get Started</NuxtLink>
<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>
</template>
<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>

View File

@@ -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">&nbsp;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>
<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>
<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" />
<span v-if="!activeMembership">&nbsp;<NuxtLink to="/signup">Sign Up</NuxtLink>&nbsp;</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>
<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>
<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>
<!-- 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" />
<span v-if="!activeMembership">&nbsp;<NuxtLink to="/signup">Sign Up</NuxtLink>&nbsp;</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>
<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>
</div>
</template>