diff --git a/src/lib/components/app-sidebar.svelte b/src/lib/components/app-sidebar.svelte index 3eba3f1..3f2a832 100644 --- a/src/lib/components/app-sidebar.svelte +++ b/src/lib/components/app-sidebar.svelte @@ -11,20 +11,17 @@ DiamondIcon, BugIcon, CupSodaIcon, - Shield, FileSpreadsheet } from '@lucide/svelte/icons'; import TaobinLogo from '$lib/assets/logo.svelte'; import { goto } from '$app/navigation'; import Button from '$lib/components/ui/button/button.svelte'; + import { get } from 'svelte/store'; import { sidebarStore } from '$lib/core/stores/sidebar'; - import { auth } from '$lib/core/stores/auth'; - import { isUserAdmin } from '$lib/core/admin/adminService'; import { referenceFromPage } from '$lib/core/stores/recipeStore'; let sideBar: HTMLElement | null = $state(null); let isSideBarOpen: boolean = $state(true); - let isAdmin: boolean = $state(false); const data = { navMain: [ @@ -84,36 +81,10 @@ } ] } + // more to add here ] }; - const adminNav = { - title: 'Admin', - items: [ - { - title: 'Permissions', - url: '/admin/users', - icon: Shield - } - ] - }; - - $effect(() => { - const currentUser = $auth; - if (currentUser) { - isUserAdmin(currentUser.uid) - .then((result) => { - isAdmin = result; - }) - .catch((e) => { - console.error('Error checking admin status:', e); - isAdmin = false; - }); - } else { - isAdmin = false; - } - }); - function onClickLogoIcon() { goto('/departments'); } @@ -175,40 +146,6 @@ {/each} - - {#if isAdmin} - - {adminNav.title} - - - {#each adminNav.items as sub} - - - {#snippet child({ props })} - { - if (nav.title === 'Sheet') { - e.preventDefault(); - referenceFromPage.set('sheet'); - goto(sub.url); - } - }} - > - {#if sub.icon} - - {/if} - {sub.title} - - {/snippet} - - - {/each} - - - - {/if} diff --git a/src/lib/core/admin/adminService.ts b/src/lib/core/admin/adminService.ts deleted file mode 100644 index 5be4a2d..0000000 --- a/src/lib/core/admin/adminService.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { doc, getDoc, updateDoc } from 'firebase/firestore'; -import { db } from '../client/firebase'; -import type { - AdminUser, - DocumentPermissions, - RolesConfig, - ToolsPermissions, - UserRole, - WhitelistConfig -} from './adminTypes'; -import { AVAILABLE_REGIONS, AVAILABLE_TOOLS } from './adminTypes'; - -/** - * Get all users from Firestore - */ -export async function getAllUsers(): Promise { - const docRef = doc(db, 'users', 'data'); - const snapshot = await getDoc(docRef); - - if (!snapshot.exists()) { - return []; - } - - const userData = snapshot.data(); - const users: AdminUser[] = []; - - for (const uid of Object.keys(userData)) { - const user = userData[uid]; - users.push({ - uid, - email: user.email || '', - displayName: user.displayName || '', - photoURL: user.photoURL || '', - role: user.role || 'guest', - permissions: user.permissions || ['no_permission'], - lastLoginAt: user.lastLoginAt, - createdAt: user.createdAt - }); - } - - return users; -} - -/** - * Get a single user by UID - */ -export async function getUserByUid(uid: string): Promise { - const docRef = doc(db, 'users', 'data'); - const snapshot = await getDoc(docRef); - - if (!snapshot.exists()) { - return null; - } - - const userData = snapshot.data(); - if (!userData[uid]) { - return null; - } - - const user = userData[uid]; - return { - uid, - email: user.email || '', - displayName: user.displayName || '', - photoURL: user.photoURL || '', - role: user.role || 'guest', - permissions: user.permissions || ['no_permission'], - lastLoginAt: user.lastLoginAt, - createdAt: user.createdAt - }; -} - -/** - * Update user role - */ -export async function updateUserRole(uid: string, role: UserRole): Promise { - const docRef = doc(db, 'users', 'data'); - const updateData: Record = {}; - updateData[`${uid}.role`] = role; - - await updateDoc(docRef, updateData); -} - -/** - * Update user permissions - */ -export async function updateUserPermissions(uid: string, permissions: string[]): Promise { - const docRef = doc(db, 'users', 'data'); - const updateData: Record = {}; - updateData[`${uid}.permissions`] = permissions; - - await updateDoc(docRef, updateData); -} - -/** - * Update user role and permissions together - */ -export async function updateUser( - uid: string, - role: UserRole, - permissions: string[] -): Promise { - const docRef = doc(db, 'users', 'data'); - const updateData: Record = {}; - updateData[`${uid}.role`] = role; - updateData[`${uid}.permissions`] = permissions; - - await updateDoc(docRef, updateData); -} - -/** - * Get role definitions from Firestore - */ -export async function getRoleDefinitions(): Promise { - const docRef = doc(db, 'roles', 'v1'); - const snapshot = await getDoc(docRef); - - if (!snapshot.exists()) { - return null; - } - - const data = snapshot.data(); - return { - definitions: { - guest: data.definitions?.guest || { permissions: ['no_permission'] }, - viewer: data.definitions?.viewer || { permissions: [] }, - admin: data.definitions?.admin || { permissions: [] } - } - }; -} - -/** - * Update role definition - */ -export async function updateRoleDefinition(role: UserRole, permissions: string[]): Promise { - const docRef = doc(db, 'roles', 'v1'); - const updateData: Record = {}; - updateData[`definitions.${role}.permissions`] = permissions; - - await updateDoc(docRef, updateData); -} - -/** - * Get document permissions (regions) - */ -export async function getDocumentPermissions(): Promise { - const docRef = doc(db, 'permissions', 'document'); - const snapshot = await getDoc(docRef); - - if (!snapshot.exists()) { - return null; - } - - const data = snapshot.data(); - return { - read: data.read || [], - write: data.write || [] - }; -} - -/** - * Get tools permissions - */ -export async function getToolsPermissions(): Promise { - const docRef = doc(db, 'permissions', 'tools'); - const snapshot = await getDoc(docRef); - - if (!snapshot.exists()) { - return null; - } - - const data = snapshot.data(); - return { - core: data.core || [] - }; -} - -/** - * Get allowed domains (whitelist) - */ -export async function getAllowedDomains(): Promise { - const docRef = doc(db, 'whitelist', 'allowedDomains'); - const snapshot = await getDoc(docRef); - - if (!snapshot.exists()) { - return []; - } - - const data = snapshot.data() as WhitelistConfig; - return data.account_email || []; -} - -/** - * Update allowed domains (whitelist) - */ -export async function updateAllowedDomains(domains: string[]): Promise { - const docRef = doc(db, 'whitelist', 'allowedDomains'); - await updateDoc(docRef, { account_email: domains }); -} - -/** - * Build all available permissions based on regions and tools - * Uses AVAILABLE_REGIONS and AVAILABLE_TOOLS as fallback if Firestore data is empty - * no_permission is expected to be in Firebase data already - */ -export async function buildAvailablePermissions(): Promise { - const permissions: string[] = []; - - const docPerms = await getDocumentPermissions(); - - // Document Read permissions (no_permission should be in Firebase read array) - const readRegions = docPerms?.read?.length ? docPerms.read : [...AVAILABLE_REGIONS]; - for (const region of readRegions) { - permissions.push(`document.read.${region}`); - } - - // Document Write permissions (no_permission should be in Firebase write array) - const writeRegions = docPerms?.write?.length ? docPerms.write : [...AVAILABLE_REGIONS]; - for (const region of writeRegions) { - permissions.push(`document.write.${region}`); - } - - // Tools permissions - const toolPerms = await getToolsPermissions(); - const tools = toolPerms?.core?.length ? toolPerms.core : [...AVAILABLE_TOOLS]; - for (const tool of tools) { - permissions.push(`tools.core.${tool}`); - } - - return permissions; -} - -/** - * Check if current user is admin - */ -export async function isUserAdmin(uid: string): Promise { - const user = await getUserByUid(uid); - return user?.role === 'admin'; -} diff --git a/src/lib/core/admin/adminStore.ts b/src/lib/core/admin/adminStore.ts deleted file mode 100644 index 8e0c03c..0000000 --- a/src/lib/core/admin/adminStore.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { writable } from 'svelte/store'; -import type { AdminUser, RolesConfig } from './adminTypes'; - -// Users list store -export const adminUsers = writable([]); - -// Roles configuration store -export const rolesConfig = writable(null); - -// Available permissions store -export const availablePermissions = writable([]); - -// Loading states -export const adminLoading = writable(false); -export const usersLoading = writable(false); -export const rolesLoading = writable(false); - -// Error state -export const adminError = writable(null); - -// Selected user for editing -export const selectedUser = writable(null); - -// Edit sheet open state -export const editSheetOpen = writable(false); diff --git a/src/lib/core/admin/adminTypes.ts b/src/lib/core/admin/adminTypes.ts deleted file mode 100644 index cb8b7df..0000000 --- a/src/lib/core/admin/adminTypes.ts +++ /dev/null @@ -1,72 +0,0 @@ -export interface AdminUser { - uid: string; - email: string; - displayName: string; - photoURL: string; - role: 'guest' | 'viewer' | 'admin'; - permissions: string[]; - lastLoginAt?: string; - createdAt?: string; -} - -export interface RoleDefinition { - permissions: string[]; -} - -export interface RolesConfig { - definitions: { - guest: RoleDefinition; - viewer: RoleDefinition; - admin: RoleDefinition; - }; -} - -export interface DocumentPermissions { - read: string[]; - write: string[]; -} - -export interface ToolsPermissions { - core: string[]; -} - -export interface WhitelistConfig { - account_email: string[]; -} - -export type UserRole = 'guest' | 'viewer' | 'admin'; - -export const AVAILABLE_REGIONS = [ - 'tha', - 'mys', - 'aus', - 'sgp', - 'uae_dubai', - 'hkg', - 'gbr', - 'rou', - 'lva', - 'est', - 'ltu' -] as const; - -export const REGION_LABELS: Record = { - tha: 'Thailand', - mys: 'Malaysia', - aus: 'Australia', - sgp: 'Singapore', - uae_dubai: 'UAE Dubai', - hkg: 'Hong Kong', - gbr: 'United Kingdom', - rou: 'Romania', - lva: 'Latvia', - est: 'Estonia', - ltu: 'Lithuania' -}; - -export const AVAILABLE_TOOLS = ['connectMachine', 'allowAdbWebToUsb'] as const; - -export const TOOL_LABELS: Record = { - connectMachine: 'Connect Machine', - allowAdbWebToUsb: 'Allow ADB Web to USB' -}; diff --git a/src/lib/core/stores/auth.ts b/src/lib/core/stores/auth.ts index bd97e46..3f1c0ec 100644 --- a/src/lib/core/stores/auth.ts +++ b/src/lib/core/stores/auth.ts @@ -6,6 +6,4 @@ import { writable } from "svelte/store"; // email: string, // }; -export const auth = writable(null); - -export const authInitialized = writable(false); \ No newline at end of file +export const auth = writable(null); \ No newline at end of file diff --git a/src/routes/(authed)/admin/+layout.server.ts b/src/routes/(authed)/admin/+layout.server.ts deleted file mode 100644 index 224d765..0000000 --- a/src/routes/(authed)/admin/+layout.server.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { redirect, type Cookies } from '@sveltejs/kit'; - -export async function load({ cookies, url }: { cookies: Cookies; url: URL }) { - // Check if user is logged in - if (!cookies.get('logged_in')) { - redirect(303, `/login?redirectTo=${url.pathname}`); - } - - // Admin permission check will be done client-side - // because we need to access Firebase Firestore -} diff --git a/src/routes/(authed)/admin/+layout.svelte b/src/routes/(authed)/admin/+layout.svelte deleted file mode 100644 index 3730cf4..0000000 --- a/src/routes/(authed)/admin/+layout.svelte +++ /dev/null @@ -1,103 +0,0 @@ - - -{#if loading} -
- -
-{:else if isAdmin} -
-
-

