mango-cms 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +17 -0
  2. package/bin/mango +4 -0
  3. package/frontend-starter/README.md +8 -0
  4. package/frontend-starter/dist/_redirects +1 -0
  5. package/frontend-starter/dist/assets/index.00922bd5.js +99 -0
  6. package/frontend-starter/dist/assets/index.1781f175.css +1 -0
  7. package/frontend-starter/dist/favicon.png +0 -0
  8. package/frontend-starter/dist/index.html +53 -0
  9. package/frontend-starter/dist/index.js +66 -0
  10. package/frontend-starter/index.html +25 -0
  11. package/frontend-starter/index.js +197 -0
  12. package/frontend-starter/package-lock.json +5454 -0
  13. package/frontend-starter/package.json +40 -0
  14. package/frontend-starter/postcss.config.js +6 -0
  15. package/frontend-starter/public/_redirects +1 -0
  16. package/frontend-starter/public/favicon.png +0 -0
  17. package/frontend-starter/public/index.js +66 -0
  18. package/frontend-starter/src/App.vue +27 -0
  19. package/frontend-starter/src/components/layout/login.vue +212 -0
  20. package/frontend-starter/src/components/layout/modal.vue +113 -0
  21. package/frontend-starter/src/components/layout/spinner.vue +17 -0
  22. package/frontend-starter/src/components/pages/404.vue +28 -0
  23. package/frontend-starter/src/components/pages/home.vue +74 -0
  24. package/frontend-starter/src/components/partials/button.vue +31 -0
  25. package/frontend-starter/src/helpers/Mango.vue +455 -0
  26. package/frontend-starter/src/helpers/breakpoints.js +34 -0
  27. package/frontend-starter/src/helpers/darkMode.js +38 -0
  28. package/frontend-starter/src/helpers/email.js +32 -0
  29. package/frontend-starter/src/helpers/formatPhone.js +18 -0
  30. package/frontend-starter/src/helpers/localDB.js +315 -0
  31. package/frontend-starter/src/helpers/mango.js +338 -0
  32. package/frontend-starter/src/helpers/model.js +9 -0
  33. package/frontend-starter/src/helpers/multiSelect.vue +252 -0
  34. package/frontend-starter/src/helpers/pills.vue +75 -0
  35. package/frontend-starter/src/helpers/reconnecting-websocket.js +357 -0
  36. package/frontend-starter/src/helpers/uploadFile.vue +157 -0
  37. package/frontend-starter/src/helpers/uploadFiles.vue +100 -0
  38. package/frontend-starter/src/helpers/uploadImages.vue +89 -0
  39. package/frontend-starter/src/helpers/user.js +40 -0
  40. package/frontend-starter/src/index.css +281 -0
  41. package/frontend-starter/src/main.js +145 -0
  42. package/frontend-starter/tailwind.config.js +46 -0
  43. package/frontend-starter/vite.config.js +10 -0
  44. package/frontend-starter/yarn.lock +3380 -0
  45. package/mango-cms-0.0.13.tgz +0 -0
  46. package/package.json +24 -0
  47. package/src/cli.js +93 -0
  48. package/src/default-config/automation/index.js +37 -0
  49. package/src/default-config/collections/examples.js +60 -0
  50. package/src/default-config/config/.collections.json +1 -0
  51. package/src/default-config/config/globalFields.js +15 -0
  52. package/src/default-config/config/settings.json +23 -0
  53. package/src/default-config/config/statuses.js +0 -0
  54. package/src/default-config/config/users.js +35 -0
  55. package/src/default-config/endpoints/index.js +19 -0
  56. package/src/default-config/fields/vimeo.js +36 -0
  57. package/src/default-config/hooks/test.js +5 -0
  58. package/src/default-config/plugins/mango-stand/index.js +206 -0
  59. package/src/main.js +278 -0
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "cfl-front",
3
+ "version": "0.0.0",
4
+ "scripts": {
5
+ "dev": "vite",
6
+ "build": "vite build",
7
+ "preview": "vite preview"
8
+ },
9
+ "dependencies": {
10
+ "@headlessui/vue": "^1.6.7",
11
+ "@mapbox/mapbox-gl-geocoder": "^5.0.0",
12
+ "@sweetalert2/theme-dark": "^5.0.10",
13
+ "@tinymce/tinymce-vue": "^4",
14
+ "@vitejs/plugin-vue": "^2.2.0",
15
+ "@vueup/vue-quill": "^1.0.0-beta.7",
16
+ "@vueuse/motion": "^2.0.0-beta.12",
17
+ "algoliasearch": "^4.12.0",
18
+ "axios": "^0.24.0",
19
+ "canvas-confetti": "^1.5.1",
20
+ "datebook": "^7.0.8",
21
+ "dayjs": "^1.10.7",
22
+ "express": "^4.18.1",
23
+ "google-maps": "^4.3.3",
24
+ "mapbox-gl": "^2.7.0",
25
+ "sweetalert2": "^11.4.0",
26
+ "vue": "^3.2.37",
27
+ "vue-router": "4",
28
+ "vue-slider-component": "^4.0.0-beta.4",
29
+ "vue-upload-component": "^3.1.2",
30
+ "vue3-clipboard": "^1.0.0",
31
+ "vue3-touch-events": "^4.1.0",
32
+ "vuedraggable": "^4.1.0"
33
+ },
34
+ "devDependencies": {
35
+ "autoprefixer": "^10.4.0",
36
+ "postcss": "^8.4.5",
37
+ "tailwindcss": "^3.0.2",
38
+ "vite": "^4.2.1"
39
+ }
40
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -0,0 +1 @@
1
+ /* /index.html 200
@@ -0,0 +1,66 @@
1
+ const express = require('express');
2
+ const fs = require('fs')
3
+ const axios = require('axios')
4
+ const settings = require('../../config/config/settings.json')
5
+ const collections = require('../../config/config/.collections.json')
6
+
7
+ let port = 1337
8
+ let metaComment = '<!-- Inject Meta -->'
9
+ let appPlaceholder = '<!--SSR-->'
10
+
11
+ let serve = async function (req, res) {
12
+
13
+ // Cookies
14
+ let cookies
15
+ const { headers: { cookie } } = req;
16
+ if (cookie) {
17
+ const values = cookie.split(';').reduce((res, item) => {
18
+ const data = item.trim().split('=');
19
+ return { ...res, [data[0]]: data[1] };
20
+ }, {});
21
+ cookies = values;
22
+ }
23
+
24
+ // User Data Injection
25
+ let index = fs.readFileSync('./index.html', 'utf8')
26
+ let authorization = cookies?.Authorization
27
+ if (authorization) {
28
+
29
+ let hash = authorization.split(':')[0]
30
+ let userId = authorization.split(':')[1]
31
+ let user = (await axios.get(`http://localhost:${settings.port}/members?search={"password.hash": "${hash}", "id": "${userId}"}`, { headers: { 'Authorization': authorization } }))
32
+ user = user?.data?.response?.[0]
33
+ if (user) index = index.replace('window.user = null;', `window.user = ${JSON.stringify(user)};`)
34
+
35
+ }
36
+
37
+ // Define the collection and ID
38
+ let collection = req.path.split('/')[1]
39
+ let id = req.path.split('/')[2] ? req.path.split('/')[2] : null
40
+
41
+ console.log('collection', collection)
42
+ console.log('id', id)
43
+
44
+ let entry = (await axios.get(`http://localhost:${settings.port}/${collection}/${id}`))?.data?.response
45
+
46
+ console.log('entry', entry)
47
+
48
+ if (!entry) return res.send(index)
49
+
50
+ // Main Content Injection
51
+ try { index = index.replace('window.mainEntry = null;', `window.mainEntry = ${JSON.stringify(entry)};`) }
52
+ catch (e) { console.log(e) }
53
+
54
+ return res.send(index)
55
+
56
+ }
57
+
58
+ const app = express()
59
+
60
+ app.get(["/index.html", "/"], serve);
61
+ app.use(express.static('./'))
62
+ app.get('/*', serve)
63
+
64
+ app.listen(port, () => {
65
+ console.log(`Example app listening at http://localhost:${port}`)
66
+ })
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <div :class="{'dark':darkMode.enabled}">
3
+ <div class="relative from-white to-slate-50 bg-gradient-to-b dark:from-black dark:to-gray-700 dark:selection:bg-gray-400 dark:text-gray-400 w-full min-h-screen">
4
+
5
+ <RouterView v-slot="{ Component, route }" @navHidden="navHidden = $event">
6
+ <Suspense :timeout="200" >
7
+ <Component :is="Component" :key="route.meta.usePathKey ? route.path : undefined" />
8
+ <template #fallback><spinner /></template>
9
+ </Suspense>
10
+ </RouterView>
11
+
12
+ </div>
13
+ </div>
14
+ </template>
15
+
16
+ <script>
17
+ import login from './components/layout/login.vue'
18
+ export default {
19
+ components: { login },
20
+ inject: ['store','darkMode'],
21
+ data() {
22
+ return {
23
+ navHidden: false
24
+ }
25
+ }
26
+ }
27
+ </script>
@@ -0,0 +1,212 @@
1
+ <template>
2
+ <div class="w-full p-8 flex items-center">
3
+ <slot
4
+ :logout="logout"
5
+ :login="login"
6
+ :createAccount="createAccount"
7
+ :sendRecoveryEmail="sendRecoveryEmail"
8
+ :resetPassword="resetPassword"
9
+ >
10
+ <form @submit.stop.prevent class="rounded-lg flex flex-col space-y-4 w-full">
11
+
12
+ <template v-if="creatingAccount">
13
+ <div class="text-2xl">Create an Account</div>
14
+ <div class="text-sm text-gray-500">The first account you create is automatically made an admin account.</div>
15
+
16
+ <div class="flex gap-4 w-full">
17
+ <input type="text" v-model.trim="user.firstName" placeholder="First Name" />
18
+ <input type="text" v-model.trim="user.lastName" placeholder="Last Name" />
19
+ </div>
20
+
21
+ <input type="email" v-model.trim="user.email" placeholder="Email" />
22
+ <input type="password" v-model.trim="user.password" placeholder="Password" autocomplete="new-password" />
23
+
24
+ <!-- <div><input type="text" v-model="user.address.address" placeholder="Address" /></div>
25
+ <div class="space-x-4 flex">
26
+ <input type="text" v-model="user.address.zip" placeholder="Zip Code" @input="extractZip" />
27
+ </div>
28
+ <div class="space-x-4 flex">
29
+ <input type="text" v-model="user.address.city" placeholder="City" />
30
+ <input type="text" v-model="user.address.state" placeholder="State" />
31
+ </div> -->
32
+ </template>
33
+
34
+ <template v-else>
35
+ <div class="text-2xl">Mango Login</div>
36
+ <input type="email" v-model.trim="user.email" placeholder="Email" />
37
+ <input type="password" v-model.trim="user.password" placeholder="Password" autocomplete="current-password" />
38
+ </template>
39
+
40
+ <button @click="action" class="px-3 py-2 bg-orange-500 dark:bg-orange-500/80 dark:text-white/75 text-white rounded flex justify-center">
41
+ <template v-if="!processing">{{ buttonText }}</template>
42
+ <Spinner v-else class="border-t-family-at-church-orange w-4 h-4 my-1" :small="true" color="border-t-white/50" />
43
+ </button>
44
+
45
+ <button v-if="store.user?.member?.id" @click="logout" class="text-xs text-gray-400 hover:underline">Logout</button>
46
+
47
+ <div class="text-center select-none" >
48
+ <button v-if="!creatingAccount" @click="creatingAccount = true; guest = false; forgotPassword = false" class="text-xs text-gray-400 hover:underline">Create Account</button>
49
+ <button v-if="creatingAccount || resettingPassword" @click="creatingAccount = false; guest = false; forgotPassword = false" class="text-xs text-gray-400 hover:underline" :class="{'ml-2 pl-2 border-l dark:border-gray-500': resettingPassword}">{{ resettingPassword || (allowGuest && !guest) ? 'Login' : 'Have an account? Login instead.' }}</button>
50
+ <button v-if="allowGuest && !guest" @click="creatingAccount = true; guest = true; forgotPassword = false" class="text-xs text-gray-400 hover:underline ml-2 pl-2 border-l dark:border-gray-500">Continue as Guest</button>
51
+ <button v-if="!creatingAccount && !resettingPassword" @click="creatingAccount = false; guest = false; forgotPassword = true" class="text-xs text-gray-400 hover:underline ml-2 pl-2 border-l dark:border-gray-500">Forgot Password?</button>
52
+ </div>
53
+ </form>
54
+ </slot>
55
+ </div>
56
+ </template>
57
+
58
+ <script>
59
+ import Swal from 'sweetalert2'
60
+ import { validateEmail } from '../../helpers/email'
61
+ import { getUser } from '../../helpers/user'
62
+ import Mango from '../../helpers/mango'
63
+
64
+ // Function for setting cookies
65
+ let setCookie = function (cname, cvalue) {
66
+ var d = new Date();
67
+ d.setTime(d.getTime() + (365 * 24 * 60 * 60 * 1000));
68
+ var expires = "expires=" + d.toUTCString();
69
+ document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
70
+ }
71
+
72
+ export default {
73
+ inject: ['store','axios'],
74
+ data() {
75
+ return {
76
+ user: {
77
+ email: null,
78
+ password: null,
79
+ firstName: null,
80
+ lastName: null,
81
+ // address: {
82
+ // name: null,
83
+ // address: null,
84
+ // city: null,
85
+ // state: null,
86
+ // zip: null,
87
+ // },
88
+ },
89
+ processing: false,
90
+ creatingAccount: true,
91
+ guest: false,
92
+ forgotPassword: false,
93
+ }
94
+ },
95
+ watch: {
96
+ loggedIn() {
97
+ this.$emit('hide')
98
+ }
99
+ },
100
+ methods: {
101
+ validateEmail,
102
+ async login() {
103
+ this.processing = true
104
+ let member = (await Mango.login({ email: this.user.email, password: this.user.password }))
105
+
106
+ if (member?.memberId) {
107
+ if (member.roles?.includes('admin')) member.admin = true
108
+
109
+ window.localStorage.setItem('user', member.memberId)
110
+ window.localStorage.setItem('token', member.token)
111
+ window.localStorage.setItem('email', this.user.email)
112
+
113
+ setCookie(`Authorization`, `${member.token}`)
114
+ this.axios.defaults.headers.common['Authorization'] = `${member.token}`
115
+
116
+ const user = await getUser()
117
+ this.store.user = user
118
+ if (user?.company?.id) this.store.theme = user.company
119
+
120
+ this.$emit('loggedIn')
121
+
122
+ let restrictedPath = this.store?.login?.next || '/'
123
+ if (restrictedPath) this.$router.push(restrictedPath)
124
+
125
+ this.$emit('hide')
126
+ } else if (member?.invalidFields) {
127
+ Swal.fire({ title: `Invalid ${member.invalidFields.join(', ')}`, icon: 'error' })
128
+ }
129
+
130
+ this.processing = false
131
+ },
132
+ logout() {
133
+ this.store.user = {}
134
+ window.user = null
135
+ window.localStorage.removeItem('user')
136
+ setCookie(`Authorization`, ``)
137
+ delete this.axios.defaults.headers.common['Authorization']
138
+ this.$emit('loggedOut')
139
+ this.$emit('hide')
140
+ this.$router.push('/login')
141
+ },
142
+ async createAccount() {
143
+ if (!this.validateEmail(this.user.email)) return Swal.fire('Email must be a valid email.')
144
+ if (this.user.password.length < 6 && !this.guest) return Swal.fire('Password must be at least 6 characters.')
145
+ this.processing = true
146
+
147
+ var data = {
148
+ ...this.user,
149
+ title: `${this.user.firstName} ${this.user.lastName || ''}`.trim()
150
+ }
151
+
152
+ let response = await Mango.members.save(data)
153
+ if (!response.id) {
154
+ Swal.fire('Account Exists', response, 'info')
155
+ } else {
156
+ await this.login()
157
+ }
158
+ this.$emit('accountCreated')
159
+ this.$emit('hide')
160
+ this.processing = false
161
+ },
162
+ async sendRecoveryEmail () {
163
+ this.processing = true
164
+ let response = await this.axios.post(`${this.store.api}/controllers/account/sendResetInstructions`, {email: this.user.email, forgot: true})
165
+ if (response.data.success) Swal.fire('Success!', `Recovery instructions sent to: ${this.user.email}`, 'success')
166
+ else Swal.fire('Invalid Email!', `The following user does not exist: ${this.user.email}`, 'warning')
167
+ this.$emit('emailSent')
168
+ this.$emit('hide')
169
+ this.processing = false
170
+ },
171
+ async resetPassword () {
172
+ if (this.user.password.length < 6) return Swal.fire('Password must be at least 6 characters.')
173
+ this.processing = true
174
+
175
+ let data = {
176
+ email: this.$route.query.email,
177
+ salt: this.$route.query.salt,
178
+ password: this.user.password,
179
+ }
180
+
181
+ let response = await this.axios.post(`${this.store.api}/controllers/account/resetPassword`, data)
182
+ if (response.data.success) {
183
+ this.user.email = this.$route.query.email
184
+ await this.login()
185
+ this.$router.push({query: null})
186
+ swal('Success!', 'Your password has been reset.', 'success')
187
+ } else {
188
+ swal('Invalid Link!', `The link you're using is invalid.`, 'error')
189
+ }
190
+
191
+ this.processing = false
192
+ this.$emit('passwordReset')
193
+ this.$emit('hide')
194
+ },
195
+ },
196
+ computed: {
197
+ guestPassword() { return Math.random().toString(36).substr(2)+Math.random().toString(36).substr(2) },
198
+ labelText() { return this.resettingPassword ? 'Reset Password' : this.forgotPassword ? 'Recover Password' : this.creatingAccount ? this.guest ? 'Continue as Guest' : 'Create Account' : 'Login' },
199
+ buttonText() { return this.resettingPassword ? 'Update Password' : this.forgotPassword ? 'Send Recovery Email' : this.creatingAccount ? this.guest ? 'Continue' : 'Create' : 'Login' },
200
+ action() { return this.resettingPassword ? this.resetPassword : this.forgotPassword ? this.sendRecoveryEmail : this.creatingAccount ? this.createAccount : this.login },
201
+ resettingPassword() { return !!this.$route.query?.salt },
202
+ loggedIn() { return !!this.store.user?.id }
203
+ }
204
+ }
205
+ </script>
206
+
207
+
208
+ <style lang="postcss" scoped>
209
+ input {
210
+ @apply border rounded outline-blue-400 px-3 py-2 w-full dark:bg-transparent dark:border-gray-600 dark:placeholder-gray-600
211
+ }
212
+ </style>
@@ -0,0 +1,113 @@
1
+ <template>
2
+ <div
3
+ ref="modal"
4
+ @click.prevent.stop="close"
5
+ tabindex="-1"
6
+ class="
7
+ fixed w-full h-screen bg-gray-800/50 dark:bg-black/60 backdrop-blur-sm md:backdrop-blur-md opacity-0 transition-all duration-500
8
+ flex items-start sm:items-center justify-center z-[100] inset-0 !m-0 overscroll-none cursor-default
9
+ "
10
+ :class="{'!opacity-100': fadeIn}"
11
+ aria-modal="true"
12
+ v-show="active"
13
+ role="dialog"
14
+ >
15
+ <button @click.prevent.stop="close" class="absolute top-2 right-3"><i class="fa fa-times md:text-4xl" /></button>
16
+ <div
17
+ ref="modalContent"
18
+ @click.stop
19
+ class="shadow-card rounded w-full p-4 md:p-8 m-2 dark:bg-gray-800 bg-white relative
20
+ border dark:border-gray-700 space-y-4 md:space-y-8 max-h-[75vh]"
21
+ :class="[dialogClasses, maxWidth, allowOverflow ? 'overflow-visible' : 'overflow-y-scroll']"
22
+ >
23
+ <slot :close="close"/>
24
+ </div>
25
+ </div>
26
+ <!-- max-w-md max-w-lg max-w-xl max-w-sm -->
27
+ </template>
28
+
29
+ <script>
30
+ export default {
31
+ props: {
32
+ dialogClasses: {type: String, default: ''},
33
+ maxWidth: {default: 'max-w-md'},
34
+ active: {default: true},
35
+ allowOverflow: {type: Boolean, default: false},
36
+ },
37
+ data() {
38
+ return {
39
+ fadeIn: false
40
+ }
41
+ },
42
+ watch: {
43
+ active: {
44
+ handler() {
45
+ if (this.active) this.freeze()
46
+ else this.thaw()
47
+ },
48
+ immediate: true
49
+ }
50
+ },
51
+ beforeDestroy() {
52
+ this.thaw();
53
+ },
54
+ unmounted() {
55
+ this.thaw();
56
+ },
57
+ methods: {
58
+ freeze() {
59
+ this.$nextTick(() => {
60
+ this.$refs.modalContent.focus()
61
+ this.fadeIn = true
62
+ document.body.style.overflow = 'hidden';
63
+ document.getElementById('app').setAttribute('aria-hidden', 'true');
64
+ this.$refs.modal.setAttribute('aria-modal', 'true');
65
+ this.attachListeners();
66
+ });
67
+ },
68
+ thaw() {
69
+ document.body.style.overflow = '';
70
+ document.getElementById('app').setAttribute('aria-hidden', 'false');
71
+ this.$refs?.modal?.removeAttribute('aria-modal');
72
+ this.removeListeners();
73
+ },
74
+ close() {
75
+ this.fadeIn = false
76
+ this.thaw()
77
+ setTimeout(() => {
78
+ this.$emit('hide')
79
+ }, 500);
80
+ },
81
+ attachListeners() {
82
+ window.addEventListener('keydown', this.handleKeydown);
83
+ },
84
+ removeListeners() {
85
+ window.removeEventListener('keydown', this.handleKeydown);
86
+ },
87
+ handleKeydown(e) {
88
+ if (e.key === 'Tab') {
89
+ this.manageFocus(e);
90
+ } else if (e.key === 'Escape') {
91
+ this.close()
92
+ }
93
+ },
94
+ manageFocus(e) {
95
+ let focusable = Array.from(this.$refs.modal.querySelectorAll('button, [href], input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])'));
96
+ focusable = focusable.filter(el => window.getComputedStyle(el).display !== 'none');
97
+ console.log('focusable', focusable)
98
+
99
+ if (!focusable.includes(document.activeElement)) {
100
+ e.preventDefault();
101
+ focusable[0].focus();
102
+ } else if (e.shiftKey && document.activeElement === focusable[0]) {
103
+ e.preventDefault();
104
+ focusable[focusable.length - 1].focus();
105
+ } else if (document.activeElement === focusable[focusable.length - 1]) {
106
+ e.preventDefault();
107
+ focusable[0].focus();
108
+ }
109
+
110
+ }
111
+ }
112
+ }
113
+ </script>
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <div
3
+ class="flex justify-center items-center border-transparent"
4
+ :class="{'w-full h-screen': !small}"
5
+ >
6
+ <div class="rounded-full border-4 animate-spin w-full h-full max-w-32 max-h-32 border-inherit" :class="color"/>
7
+ </div>
8
+ </template>
9
+
10
+ <script>
11
+ export default {
12
+ props: {
13
+ small: {type: Boolean, default: false},
14
+ color: {default: 'border-t-blue-500'}
15
+ },
16
+ }
17
+ </script>
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <main class="w-full pt-16 flex flex-col justify-center items-center text-center min-h-screen">
3
+ <div class="w-full flex flex-wrap justify-center items-center relative px-8 md:px-16">
4
+ <h1 class="w-full text-8xl sm:text-9xl md:text-10xl font-bold text-gray-800 leading-none">404</h1>
5
+ <p class="w-full text-4xl font-semibold text-gray-600">Are you lost?</p>
6
+ <p class="w-full text-2xl font-semibold text-gray-600 mt-4">
7
+ How about messages on
8
+ <router-link
9
+ class="text-3xl text-ncfic-blue-500 dark:text-ncfic-blue-800 hover:text-ncfic-blue-600 font-bold hover:underline hover:-translate-y-2 transition-all duration-500 inline-block"
10
+ :to="`/resources/topics/60c927e8826ba92e9196fcd5`"
11
+ >
12
+ The Gospel
13
+ <i class="fas fa-long-arrow-right text-gray-200 dark:text-gray-800"></i>
14
+ </router-link>
15
+ </p>
16
+ </div>
17
+ </main>
18
+ </template>
19
+
20
+ <script>
21
+ export default {
22
+
23
+ }
24
+ </script>
25
+
26
+ <style>
27
+
28
+ </style>
@@ -0,0 +1,74 @@
1
+ <template>
2
+
3
+ <div class="relative">
4
+
5
+ <div class="fixed top-4 left-4 rounded border border-gray-400 dark:border-gray-600 dark:bg-black px-2 flex space-x-2 items-center">
6
+ <div class="rounded-full w-2 h-2 shrink-0" :class="online ? 'bg-green-500' : 'bg-red-500'" />
7
+ <div>{{ online ? 'Online' : 'Offline' }}</div>
8
+ </div>
9
+
10
+ <div class="pt-16 lg:pt-32 flex items-center justify-center w-full">
11
+
12
+ <div class="w-full max-2xl py-16 md:py-32 text-center flex flex-col items-center px-8 md:px-16">
13
+ <div class="text-9xl pb-8">🥭</div>
14
+ <h1 class="font-bold text-5xl pb-4 bg-clip-text bg-gradient-to-r from-orange-500 to-green-500 text-transparent">Welcome to Mango</h1>
15
+ <h1 class="font-bold text-lg bg-clip-text bg-gradient-to-r from-orange-500 to-green-500 text-transparent italic opacity-50">Here to make your life stinking easy.</h1>
16
+ </div>
17
+
18
+ </div>
19
+
20
+ <div v-if="!store.user?.id || !online" class="flex w-full justify-center">
21
+ <login v-if="online" class="max-w-md" />
22
+ <div v-else class="space-y-8">
23
+ <div class="text-lg">Looks like the local Mango server isn't up and running yet. Please start it by running:</div>
24
+ <div class="w-full">
25
+ <code class="bg-gray-200 dark:bg-black p-3 border border-gray-700 rounded-lg w-full">cd mango; yarn; yarn watch;</code>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <div v-else-if="store.user?.id" class="w-full flex flex-col p-4 items-center space-y-4">
31
+
32
+ <div class="w-full max-w-xl">
33
+ <div class="text mb-4">Great! Now you're logged in as:</div>
34
+ <div class="text-2xl mb-8">{{ store.user.title }}</div>
35
+ <div class="rounded-lg bg-gray-100 dark:bg-gray-800 p-4 ">
36
+ <div class="tracking-widest opacity-75 font-mono mt-2 truncate" v-html="JSON.stringify(store.user, undefined, 4).replaceAll('\n', '<br>').replaceAll(' ', '&nbsp;')" />
37
+ </div>
38
+ </div>
39
+
40
+ <!-- <Mango collection="members" v-slot="{data, loading}" >
41
+ <div v-if="data">
42
+ <div v-for="member in data">{{ member.title }}</div>
43
+ </div>
44
+ </Mango> -->
45
+
46
+ </div>
47
+ </div>
48
+
49
+ </template>
50
+
51
+ <script>
52
+ import Mango from '../../helpers/mango'
53
+ import Login from '../layout/login.vue'
54
+
55
+ export default {
56
+ components: { Login },
57
+ inject: ['store'],
58
+ data() {
59
+ return {
60
+ online: false,
61
+ checker: null,
62
+ }
63
+ },
64
+ async created() {
65
+ this.online = await Mango.online()
66
+ this.checker = setInterval(async () => {
67
+ this.online = await Mango.online()
68
+ }, 500);
69
+ },
70
+ beforeDestroy() {
71
+ clearInterval(this.checker)
72
+ }
73
+ }
74
+ </script>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <div class="relative pb-[4px]">
3
+ <button
4
+ class="relative z-10 w-full text-3xl rounded-xl text-white uppercase"
5
+ :class="[`bg-${store.theme.color}-500`, selected ? 'py-4 px-6' : 'py-6 px-8', selected ? `border-8 border-${store.theme.color}-400`: '']"
6
+ >
7
+ {{ label }}
8
+ </button>
9
+ <div :class="`bg-${store.theme.color}-600`" class="absolute bottom-0 w-full text-3xl px-8 py-6 rounded-xl">&nbsp;</div>
10
+ <div :class="`bg-${store.theme.color}-400`" class="absolute z-20 -top-2 -right-2 rounded-full flex items-center justify-center w-8 h-8 shrink-0 text-white" v-if="selected">
11
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="4" stroke="currentColor" class="w-4 h-4">
12
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
13
+ </svg>
14
+ </div>
15
+ </div>
16
+ </template>
17
+
18
+ <script>
19
+ export default {
20
+ inject: ['store'],
21
+ props: {
22
+ label: {
23
+ type: String,
24
+ },
25
+ selected: {
26
+ type: Boolean,
27
+ default: false,
28
+ }
29
+ }
30
+ }
31
+ </script>