@edgedev/create-edge-app 0.0.1

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 (44) hide show
  1. package/.eslintrc +10 -0
  2. package/.vscode/settings.json +14 -0
  3. package/README.md +63 -0
  4. package/app.vue +106 -0
  5. package/bin/cli-edge-app.js +99 -0
  6. package/components/.gitkeep +0 -0
  7. package/components/account.vue +84 -0
  8. package/components/bottomMenu.vue +35 -0
  9. package/components/dashboard.vue +186 -0
  10. package/components/editor.vue +250 -0
  11. package/components/formSubtypes/.gitkeep +0 -0
  12. package/components/topMenu.vue +17 -0
  13. package/components/userMenu.vue +64 -0
  14. package/composables/global.ts +15 -0
  15. package/composables/vuetify.ts +10 -0
  16. package/deploy.sh +6 -0
  17. package/emulator.sh +17 -0
  18. package/firebase.json +56 -0
  19. package/firestore.indexes.json +4 -0
  20. package/firestore.rules +294 -0
  21. package/firestore.rules.backup +1 -0
  22. package/functions/.runtimeconfig.json +5 -0
  23. package/functions/index.js +226 -0
  24. package/functions/index.js.backup +4 -0
  25. package/functions/package-lock.json +4535 -0
  26. package/functions/package.json +25 -0
  27. package/middleware/auth.ts +17 -0
  28. package/nuxt.config.ts +27 -0
  29. package/package.json +34 -0
  30. package/pages/app/[[page]]/[[collection]]/[[docId]].vue +48 -0
  31. package/pages/app/login.vue +16 -0
  32. package/pages/app/signup.vue +17 -0
  33. package/plugins/draggable.ts +5 -0
  34. package/plugins/edgeFirebaseFramework.ts +5 -0
  35. package/plugins/firebase.client.ts +16 -0
  36. package/plugins/maska.ts +5 -0
  37. package/plugins/number.ts +4 -0
  38. package/plugins/vuetify.ts +14 -0
  39. package/public/favicon.ico +0 -0
  40. package/public/images/logo-square.png +0 -0
  41. package/public/images/logo.png +0 -0
  42. package/server/tsconfig.json +3 -0
  43. package/storage.rules +8 -0
  44. package/tsconfig.json +4 -0
