Skip to content

Commit e6a8e62

Browse files
committed
initial dashboard sketch
1 parent b60f1d6 commit e6a8e62

15 files changed

Lines changed: 3378 additions & 36 deletions

firebase.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"predeploy": ["npm --prefix \"$RESOURCE_DIR\" run lint"],
44
"source": "functions"
55
},
6+
"firestore": {
7+
"rules": "firebase/firebase.rules"
8+
},
69
"emulators": {
710
"functions": {
811
"port": 5001

firebase/firebase.rules

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,41 @@ service cloud.firestore {
1212
return isAuthenticated() &&
1313
request.auth.uid == doc.data.firebaseAnonAuthID;
1414
}
15+
16+
// Helper function to check if user has admin access
17+
function isAdmin() {
18+
return isAuthenticated() && (
19+
// Add your admin user IDs here (replace with actual UIDs from the dashboard)
20+
request.auth.uid == "62hhFmQoJwOMjnbkeNG9y7uhY8i2" ||
21+
request.auth.uid == "ANOTHER_ADMIN_UID_HERE" ||
22+
// Option for custom claims (if you implement that later)
23+
request.auth.token.admin == true ||
24+
request.auth.token.role == "admin"
25+
);
26+
}
1527

1628
// Rules for testing collection
1729
match /testing/{studyID} {
1830
allow create: if isAuthenticated() && !exists(/databases/$(database)/documents/testing/$(studyID));
19-
allow read: if isAuthenticated(); // Add this line
31+
allow read: if isAuthenticated();
32+
allow list: if isAdmin(); // Admins can list all studies
33+
2034
match /data/{partID} {
2135
allow create: if isAuthenticated() &&
2236
request.resource.data.firebaseAnonAuthID == request.auth.uid;
23-
allow read, update: if isOwner(resource);
37+
allow read, update: if isOwner(resource) || isAdmin(); // Admins can read/update any data
38+
allow list: if isAdmin(); // Admins can list all data documents
2439
allow delete: if false;
2540

2641
match /private/private_data {
2742
allow create: if isAuthenticated() &&
2843
request.resource.data.firebaseAnonAuthID == request.auth.uid;
29-
allow read, update: if isOwner(resource);
44+
allow read, update: if isOwner(resource) || isAdmin(); // Admins can access private data
45+
allow delete: if false;
46+
}
47+
48+
match /notes/annotations {
49+
allow create, read, update: if isAdmin(); // Only admins can manage notes
3050
allow delete: if false;
3151
}
3252
}
@@ -35,17 +55,25 @@ service cloud.firestore {
3555
// Rules for real collection (identical to testing)
3656
match /real/{studyID} {
3757
allow create: if isAuthenticated() && !exists(/databases/$(database)/documents/real/$(studyID));
38-
allow read: if isAuthenticated(); // Add this line
58+
allow read: if isAuthenticated();
59+
allow list: if isAdmin(); // Admins can list all studies
60+
3961
match /data/{partID} {
4062
allow create: if isAuthenticated() &&
4163
request.resource.data.firebaseAnonAuthID == request.auth.uid;
42-
allow read, update: if isOwner(resource);
64+
allow read, update: if isOwner(resource) || isAdmin(); // Admins can read/update any data
65+
allow list: if isAdmin(); // Admins can list all data documents
4366
allow delete: if false;
4467

4568
match /private/private_data {
4669
allow create: if isAuthenticated() &&
4770
request.resource.data.firebaseAnonAuthID == request.auth.uid;
48-
allow read, update: if isOwner(resource);
71+
allow read, update: if isOwner(resource) || isAdmin(); // Admins can access private data
72+
allow delete: if false;
73+
}
74+
75+
match /notes/annotations {
76+
allow create, read, update: if isAdmin(); // Only admins can manage notes
4977
allow delete: if false;
5078
}
5179
}

package-lock.json

Lines changed: 38 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"@google-cloud/storage": "^7.15.0",
5353
"@svgdotjs/svg.js": "^3.2.4",
5454
"@tailwindcss/vite": "^4.1.10",
55+
"@tanstack/vue-table": "^8.21.3",
5556
"@vitejs/plugin-vue": "^5.2.1",
5657
"@vueuse/core": "^12.8.2",
5758
"axios": "^1.7.9",
@@ -86,7 +87,8 @@
8687
"vite": "^6.1.0",
8788
"vue": "^3.5.13",
8889
"vue-gtag": "^2.0.1",
89-
"vue-router": "^4.5.0"
90+
"vue-router": "^4.5.0",
91+
"vue-sonner": "^2.0.1"
9092
},
9193
"devDependencies": {
9294
"@iconify/json": "^2.2.351",

src/dev/dashboard/AdminSetup.vue

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<script setup>
2+
import { ref } from 'vue'
3+
import { getAuth, signInAnonymously } from 'firebase/auth'
4+
5+
const currentUser = ref(null)
6+
const auth = getAuth()
7+
8+
async function authenticate() {
9+
try {
10+
const userCredential = await signInAnonymously(auth)
11+
currentUser.value = userCredential.user
12+
} catch (error) {
13+
console.error('Authentication failed:', error)
14+
}
15+
}
16+
17+
async function copyToClipboard(text) {
18+
try {
19+
await navigator.clipboard.writeText(text)
20+
alert('User ID copied to clipboard!')
21+
} catch (err) {
22+
console.error('Failed to copy:', err)
23+
// Fallback for older browsers
24+
const textArea = document.createElement('textarea')
25+
textArea.value = text
26+
document.body.appendChild(textArea)
27+
textArea.select()
28+
document.execCommand('copy')
29+
document.body.removeChild(textArea)
30+
alert('User ID copied to clipboard!')
31+
}
32+
}
33+
</script>
34+
35+
<template>
36+
<div class="admin-setup p-6 bg-yellow-50 border border-yellow-200 rounded-lg">
37+
<h3 class="text-lg font-semibold text-yellow-800 mb-4">Admin Setup</h3>
38+
39+
<div v-if="!currentUser" class="mb-4">
40+
<button @click="authenticate" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
41+
Authenticate to Get Admin ID
42+
</button>
43+
</div>
44+
45+
<div v-else class="space-y-4">
46+
<div class="bg-white p-4 rounded border">
47+
<h4 class="font-medium mb-2">Your User ID (for admin setup):</h4>
48+
<div class="bg-gray-100 p-2 rounded font-mono text-sm break-all">
49+
{{ currentUser.uid }}
50+
</div>
51+
<button
52+
@click="copyToClipboard(currentUser.uid)"
53+
class="mt-2 px-3 py-1 bg-green-500 text-white text-sm rounded hover:bg-green-600"
54+
>
55+
Copy ID
56+
</button>
57+
</div>
58+
59+
<div class="bg-blue-50 p-4 rounded border border-blue-200">
60+
<h4 class="font-medium text-blue-800 mb-2">Next Steps:</h4>
61+
<ol class="text-sm text-blue-700 space-y-1 list-decimal ml-4">
62+
<li>Copy your User ID above</li>
63+
<li>Add it to the Firebase rules as an admin ID</li>
64+
<li>Deploy the updated rules</li>
65+
<li>Refresh this page to get admin access</li>
66+
</ol>
67+
</div>
68+
69+
<div class="bg-green-50 p-4 rounded border border-green-200">
70+
<h4 class="font-medium text-green-800 mb-2">For Firebase Rules:</h4>
71+
<p class="text-sm text-green-700 mb-2">Replace the admin check in firebase/firebase.rules with:</p>
72+
<pre class="bg-green-100 p-2 rounded text-xs overflow-x-auto"><code>function isAdmin() {
73+
return isAuthenticated() && (
74+
request.auth.uid == "{{ currentUser.uid }}" ||
75+
// Add more admin UIDs here
76+
request.auth.uid == "another-admin-uid-here"
77+
);
78+
}</code></pre>
79+
</div>
80+
</div>
81+
</div>
82+
</template>
83+
84+
<style scoped>
85+
code {
86+
white-space: pre-wrap;
87+
}
88+
</style>

0 commit comments

Comments
 (0)