@edgedev/create-edge-app 1.1.28 → 1.2.29
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.
- package/agents.md +2 -0
- package/bin/cli.js +13 -5
- package/deploy-services.sh +237 -0
- package/deploy.sh +88 -1
- package/edge/components/auth/register.vue +51 -0
- package/edge/components/cms/block.vue +29 -16
- package/edge/components/cms/blockEditor.vue +748 -7
- package/edge/components/cms/codeEditor.vue +24 -2
- package/edge/components/cms/htmlContent.vue +10 -2
- package/edge/components/cms/mediaManager.vue +19 -3
- package/edge/components/cms/menu.vue +231 -34
- package/edge/components/cms/optionsSelect.vue +20 -3
- package/edge/components/cms/page.vue +9 -0
- package/edge/components/cms/site.vue +114 -5
- package/edge/components/cms/siteSettingsForm.vue +7 -0
- package/edge/components/cms/themeEditor.vue +9 -3
- package/edge/components/dashboard.vue +22 -3
- package/edge/components/imagePicker.vue +126 -0
- package/edge/components/myAccount.vue +1 -0
- package/edge/components/myProfile.vue +345 -61
- package/edge/components/organizationMembers.vue +569 -261
- package/edge/components/shad/combobox.vue +2 -2
- package/edge/components/shad/number.vue +2 -2
- package/edge/composables/global.ts +5 -2
- package/edge/composables/structuredDataTemplates.js +6 -6
- package/firebase_init.sh +63 -2
- package/package.json +1 -1
- package/services/.deploy.shared.env.example +12 -0
package/agents.md
CHANGED
|
@@ -7,6 +7,7 @@ This project is Nuxt 3 + Vue 3, SPA mode. Follow these rules so new code matches
|
|
|
7
7
|
- Components and composables from `edge/composables/**` are auto-imported; avoid manual imports unless needed.
|
|
8
8
|
- Utilities: use `cn` from `@/lib/utils` for class merging, `lucide-vue-next` for icons, Tailwind for layout/styling. Keep comments minimal and useful.
|
|
9
9
|
- Components under `edge/components` are globally registered with the `edge-` prefix (e.g., `edge-dashboard`, `edge-editor`, `edge-shad-button`).
|
|
10
|
+
- Check the project's lint tooling (package.json scripts + config files) before coding and follow its rules; avoid using functions before they are defined.
|
|
10
11
|
|
|
11
12
|
## Firebase and data access
|
|
12
13
|
- Never import Firebase SDKs directly. All Auth/Firestore/Functions/Storage access goes through the injected `edgeFirebase` plugin (`plugins/firebase.client.ts` from `@edgedev/firebase`).
|
|
@@ -78,3 +79,4 @@ Adjust props (search, filters, pagination, save overrides) using the existing co
|
|
|
78
79
|
## Firebase Functions guidance
|
|
79
80
|
- Review `functions/config.js`, `functions/edgeFirebase.js`, and `functions/cms.js` to mirror established patterns, but do not edit those files.
|
|
80
81
|
- When adding new cloud functions, create a new JS file under `functions/` and export handlers using the shared imports from `config.js`. Wire it up by requiring it in `functions/index.js` (same pattern as `stripe.js`), instead of modifying restricted files.
|
|
82
|
+
- For every `onCall` function, always enforce both checks up front: `request.auth?.uid` must exist, and `request.data?.uid` must exactly match `request.auth.uid`. Throw `HttpsError('unauthenticated', ...)` when auth is missing and `HttpsError('permission-denied', ...)` when the uid does not match.
|
package/bin/cli.js
CHANGED
|
@@ -63,8 +63,9 @@ const cleanGitignore = (repoName) => {
|
|
|
63
63
|
|
|
64
64
|
const repoName = process.argv[2]
|
|
65
65
|
|
|
66
|
-
const gitCheckoutCommand = `git clone
|
|
67
|
-
const
|
|
66
|
+
const gitCheckoutCommand = `git clone https://github.com/Edge-Marketing-and-Design/edgeApp.git ${repoName}`
|
|
67
|
+
const removeOriginRemoteCommand = `cd ${repoName} && git remote remove origin`
|
|
68
|
+
const addEdgeComponentsRemoteCommand = `cd ${repoName} && git remote add edge-vue-components https://github.com/Edge-Marketing-and-Design/edge-vue-components.git`
|
|
68
69
|
const installDependenciesCommand = `cd ${repoName} && pnpm store prune && pnpm install --force --ignore-scripts=false`
|
|
69
70
|
const installFunctionDependenciesCommand = `cd ${repoName}/functions && npm install`
|
|
70
71
|
// const cloneFirebaseFrameworkCommand = `cd ${repoName} && git clone https://github.com/Edge-Marketing-and-Design/edge-vue-components.git edge`
|
|
@@ -75,9 +76,15 @@ if (!checkedOut) {
|
|
|
75
76
|
process.exit(1)
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
console.log(`
|
|
79
|
-
const
|
|
80
|
-
if (!
|
|
79
|
+
console.log(`Detaching template origin remote from ${repoName}...`)
|
|
80
|
+
const removedOriginRemote = runCommand(removeOriginRemoteCommand)
|
|
81
|
+
if (!removedOriginRemote) {
|
|
82
|
+
process.exit(1)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log(`Adding edge-vue-components remote to ${repoName}...`)
|
|
86
|
+
const addedEdgeComponentsRemote = runCommand(addEdgeComponentsRemoteCommand)
|
|
87
|
+
if (!addedEdgeComponentsRemote) {
|
|
81
88
|
process.exit(1)
|
|
82
89
|
}
|
|
83
90
|
|
|
@@ -112,4 +119,5 @@ if (!installedFunctionDeps) {
|
|
|
112
119
|
// }
|
|
113
120
|
|
|
114
121
|
console.log(`Successfully created ${repoName}!`)
|
|
122
|
+
console.log(`Add your own git origin with: cd ${repoName} && git remote add origin <your-repo-url>`)
|
|
115
123
|
console.log(`cd into ${repoName} and run 'sh firebase_init.sh' to initialize your firebase project.`)
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
5
|
+
SERVICES_DIR="$ROOT_DIR/services"
|
|
6
|
+
ROOT_ENV_FILE="$ROOT_DIR/.env"
|
|
7
|
+
SHARED_DEPLOY_ENV_FILE="$SERVICES_DIR/.deploy.shared.env"
|
|
8
|
+
FIREBASERC_FILE="$ROOT_DIR/.firebaserc"
|
|
9
|
+
FUNCTIONS_PROD_ENV_FILE="$ROOT_DIR/functions/.env.prod"
|
|
10
|
+
|
|
11
|
+
trim() {
|
|
12
|
+
printf '%s' "$1" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
load_env_file() {
|
|
16
|
+
file="$1"
|
|
17
|
+
if [ ! -f "$file" ]; then
|
|
18
|
+
return 0
|
|
19
|
+
fi
|
|
20
|
+
set -a
|
|
21
|
+
# shellcheck disable=SC1090
|
|
22
|
+
. "$file"
|
|
23
|
+
set +a
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
extract_default_project_id() {
|
|
27
|
+
if [ ! -f "$FIREBASERC_FILE" ]; then
|
|
28
|
+
printf ''
|
|
29
|
+
return 0
|
|
30
|
+
fi
|
|
31
|
+
node -e "const fs=require('fs');const p=process.argv[1];const data=JSON.parse(fs.readFileSync(p,'utf8'));process.stdout.write((data.projects&&data.projects.default)||'')" "$FIREBASERC_FILE"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
service_name_to_env_key() {
|
|
35
|
+
service_name="$1"
|
|
36
|
+
normalized=''
|
|
37
|
+
normalized="$(printf '%s' "$service_name" \
|
|
38
|
+
| tr '[:lower:]' '[:upper:]' \
|
|
39
|
+
| sed -E 's/[^A-Z0-9]+/_/g; s/^_+//; s/_+$//')"
|
|
40
|
+
printf '%s_SERVICE' "$normalized"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
upsert_env_key_value() {
|
|
44
|
+
file="$1"
|
|
45
|
+
key="$2"
|
|
46
|
+
value="$3"
|
|
47
|
+
tmp=''
|
|
48
|
+
|
|
49
|
+
mkdir -p "$(dirname "$file")"
|
|
50
|
+
if [ ! -f "$file" ]; then
|
|
51
|
+
printf '%s=%s\n' "$key" "$value" > "$file"
|
|
52
|
+
return 0
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
tmp="$(mktemp)"
|
|
56
|
+
awk -v key="$key" -v value="$value" '
|
|
57
|
+
BEGIN { updated = 0 }
|
|
58
|
+
$0 ~ ("^" key "=") {
|
|
59
|
+
if (!updated) {
|
|
60
|
+
print key "=" value
|
|
61
|
+
updated = 1
|
|
62
|
+
}
|
|
63
|
+
next
|
|
64
|
+
}
|
|
65
|
+
{ print }
|
|
66
|
+
END {
|
|
67
|
+
if (!updated) {
|
|
68
|
+
print key "=" value
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
' "$file" > "$tmp"
|
|
72
|
+
mv "$tmp" "$file"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
build_update_env_vars_arg() {
|
|
76
|
+
env_file="$1"
|
|
77
|
+
joined=''
|
|
78
|
+
keys="$(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$env_file" | cut -d= -f1 || true)"
|
|
79
|
+
|
|
80
|
+
for key in $keys; do
|
|
81
|
+
case "$key" in
|
|
82
|
+
SERVICE_NAME|SOURCE_DIR|PROJECT_ID|REGION|ALLOW_UNAUTHENTICATED|INVOKER_MEMBERS)
|
|
83
|
+
continue
|
|
84
|
+
;;
|
|
85
|
+
esac
|
|
86
|
+
value="$(eval "printf '%s' \"\${$key-}\"")"
|
|
87
|
+
if [ -n "$joined" ]; then
|
|
88
|
+
joined="${joined},${key}=${value}"
|
|
89
|
+
else
|
|
90
|
+
joined="${key}=${value}"
|
|
91
|
+
fi
|
|
92
|
+
done
|
|
93
|
+
|
|
94
|
+
printf '%s' "$joined"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if ! command -v gcloud >/dev/null 2>&1; then
|
|
98
|
+
echo "Deploy aborted: gcloud CLI not found." >&2
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
DEFAULT_PROJECT_ID="$(extract_default_project_id)"
|
|
103
|
+
if [ -z "${PROJECT_ID:-}" ] && [ -z "$DEFAULT_PROJECT_ID" ]; then
|
|
104
|
+
echo "Deploy aborted: PROJECT_ID is not set and .firebaserc has no default project." >&2
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
deployed_count=0
|
|
109
|
+
|
|
110
|
+
for service_env_file in "$SERVICES_DIR"/*/.deploy.env; do
|
|
111
|
+
if [ ! -f "$service_env_file" ]; then
|
|
112
|
+
continue
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
service_dir="$(cd "$(dirname "$service_env_file")" && pwd)"
|
|
116
|
+
|
|
117
|
+
(
|
|
118
|
+
load_env_file "$ROOT_ENV_FILE"
|
|
119
|
+
load_env_file "$SHARED_DEPLOY_ENV_FILE"
|
|
120
|
+
load_env_file "$service_env_file"
|
|
121
|
+
|
|
122
|
+
service_name="${SERVICE_NAME:-$(basename "$service_dir")}"
|
|
123
|
+
project_id="${PROJECT_ID:-$DEFAULT_PROJECT_ID}"
|
|
124
|
+
region="${REGION:-${VITE_FIREBASE_FUNCTIONS_REGION:-us-central1}}"
|
|
125
|
+
source_dir="${SOURCE_DIR:-$service_dir}"
|
|
126
|
+
allow_unauthenticated="$(printf '%s' "${ALLOW_UNAUTHENTICATED:-false}" | tr '[:upper:]' '[:lower:]')"
|
|
127
|
+
invoker_members_raw="${INVOKER_MEMBERS:-}"
|
|
128
|
+
|
|
129
|
+
if [ -z "$service_name" ]; then
|
|
130
|
+
echo "Skipping '$service_dir': SERVICE_NAME is empty." >&2
|
|
131
|
+
exit 0
|
|
132
|
+
fi
|
|
133
|
+
if [ -z "$project_id" ]; then
|
|
134
|
+
echo "Skipping '$service_dir': PROJECT_ID resolved empty." >&2
|
|
135
|
+
exit 0
|
|
136
|
+
fi
|
|
137
|
+
if [ -z "$region" ]; then
|
|
138
|
+
echo "Skipping '$service_dir': REGION resolved empty." >&2
|
|
139
|
+
exit 0
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
case "$source_dir" in
|
|
143
|
+
/*) ;;
|
|
144
|
+
*) source_dir="$ROOT_DIR/$source_dir" ;;
|
|
145
|
+
esac
|
|
146
|
+
if [ ! -d "$source_dir" ]; then
|
|
147
|
+
echo "Skipping '$service_dir': SOURCE_DIR '$source_dir' not found." >&2
|
|
148
|
+
exit 0
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
update_env_vars_arg="$(build_update_env_vars_arg "$service_env_file")"
|
|
152
|
+
|
|
153
|
+
echo "Deploying service '$service_name' from '$source_dir' (project=$project_id, region=$region)..."
|
|
154
|
+
if [ -n "$update_env_vars_arg" ]; then
|
|
155
|
+
if [ "$allow_unauthenticated" = 'true' ]; then
|
|
156
|
+
gcloud run deploy "$service_name" \
|
|
157
|
+
--project "$project_id" \
|
|
158
|
+
--region "$region" \
|
|
159
|
+
--source "$source_dir" \
|
|
160
|
+
--allow-unauthenticated \
|
|
161
|
+
--update-env-vars "$update_env_vars_arg"
|
|
162
|
+
else
|
|
163
|
+
gcloud run deploy "$service_name" \
|
|
164
|
+
--project "$project_id" \
|
|
165
|
+
--region "$region" \
|
|
166
|
+
--source "$source_dir" \
|
|
167
|
+
--no-allow-unauthenticated \
|
|
168
|
+
--update-env-vars "$update_env_vars_arg"
|
|
169
|
+
fi
|
|
170
|
+
else
|
|
171
|
+
if [ "$allow_unauthenticated" = 'true' ]; then
|
|
172
|
+
gcloud run deploy "$service_name" \
|
|
173
|
+
--project "$project_id" \
|
|
174
|
+
--region "$region" \
|
|
175
|
+
--source "$source_dir" \
|
|
176
|
+
--allow-unauthenticated
|
|
177
|
+
else
|
|
178
|
+
gcloud run deploy "$service_name" \
|
|
179
|
+
--project "$project_id" \
|
|
180
|
+
--region "$region" \
|
|
181
|
+
--source "$source_dir" \
|
|
182
|
+
--no-allow-unauthenticated
|
|
183
|
+
fi
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
service_url="$(gcloud run services describe "$service_name" \
|
|
187
|
+
--project "$project_id" \
|
|
188
|
+
--region "$region" \
|
|
189
|
+
--format='value(status.url)')"
|
|
190
|
+
if [ -n "$service_url" ]; then
|
|
191
|
+
service_env_key="$(service_name_to_env_key "$service_name")"
|
|
192
|
+
upsert_env_key_value "$FUNCTIONS_PROD_ENV_FILE" "$service_env_key" "$service_url"
|
|
193
|
+
echo "Updated $FUNCTIONS_PROD_ENV_FILE with ${service_env_key}."
|
|
194
|
+
else
|
|
195
|
+
echo "Warning: could not resolve URL for '$service_name'; skipped functions/.env.prod update." >&2
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
if [ "$allow_unauthenticated" != 'true' ]; then
|
|
199
|
+
gcloud run services remove-iam-policy-binding "$service_name" \
|
|
200
|
+
--project "$project_id" \
|
|
201
|
+
--region "$region" \
|
|
202
|
+
--member="allUsers" \
|
|
203
|
+
--role="roles/run.invoker" \
|
|
204
|
+
--quiet >/dev/null 2>&1 || true
|
|
205
|
+
|
|
206
|
+
if [ -n "$invoker_members_raw" ]; then
|
|
207
|
+
printf '%s\n' "$invoker_members_raw" | tr ',' '\n' | while IFS= read -r member; do
|
|
208
|
+
clean_member="$(trim "$member")"
|
|
209
|
+
if [ -z "$clean_member" ]; then
|
|
210
|
+
continue
|
|
211
|
+
fi
|
|
212
|
+
case "$clean_member" in
|
|
213
|
+
*:*) ;;
|
|
214
|
+
*) clean_member="serviceAccount:${clean_member}" ;;
|
|
215
|
+
esac
|
|
216
|
+
gcloud run services add-iam-policy-binding "$service_name" \
|
|
217
|
+
--project "$project_id" \
|
|
218
|
+
--region "$region" \
|
|
219
|
+
--member="$clean_member" \
|
|
220
|
+
--role="roles/run.invoker" \
|
|
221
|
+
--quiet >/dev/null
|
|
222
|
+
done
|
|
223
|
+
else
|
|
224
|
+
echo "Info: '$service_name' is private and INVOKER_MEMBERS is empty. Using existing Cloud Run invokers (for example, the default compute service account if already granted)." >&2
|
|
225
|
+
fi
|
|
226
|
+
fi
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
deployed_count=$((deployed_count + 1))
|
|
230
|
+
done
|
|
231
|
+
|
|
232
|
+
if [ "$deployed_count" -eq 0 ]; then
|
|
233
|
+
echo "No services deployed. Add a .deploy.env file under services/<service>/ to opt in."
|
|
234
|
+
exit 0
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
echo "Services deploy complete. Deployed $deployed_count service(s)."
|
package/deploy.sh
CHANGED
|
@@ -1,5 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
check_repo() {
|
|
5
|
+
local repo_path="$1"
|
|
6
|
+
local label="$2"
|
|
7
|
+
|
|
8
|
+
if ! git -C "$repo_path" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
9
|
+
echo "Deploy aborted: ${label} repo not found at '$repo_path'." >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
local current_branch
|
|
14
|
+
current_branch="$(git -C "$repo_path" rev-parse --abbrev-ref HEAD)"
|
|
15
|
+
if [ "$current_branch" != "main" ]; then
|
|
16
|
+
echo "Deploy aborted: ${label} branch is '$current_branch'. Switch to 'main' before deploying." >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
if ! git -C "$repo_path" diff --quiet || ! git -C "$repo_path" diff --cached --quiet; then
|
|
21
|
+
echo "Deploy aborted: ${label} has uncommitted changes. Commit or stash before deploying." >&2
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if ! git -C "$repo_path" rev-parse --abbrev-ref --symbolic-full-name @{upstream} >/dev/null 2>&1; then
|
|
26
|
+
echo "Deploy aborted: ${label} has no upstream set for 'main'. Set upstream before deploying." >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
git -C "$repo_path" fetch --quiet
|
|
31
|
+
|
|
32
|
+
local counts behind ahead
|
|
33
|
+
counts="$(git -C "$repo_path" rev-list --left-right --count @{upstream}...HEAD)"
|
|
34
|
+
set -- $counts
|
|
35
|
+
behind="${1:-0}"
|
|
36
|
+
ahead="${2:-0}"
|
|
37
|
+
if [ "$behind" -ne 0 ] && [ "$ahead" -ne 0 ]; then
|
|
38
|
+
echo "Deploy aborted: ${label} branch has diverged (ahead $ahead, behind $behind). Pull/rebase and push." >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
if [ "$behind" -ne 0 ]; then
|
|
42
|
+
echo "Deploy aborted: ${label} main is behind upstream by $behind commits. Pull before deploying." >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
if [ "$ahead" -ne 0 ]; then
|
|
46
|
+
echo "Deploy aborted: ${label} main is ahead of upstream by $ahead commits. Push before deploying." >&2
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
warn_edge_subtree_sync() {
|
|
52
|
+
if ! git rev-parse --verify HEAD:edge >/dev/null 2>&1; then
|
|
53
|
+
echo "Deploy aborted: edge subtree path ('edge') is missing from HEAD." >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if ! git remote get-url edge-vue-components >/dev/null 2>&1; then
|
|
58
|
+
echo "Deploy aborted: remote 'edge-vue-components' is not configured." >&2
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
if ! git fetch --quiet edge-vue-components; then
|
|
63
|
+
echo "Deploy aborted: failed to fetch 'edge-vue-components'." >&2
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
local up_tree local_tree
|
|
68
|
+
if ! up_tree="$(git rev-parse edge-vue-components/main^{tree} 2>/dev/null)"; then
|
|
69
|
+
echo "Deploy aborted: could not resolve edge-vue-components/main." >&2
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
if ! local_tree="$(git rev-parse HEAD:edge 2>/dev/null)"; then
|
|
73
|
+
echo "Deploy aborted: could not resolve HEAD:edge." >&2
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if [ "$up_tree" = "$local_tree" ]; then
|
|
78
|
+
echo "edge subtree is in sync with edge-vue-components/main"
|
|
79
|
+
else
|
|
80
|
+
echo "Warning: edge subtree differs from edge-vue-components/main. Continuing deploy." >&2
|
|
81
|
+
fi
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
check_repo "." "root"
|
|
85
|
+
warn_edge_subtree_sync
|
|
86
|
+
|
|
1
87
|
pnpm run generate
|
|
2
88
|
export NODE_ENV=production
|
|
3
89
|
firebase deploy --only functions
|
|
4
90
|
firebase deploy --only hosting
|
|
5
|
-
firebase deploy --only firestore
|
|
91
|
+
firebase deploy --only firestore
|
|
92
|
+
firebase deploy --only storage
|
|
@@ -77,6 +77,47 @@ const register = reactive({
|
|
|
77
77
|
requestedOrgId: '',
|
|
78
78
|
})
|
|
79
79
|
|
|
80
|
+
const resolveAuthEmail = () => {
|
|
81
|
+
return (
|
|
82
|
+
edgeFirebase?.user?.email
|
|
83
|
+
|| edgeFirebase?.user?.firebaseUser?.email
|
|
84
|
+
|| edgeFirebase?.user?.firebaseUser?.providerData?.find(p => p?.email)?.email
|
|
85
|
+
|| ''
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const waitForUserSnapshot = async (timeoutMs = 8000) => {
|
|
90
|
+
const findUser = () => {
|
|
91
|
+
const users = Object.values(edgeFirebase.state?.users || {})
|
|
92
|
+
const stagedDocId = edgeFirebase?.user?.stagedDocId
|
|
93
|
+
const uid = edgeFirebase?.user?.uid
|
|
94
|
+
return users.find(u => (stagedDocId && u?.docId === stagedDocId) || (uid && u?.userId === uid))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (findUser())
|
|
98
|
+
return true
|
|
99
|
+
|
|
100
|
+
return await new Promise((resolve) => {
|
|
101
|
+
let timeoutId = null
|
|
102
|
+
const stop = watch(
|
|
103
|
+
() => edgeFirebase.state?.users,
|
|
104
|
+
() => {
|
|
105
|
+
if (findUser()) {
|
|
106
|
+
stop()
|
|
107
|
+
if (timeoutId)
|
|
108
|
+
clearTimeout(timeoutId)
|
|
109
|
+
resolve(true)
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{ immediate: true, deep: true },
|
|
113
|
+
)
|
|
114
|
+
timeoutId = setTimeout(() => {
|
|
115
|
+
stop()
|
|
116
|
+
resolve(false)
|
|
117
|
+
}, timeoutMs)
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
80
121
|
const onSubmit = async () => {
|
|
81
122
|
state.registering = true
|
|
82
123
|
|
|
@@ -108,6 +149,9 @@ const onSubmit = async () => {
|
|
|
108
149
|
if (state.showRegistrationCode || !props.registrationCode) {
|
|
109
150
|
register.registrationCode = state.registrationCode
|
|
110
151
|
}
|
|
152
|
+
if (state.provider === 'email' && register.email) {
|
|
153
|
+
register.meta.email = register.email
|
|
154
|
+
}
|
|
111
155
|
const result = await edgeFirebase.registerUser(register, state.provider)
|
|
112
156
|
state.error.error = !result.success
|
|
113
157
|
if (result.message === `${props.requestedOrgIdLabel} already exists.`) {
|
|
@@ -118,6 +162,13 @@ const onSubmit = async () => {
|
|
|
118
162
|
result.message = `${orgLabel} already exists. Please choose another.`
|
|
119
163
|
}
|
|
120
164
|
state.error.message = result.message.code ? result.message.code : result.message
|
|
165
|
+
if (result.success) {
|
|
166
|
+
const authEmail = resolveAuthEmail()
|
|
167
|
+
if (authEmail && (!register.meta.email || register.meta.email !== authEmail)) {
|
|
168
|
+
await waitForUserSnapshot()
|
|
169
|
+
await edgeFirebase.setUserMeta({ email: authEmail })
|
|
170
|
+
}
|
|
171
|
+
}
|
|
121
172
|
}
|
|
122
173
|
|
|
123
174
|
state.registering = false
|
|
@@ -122,11 +122,13 @@ const sanitizeQueryItems = (meta) => {
|
|
|
122
122
|
return cleaned
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
const resetArrayItems = (field) => {
|
|
125
|
+
const resetArrayItems = (field, metaSource = null) => {
|
|
126
|
+
const meta = metaSource || modelValue.value?.meta || {}
|
|
127
|
+
const fieldMeta = meta?.[field]
|
|
126
128
|
if (!state.arrayItems?.[field]) {
|
|
127
129
|
state.arrayItems[field] = {}
|
|
128
130
|
}
|
|
129
|
-
for (const schemaItem of
|
|
131
|
+
for (const schemaItem of (fieldMeta?.schema || [])) {
|
|
130
132
|
if (schemaItem.type === 'text') {
|
|
131
133
|
state.arrayItems[field][schemaItem.field] = ''
|
|
132
134
|
}
|
|
@@ -148,25 +150,35 @@ const resetArrayItems = (field) => {
|
|
|
148
150
|
const openEditor = async () => {
|
|
149
151
|
if (!props.editMode)
|
|
150
152
|
return
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
const blockData = edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/blocks`]?.[modelValue.value.blockId]
|
|
154
|
+
const templateMeta = blockData?.meta || modelValue.value?.meta || {}
|
|
155
|
+
const storedMeta = modelValue.value?.meta || {}
|
|
156
|
+
const mergedMeta = edgeGlobal.dupObject(templateMeta) || {}
|
|
157
|
+
|
|
158
|
+
for (const key of Object.keys(mergedMeta)) {
|
|
159
|
+
const storedField = storedMeta?.[key]
|
|
160
|
+
if (!storedField || typeof storedField !== 'object')
|
|
161
|
+
continue
|
|
162
|
+
if (storedField.queryItems && typeof storedField.queryItems === 'object') {
|
|
163
|
+
mergedMeta[key].queryItems = edgeGlobal.dupObject(storedField.queryItems)
|
|
164
|
+
}
|
|
165
|
+
if (storedField.limit !== undefined) {
|
|
166
|
+
mergedMeta[key].limit = storedField.limit
|
|
156
167
|
}
|
|
157
168
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (blockData?.meta) {
|
|
164
|
-
for (const key of Object.keys(blockData.meta)) {
|
|
165
|
-
if (!(key in state.metaUpdate)) {
|
|
166
|
-
state.metaUpdate[key] = blockData.meta[key]
|
|
169
|
+
|
|
170
|
+
for (const key of Object.keys(mergedMeta || {})) {
|
|
171
|
+
if (mergedMeta[key]?.type === 'array' && mergedMeta[key]?.schema) {
|
|
172
|
+
if (!mergedMeta[key]?.api) {
|
|
173
|
+
resetArrayItems(key, mergedMeta)
|
|
167
174
|
}
|
|
168
175
|
}
|
|
169
176
|
}
|
|
177
|
+
|
|
178
|
+
state.draft = JSON.parse(JSON.stringify(modelValue.value?.values || {}))
|
|
179
|
+
state.meta = JSON.parse(JSON.stringify(mergedMeta || {}))
|
|
180
|
+
ensureQueryItemsDefaults(state.meta)
|
|
181
|
+
state.metaUpdate = edgeGlobal.dupObject(mergedMeta) || {}
|
|
170
182
|
if (blockData?.values) {
|
|
171
183
|
for (const key of Object.keys(blockData.values)) {
|
|
172
184
|
if (!(key in state.draft)) {
|
|
@@ -682,6 +694,7 @@ const getTagsFromPosts = computed(() => {
|
|
|
682
694
|
v-model="state.meta[entry.field].queryItems[option.field]"
|
|
683
695
|
:option="option"
|
|
684
696
|
:label="genTitleFromField(option)"
|
|
697
|
+
:multiple="option?.multiple || false"
|
|
685
698
|
/>
|
|
686
699
|
</div>
|
|
687
700
|
</template>
|