package/.eslintrc ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "root": true,
3
+ "extends": "@antfu",
4
+ "rules": {
5
+ "vue/no-deprecated-slot-attribute": "off",
6
+ "curly": "off",
7
+ "no-console": "off",
8
+ "antfu/top-level-function": "off"
9
+ }
10
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "files.associations": {
3
+ "*.json": "json"
4
+ },
5
+ "[json]": {
6
+ "editor.formatOnSave": true,
7
+ },
8
+ "editor.formatOnSave": false,
9
+ "vetur.validation.template": false,
10
+ "editor.codeActionsOnSave": {
11
+ "source.fixAll.eslint": true,
12
+ },
13
+ "vue3snippets.enable-compile-vue-file-on-did-save-code": false
14
+ }
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # Nuxt 3 Minimal Starter
2
+
3
+ Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
4
+
5
+ ## Setup
6
+
7
+ Make sure to install the dependencies:
8
+
9
+ ```bash
10
+ # npm
11
+ npm install
12
+
13
+ # pnpm
14
+ pnpm install
15
+
16
+ # yarn
17
+ yarn install
18
+ ```
19
+
20
+ ## Development Server
21
+
22
+ Start the development server on `http://localhost:3000`:
23
+
24
+ ```bash
25
+ # npm
26
+ npm run dev
27
+
28
+ # pnpm
29
+ pnpm run dev
30
+
31
+ # yarn
32
+ yarn dev
33
+ ```
34
+
35
+ ## Production
36
+
37
+ Build the application for production:
38
+
39
+ ```bash
40
+ # npm
41
+ npm run build
42
+
43
+ # pnpm
44
+ pnpm run build
45
+
46
+ # yarn
47
+ yarn build
48
+ ```
49
+
50
+ Locally preview production build:
51
+
52
+ ```bash
53
+ # npm
54
+ npm run preview
55
+
56
+ # pnpm
57
+ pnpm run preview
58
+
59
+ # yarn
60
+ yarn preview
61
+ ```
62
+
63
+ Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
package/app.vue ADDED
@@ -0,0 +1,106 @@
1
+ <script setup>
2
+ import { useTheme } from 'vuetify'
3
+
4
+ const vueTheme = useTheme()
5
+ const changeTheme = (theme) => {
6
+ vueTheme.global.name.value = theme
7
+ }
8
+ const edgeFirebase = inject('edgeFirebase')
9
+ const edgeGlobal = inject('edgeGlobal')
10
+
11
+ const currentOrganization = computed(() => {
12
+ return edgeGlobal.edgeState.currentOrganization
13
+ })
14
+
15
+ watch(currentOrganization, async () => {
16
+ if (currentOrganization.value) {
17
+ // RUN STUFF HERE WHEN ORGANIZATION CHANGES LIKE SNAPSHOTS
18
+ await projectSetOrg(currentOrganization.value, edgeFirebase, edgeGlobal)
19
+
20
+ // KEEP THIS CODE:
21
+ const auth = useState('auth')
22
+ auth.value = edgeFirebase.user
23
+
24
+ const preLoginRoute = useState('preLoginRoute')
25
+ const router = useRouter()
26
+
27
+ let cleanedRoute = ''
28
+ if (preLoginRoute.value) {
29
+ cleanedRoute = preLoginRoute.value.endsWith('/') ? preLoginRoute.value.slice(0, -1) : preLoginRoute.value
30
+ }
31
+
32
+ if (cleanedRoute === ''
33
+ || cleanedRoute === '/app'
34
+ || cleanedRoute === '/app/login'
35
+ || cleanedRoute === '/app/signup') {
36
+ router.push('/app/dashboard')
37
+ }
38
+ else {
39
+ router.push(preLoginRoute.value)
40
+ }
41
+
42
+ console.log(auth.value)
43
+ }
44
+ if (!currentOrganization.value) {
45
+ const auth = useState('auth')
46
+ auth.value = ''
47
+ const router = useRouter()
48
+ router.push('/app/login')
49
+ }
50
+ })
51
+
52
+ const user = computed(() => {
53
+ return edgeFirebase.user
54
+ })
55
+
56
+ watch (user, async () => {
57
+ if (user.value) {
58
+ const auth = useState('auth')
59
+ auth.value = user.value
60
+ }
61
+ })
62
+
63
+ onMounted(() => {
64
+ if (edgeGlobal.isDarkMode()) {
65
+ changeTheme('dark')
66
+ }
67
+ else {
68
+ changeTheme('light')
69
+ }
70
+ })
71
+ edgeFirebase.runFunction('initFirestore', {})
72
+ edgeGlobal.edgeState.userRoles = [
73
+ {
74
+ name: 'Admin',
75
+ roles: [
76
+ {
77
+ collectionPath: 'organizationDocPath',
78
+ role: 'admin',
79
+ },
80
+ ],
81
+ },
82
+ {
83
+ name: 'User',
84
+ roles: [
85
+ {
86
+ collectionPath: 'organizationDocPath',
87
+ role: 'user',
88
+ },
89
+ ],
90
+ },
91
+ ]
92
+ </script>
93
+
94
+ <template>
95
+ <v-app>
96
+ <top-menu v-if="edgeFirebase.user.loggedIn" />
97
+ <v-main>
98
+ <NuxtPage />
99
+ </v-main>
100
+ <bottom-menu v-if="edgeFirebase.user.loggedIn" />
101
+ </v-app>
102
+ </template>
103
+
104
+ <style lang="scss">
105
+ .firebase-emulator-warning { display: none; }
106
+ </style>
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ const { execSync } = require('child_process')
3
+ const fs = require('fs')
4
+ const readline = require('readline')
5
+
6
+ const runCommand = (command) => {
7
+ try {
8
+ execSync(`${command}`, { stdio: 'inherit' })
9
+ }
10
+ catch (err) {
11
+ console.error(`Failed to run command: ${command}`, err)
12
+ return false
13
+ }
14
+ return true
15
+ }
16
+
17
+ const promptForProjectId = () => {
18
+ const rl = readline.createInterface({
19
+ input: process.stdin,
20
+ output: process.stdout,
21
+ })
22
+
23
+ return new Promise((resolve, reject) => {
24
+ rl.question('Please enter your Firebase project ID: ', (projectId) => {
25
+ rl.close()
26
+ resolve(projectId)
27
+ })
28
+ })
29
+ }
30
+
31
+ const initializeFirebase = async (projectId, repoName) => {
32
+ if (!projectId) {
33
+ console.error('Error: Firebase project ID cannot be empty.')
34
+ process.exit(1)
35
+ }
36
+
37
+ try {
38
+ execSync('firebase --version', { stdio: 'inherit' })
39
+ }
40
+ catch (err) {
41
+ console.error('Firebase CLI could not be found. Please install it and try again.')
42
+ process.exit(1)
43
+ }
44
+
45
+ const pathToFirebaseJson = `${repoName}/firebase.json`
46
+ const pathToFirestoreRules = `${repoName}/firestore.rules`
47
+
48
+ if (fs.existsSync(pathToFirebaseJson)) {
49
+ fs.renameSync(pathToFirebaseJson, `${pathToFirebaseJson}.temp`)
50
+ }
51
+
52
+ if (fs.existsSync(pathToFirestoreRules)) {
53
+ fs.renameSync(pathToFirestoreRules, `${pathToFirestoreRules}.temp`)
54
+ }
55
+
56
+ console.log(`Initializing Firebase for ${repoName}...`)
57
+ runCommand(`cd ${repoName} && firebase use --add ${projectId} --alias default`)
58
+ runCommand(`cd ${repoName} && firebase init firestore functions hosting storage emulators --project default`)
59
+
60
+ if (fs.existsSync(`${pathToFirebaseJson}.temp`)) {
61
+ fs.renameSync(`${pathToFirebaseJson}.temp`, pathToFirebaseJson)
62
+ }
63
+
64
+ if (fs.existsSync(`${pathToFirestoreRules}.temp`)) {
65
+ fs.renameSync(`${pathToFirestoreRules}.temp`, pathToFirestoreRules)
66
+ }
67
+ }
68
+
69
+ const repoName = process.argv[2]
70
+ const gitCheckoutCommand = `git clone --depth 1 https://github.com/Edge-Marketing-and-Design/edgeApp.git ${repoName}`
71
+ const installDependenciesCommand = `cd ${repoName} && pnpm install`
72
+ const installFunctionDependenciesCommand = `cd ${repoName}/functions && npm install`
73
+
74
+ console.log(`Cloning with name ${repoName}...`)
75
+ const checkedOut = runCommand(gitCheckoutCommand)
76
+ if (!checkedOut) {
77
+ process.exit(1)
78
+ }
79
+
80
+ console.log(`Installing dependencies for ${repoName}...`)
81
+ const installedDeps = runCommand(installDependenciesCommand)
82
+ if (!installedDeps) {
83
+ process.exit(1)
84
+ }
85
+
86
+ console.log(`Installing function dependencies for ${repoName}...`)
87
+ const installedFunctionDeps = runCommand(installFunctionDependenciesCommand)
88
+ if (!installedFunctionDeps) {
89
+ process.exit(1)
90
+ }
91
+
92
+ console.log(`Successfully created ${repoName}!`)
93
+
94
+ const main = async () => {
95
+ const projectId = await promptForProjectId()
96
+ await initializeFirebase(projectId, repoName)
97
+ }
98
+
99
+ main()
File without changes
@@ -0,0 +1,84 @@
1
+ <script setup>
2
+ const route = useRoute()
3
+ const edgeGlobal = inject('edgeGlobal')
4
+ const site = computed(() => {
5
+ return route.params.collection
6
+ })
7
+ const metaFields = [
8
+ {
9
+ field: 'name',
10
+ type: 'text',
11
+ label: 'Name',
12
+ hint: 'Your name, shown in the user interface.',
13
+ rules: [edgeGlobal.edgeRules.required],
14
+ },
15
+ ]
16
+ const orgFields = [
17
+ {
18
+ field: 'name',
19
+ type: 'text',
20
+ label: 'Name',
21
+ hint: 'Your name, shown in the user interface.',
22
+ rules: [edgeGlobal.edgeRules.required],
23
+ },
24
+ ]
25
+
26
+ const config = useRuntimeConfig()
27
+ </script>
28
+
29
+ <template>
30
+ <v-card>
31
+ <v-toolbar flat>
32
+ <v-icon class="mx-4">
33
+ mdi-account-group-outline
34
+ </v-icon>
35
+ {{ edgeGlobal.currentOrganizationObject.name }}
36
+ </v-toolbar>
37
+ <v-card-text>
38
+ <v-row>
39
+ <v-col cols="3">
40
+ <v-card>
41
+ <v-list :lines="false" density="compact" nav>
42
+ <v-list-subheader class="">
43
+ Organization
44
+ </v-list-subheader>
45
+ <v-list-item link to="/app/account/organization-settings">
46
+ <v-list-item-title>Settings</v-list-item-title>
47
+ </v-list-item>
48
+
49
+ <v-list-item link to="/app/account/organization-members">
50
+ <v-list-item-title>Members</v-list-item-title>
51
+ </v-list-item>
52
+ </v-list>
53
+ <v-divider />
54
+
55
+ <v-list :lines="false" density="compact" nav>
56
+ <v-list-subheader class="">
57
+ My Settings
58
+ </v-list-subheader>
59
+ <v-list-item link to="/app/account/my-profile">
60
+ <v-list-item-title>Profile</v-list-item-title>
61
+ </v-list-item>
62
+
63
+ <v-list-item link to="/app/account/my-account">
64
+ <v-list-item-title>Account</v-list-item-title>
65
+ </v-list-item>
66
+ <v-list-item link to="/app/account/my-organizations">
67
+ <v-list-item-title>Organizations</v-list-item-title>
68
+ </v-list-item>
69
+ </v-list>
70
+
71
+ <v-divider />
72
+ </v-card>
73
+ </v-col>
74
+ <v-col cols="9">
75
+ <edge-organization-settings v-if="site === 'organization-settings'" :org-fields="orgFields" />
76
+ <edge-my-account v-if="site === 'my-account'" />
77
+ <edge-my-profile v-if="site === 'my-profile'" :meta-fields="metaFields" />
78
+ <edge-organization-members v-if="site === 'organization-members'" />
79
+ <edge-my-organizations v-if="site === 'my-organizations'" :registration-code="config.public.registrationCode" />
80
+ </v-col>
81
+ </v-row>
82
+ </v-card-text>
83
+ </v-card>
84
+ </template>
@@ -0,0 +1,35 @@
1
+ <script setup>
2
+ const edgeFirebase = inject('edgeFirebase')
3
+ const edgeGlobal = inject('edgeGlobal')
4
+ </script>
5
+
6
+ <template>
7
+ <v-bottom-navigation>
8
+ <v-btn to="/app/dashboard">
9
+ <v-icon>mdi-view-dashboard</v-icon>
10
+
11
+ Dashboard
12
+ </v-btn>
13
+ <v-btn to="/app/dashboard/subthings">
14
+ <v-icon>mdi-cube</v-icon>
15
+
16
+ Sub Things
17
+ </v-btn>
18
+
19
+ <v-btn to="/app/account/organization-settings">
20
+ <v-icon>mdi-cog</v-icon>
21
+
22
+ Settings
23
+ </v-btn>
24
+
25
+ <v-btn @click="logOut(edgeFirebase, edgeGlobal)">
26
+ <v-icon>mdi-logout</v-icon>
27
+
28
+ Logout
29
+ </v-btn>
30
+ </v-bottom-navigation>
31
+ </template>
32
+
33
+ <style lang="scss" scoped>
34
+
35
+ </style>
@@ -0,0 +1,186 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ collection: {
4
+ type: String,
5
+ required: true,
6
+ },
7
+ })
8
+
9
+ const edgeFirebase = inject('edgeFirebase')
10
+ const edgeGlobal = inject('edgeGlobal')
11
+ const router = useRouter()
12
+
13
+ const state = reactive({
14
+ form: false,
15
+ menu: false,
16
+ dialog: false,
17
+ apiKeys: [],
18
+ filter: '',
19
+ empty: false,
20
+ afterMount: false,
21
+ deleteDialog: false,
22
+ deleteItemName: '',
23
+ deleteItemDocId: '',
24
+ })
25
+
26
+ const gotoSite = (docId) => {
27
+ router.push(`/app/dashboard/${props.collection}/${docId}`)
28
+ }
29
+
30
+ const capitalizeFirstLetter = (str) => {
31
+ return str.charAt(0).toUpperCase() + str.slice(1)
32
+ }
33
+
34
+ const singularize = (word) => {
35
+ if (word.endsWith('ies')) {
36
+ return `${word.slice(0, -3)}y`
37
+ }
38
+ else if (word.endsWith('es')) {
39
+ return word.slice(0, -2)
40
+ }
41
+ else if (word.endsWith('s')) {
42
+ return word.slice(0, -1)
43
+ }
44
+ else {
45
+ return word
46
+ }
47
+ }
48
+
49
+ const filtered = computed(() => {
50
+ if (edgeGlobal.objHas(edgeFirebase.data, `${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`) === false) {
51
+ return []
52
+ }
53
+
54
+ const allData = Object.values(edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`])
55
+
56
+ const filtered = allData.filter((entry) => {
57
+ if (state.filter.trim() === '') {
58
+ return true
59
+ }
60
+
61
+ // Modify the condition as needed, e.g., using "startsWith" or "includes"
62
+ return entry.name.toLowerCase().includes(state.filter.toLowerCase())
63
+ })
64
+ return filtered.sort((a, b) => {
65
+ if (a.name < b.name) {
66
+ return -1
67
+ }
68
+ if (a.name > b.name) {
69
+ return 1
70
+ }
71
+ return 0
72
+ })
73
+ })
74
+
75
+ onBeforeMount (async () => {
76
+ await edgeFirebase.startSnapshot(`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`)
77
+ state.afterMount = true
78
+ })
79
+
80
+ const deleteItem = (docId) => {
81
+ state.deleteDialog = true
82
+ state.deleteItemName = edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`][docId].name
83
+ state.deleteItemDocId = docId
84
+ }
85
+
86
+ const deleteAction = () => {
87
+ edgeFirebase.removeDoc(`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`, state.deleteItemDocId)
88
+ state.deleteDialog = false
89
+ }
90
+ </script>
91
+
92
+ <template>
93
+ <v-card v-if="state.afterMount">
94
+ <v-toolbar flat>
95
+ <v-toolbar-title>{{ capitalizeFirstLetter(props.collection) }}</v-toolbar-title>
96
+ <v-text-field
97
+ v-model="state.filter"
98
+ label="Filter"
99
+ prepend-icon="mdi-filter"
100
+ variant="underlined"
101
+ hide-details
102
+ clearable
103
+ @click:clear="state.filter = ''"
104
+ />
105
+ <v-spacer />
106
+ <v-btn variant="outlined" :to="`/app/dashboard/${props.collection}/new`">
107
+ Add {{ singularize(props.collection) }}
108
+ </v-btn>
109
+ </v-toolbar>
110
+ <v-card-text>
111
+ <v-list lines="two">
112
+ <template v-for="item in filtered" :key="item.docId">
113
+ <v-list-item @click="gotoSite(item.docId)">
114
+ <template #prepend>
115
+ <v-avatar color="grey-darken-1">
116
+ <v-icon>mdi-file-edit</v-icon>
117
+ </v-avatar>
118
+ </template>
119
+
120
+ <v-list-item-title>{{ item.name }}</v-list-item-title>
121
+ <template #append>
122
+ <v-btn
123
+ color="grey-lighten-1"
124
+ icon="mdi-delete"
125
+ variant="text"
126
+ @click.stop="deleteItem(item.docId)"
127
+ />
128
+ </template>
129
+ </v-list-item>
130
+ <v-divider
131
+ inset
132
+ />
133
+ </template>
134
+ </v-list>
135
+ </v-card-text>
136
+ <v-dialog
137
+ v-model="state.deleteDialog"
138
+ persistent
139
+ max-width="600"
140
+ transition="fade-transition"
141
+ >
142
+ <v-card>
143
+ <v-toolbar flat>
144
+ <v-icon class="mx-4">
145
+ mdi-list-box
146
+ </v-icon>
147
+ Delete
148
+ <v-spacer />
149
+
150
+ <v-btn
151
+ type="submit"
152
+ color="primary"
153
+ icon
154
+ @click="state.deleteDialog = false"
155
+ >
156
+ <v-icon> mdi-close</v-icon>
157
+ </v-btn>
158
+ </v-toolbar>
159
+ <v-card-title>Are you sure you want to delete "{{ state.deleteItemName }}"?</v-card-title>
160
+ <v-card-text>Hey there, you're about to banish "{{ state.deleteItemName }}" into the endless abyss of deletion, never to be seen again. Remember, this is not just another trashy reality TV show - there are no sudden comebacks or surprise twists. Once it's gone, it's gone, just like the dignity of anyone who wears socks with sandals. Are you 100% sure you're ready to make ""{{ state.deleteItemName }}"" disappear like a magician's assistant? Tick-tock, the choice is yours!</v-card-text>
161
+ <v-card-actions>
162
+ <v-spacer />
163
+ <v-btn
164
+ color="blue-darken-1"
165
+ variant="text"
166
+ @click="state.deleteDialog = false"
167
+ >
168
+ Cancel
169
+ </v-btn>
170
+ <v-btn
171
+ type="submit"
172
+ color="error"
173
+ variant="text"
174
+ @click="deleteAction()"
175
+ >
176
+ Delete
177
+ </v-btn>
178
+ </v-card-actions>
179
+ </v-card>
180
+ </v-dialog>
181
+ </v-card>
182
+ </template>
183
+
184
+ <style lang="scss" scoped>
185
+
186
+ </style>