Admin Panel

-

Manage users, roles, and system settings

-
- - - - - - Users - - - - Roles - - - - Settings - - - -
- {@render children()} -
-
-
-{:else} -
-

Access denied. Admin privileges required.

-
-{/if} diff --git a/src/routes/(authed)/admin/+page.svelte b/src/routes/(authed)/admin/+page.svelte deleted file mode 100644 index 2de555e..0000000 --- a/src/routes/(authed)/admin/+page.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - -
-

Redirecting...

-
diff --git a/src/routes/(authed)/admin/roles/+page.svelte b/src/routes/(authed)/admin/roles/+page.svelte deleted file mode 100644 index 28be91e..0000000 --- a/src/routes/(authed)/admin/roles/+page.svelte +++ /dev/null @@ -1,72 +0,0 @@ - - -
-
-
-

Roles

-

View and manage role definitions

-
- -
- - {#if $rolesLoading} -
- -
- {:else if $rolesConfig} -
- {#each roleOrder as role} - - {/each} -
- {:else} -
-

No role definitions found.

-
- {/if} -
diff --git a/src/routes/(authed)/admin/roles/role-card.svelte b/src/routes/(authed)/admin/roles/role-card.svelte deleted file mode 100644 index 3251b4f..0000000 --- a/src/routes/(authed)/admin/roles/role-card.svelte +++ /dev/null @@ -1,195 +0,0 @@ - - - - -
-
-
- -
- {role} -
- -
- {description} -
- -
-

- {displayPermissions.length} permission(s) -

-
- {#if displayPermissions.length === 0} - No permissions - {:else} - {#each displayPermissions.slice(0, 5) as perm} - {getPermissionLabel(perm)} - {/each} - {#if displayPermissions.length > 5} - +{displayPermissions.length - 5} more - {/if} - {/if} -
-
-
-
- - - - - Edit {role} Role - Select permissions for this role - - -
- {#each Object.entries(groupedAvailable) as [group, perms]} - {#if perms.length > 0} -
-

{group}

-
- {#each perms as perm} - - {/each} -
-
- {/if} - {/each} -
- - - - - -
-
diff --git a/src/routes/(authed)/admin/settings/+page.svelte b/src/routes/(authed)/admin/settings/+page.svelte deleted file mode 100644 index 62c1f91..0000000 --- a/src/routes/(authed)/admin/settings/+page.svelte +++ /dev/null @@ -1,142 +0,0 @@ - - -
-
-
-

Settings

-

View system configuration (read-only)

-
- -
- - {#if loading} -
- -
- {:else} -
- - - -
- - Document Regions -
- Available regions for document access -
- - {#if documentPerms} -
-

Read Access

-
- {#each documentPerms.read as region} - {REGION_LABELS[region] || region} - {/each} -
-
-
-

Write Access

-
- {#each documentPerms.write as region} - {REGION_LABELS[region] || region} - {/each} -
-
- {:else} -

No document permissions configured

- {/if} -
-
- - - - -
- - Tools Permissions -
- Available tool permissions -
- - {#if toolsPerms} -
-

Core Tools

-
- {#each toolsPerms.core as tool} - {TOOL_LABELS[tool] || tool} - {/each} -
-
- {:else} -

No tool permissions configured

- {/if} -
-
- - - - -
- - Email Domain Whitelist -
- Allowed email domains for login -
- - {#if allowedDomains.length > 0} -
- {#each allowedDomains as domain} - @{domain} - {/each} -
- {:else} -

No domains configured

- {/if} -
-
-
- {/if} -
diff --git a/src/routes/(authed)/admin/users/+page.svelte b/src/routes/(authed)/admin/users/+page.svelte deleted file mode 100644 index a551aa7..0000000 --- a/src/routes/(authed)/admin/users/+page.svelte +++ /dev/null @@ -1,56 +0,0 @@ - - -
-
-
-

Users

-

Manage user roles and permissions

-
- -
- - {#if $usersLoading} -
- -
- {:else if $adminError} -
-

{$adminError}

-
- {:else} - - {/if} -
- - diff --git a/src/routes/(authed)/admin/users/columns.ts b/src/routes/(authed)/admin/users/columns.ts deleted file mode 100644 index a8388bc..0000000 --- a/src/routes/(authed)/admin/users/columns.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { renderComponent } from '$lib/components/ui/data-table'; -import type { ColumnDef } from '@tanstack/table-core'; -import type { AdminUser } from '$lib/core/admin/adminTypes'; -import DataTableRoleBadge from './data-table-role-badge.svelte'; -import DataTableActions from './data-table-actions.svelte'; -import DataTableAvatar from './data-table-avatar.svelte'; - -export const columns: ColumnDef[] = [ - { - id: 'avatar', - header: '', - cell: ({ row }) => { - return renderComponent(DataTableAvatar, { - photoURL: row.original.photoURL, - displayName: row.original.displayName - }); - }, - enableGlobalFilter: false - }, - { - accessorKey: 'displayName', - header: 'Name', - enableGlobalFilter: true, - filterFn: 'includesString' - }, - { - accessorKey: 'email', - header: 'Email', - enableGlobalFilter: true, - filterFn: 'includesString' - }, - { - accessorKey: 'role', - header: 'Role', - cell: ({ row }) => { - return renderComponent(DataTableRoleBadge, { role: row.original.role }); - }, - enableGlobalFilter: true, - filterFn: 'includesString' - }, - { - id: 'permissionCount', - header: 'Permissions', - cell: ({ row }) => { - const count = row.original.permissions?.length || 0; - return `${count} permissions`; - }, - enableGlobalFilter: false - }, - { - id: 'actions', - header: '', - cell: ({ row }) => { - return renderComponent(DataTableActions, { user: row.original }); - } - } -]; diff --git a/src/routes/(authed)/admin/users/data-table-actions.svelte b/src/routes/(authed)/admin/users/data-table-actions.svelte deleted file mode 100644 index 866f207..0000000 --- a/src/routes/(authed)/admin/users/data-table-actions.svelte +++ /dev/null @@ -1,18 +0,0 @@ - - - diff --git a/src/routes/(authed)/admin/users/data-table-avatar.svelte b/src/routes/(authed)/admin/users/data-table-avatar.svelte deleted file mode 100644 index a617cb7..0000000 --- a/src/routes/(authed)/admin/users/data-table-avatar.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - -
- {#if photoURL} - {displayName} - {:else} - - {/if} -
diff --git a/src/routes/(authed)/admin/users/data-table-role-badge.svelte b/src/routes/(authed)/admin/users/data-table-role-badge.svelte deleted file mode 100644 index 145eb6a..0000000 --- a/src/routes/(authed)/admin/users/data-table-role-badge.svelte +++ /dev/null @@ -1,22 +0,0 @@ - - - - {roleLabels[role] || role} - diff --git a/src/routes/(authed)/admin/users/data-table.svelte b/src/routes/(authed)/admin/users/data-table.svelte deleted file mode 100644 index 1ee4702..0000000 --- a/src/routes/(authed)/admin/users/data-table.svelte +++ /dev/null @@ -1,160 +0,0 @@ - - -
-
- - { - table.setGlobalFilter(e.currentTarget.value); - }} - oninput={(e) => { - table.setGlobalFilter(e.currentTarget.value); - }} - /> -
- -
- - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {#if !header.isPlaceholder} - - {/if} - - {/each} - - {/each} - - - {#each table.getRowModel().rows as row (row.id)} - - {#each row.getVisibleCells() as cell (cell.id)} - - - - {/each} - - {:else} - - No users found. - - {/each} - - -
- - {table.getFilteredRowModel().rows.length} user(s) total - -
- - -
-
-
-
diff --git a/src/routes/(authed)/admin/users/user-edit-sheet.svelte b/src/routes/(authed)/admin/users/user-edit-sheet.svelte deleted file mode 100644 index 35251bd..0000000 --- a/src/routes/(authed)/admin/users/user-edit-sheet.svelte +++ /dev/null @@ -1,309 +0,0 @@ - - - - - - Edit User - Update user role and permissions - - - {#if $selectedUser} -
- -
-
- {#if $selectedUser.photoURL} - {$selectedUser.displayName} - {:else} - - {/if} -
-
-

{$selectedUser.displayName}

-

{$selectedUser.email}

-
-
- - -
- - { - if (v) selectedRole = v as UserRole; - }} - > - - {roles.find((r) => r.value === selectedRole)?.label || 'Select role'} - - - {#each roles as role} - {role.label} - {/each} - - -
- - -
- - - {#if loadingPermissions} -
- -
- {:else} -
- {#each Object.entries(groupedPermissions()) as [group, perms]} - {#if perms.length > 0} - {@const noPermSelected = isNoPermissionSelected(group)} - {@const hasNoPermOption = group === 'Document Read' || group === 'Document Write'} -
-
-

{group}

-
- - -
-
- -
- {#each perms as perm} - {@const isNoPerm = isNoPermissionPerm(perm)} - {@const isDisabled = !isNoPerm && noPermSelected} - - {/each} -
-
- {/if} - {/each} -
- {/if} -
-
- {/if} - - - - - -
-
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 8e13784..9694127 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -9,7 +9,7 @@ import * as NavigationMenu from '$lib/components/ui/navigation-menu/index.js'; import { onMount } from 'svelte'; import { onAuthStateChanged } from 'firebase/auth'; - import { auth as authStore, authInitialized } from '$lib/core/stores/auth'; + import { auth as authStore } from '$lib/core/stores/auth'; import { auth } from '$lib/core/client/firebase'; import { goto } from '$app/navigation'; import { getUserPermission } from '$lib/core/auth/userPermissions'; @@ -34,7 +34,6 @@ onAuthStateChanged(auth, async function (s) { authStore.set(s); - authInitialized.set(true); if (s) { if (browser && 'cookieStore' in window) await cookieStore.set('logged_in', 'true'); else {