-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add access dashboard for role management
- Loading branch information
Showing
7 changed files
with
306 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
<script lang="ts"> | ||
import Modal from '$lib/util/modal.svelte'; | ||
import { API_URL } from '../../constants'; | ||
import type { Role } from '$lib/types'; | ||
import SmartInput from '../util/smart-input.svelte'; | ||
const initialRole = { | ||
email: '', | ||
role: '' | ||
}; | ||
let roleData: Role = initialRole; | ||
export let show: boolean; | ||
export let loadRoles: Function; | ||
let errors: { [K in keyof Role]?: string } = {}; | ||
let apiError: string | null = null; | ||
const validationFails = () => { | ||
let email = roleData.email.trim(); | ||
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | ||
if (!regex.test(email)) { | ||
errors.email = 'This is not a valid email'; | ||
return true; | ||
} | ||
return false; | ||
}; | ||
const createEvent = async () => { | ||
if (validationFails()) return; | ||
const response = await fetch(`${$API_URL}/roles`, { | ||
method: 'POST', | ||
credentials: 'include', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: JSON.stringify(roleData) | ||
}); | ||
if (response.ok) { | ||
roleData = initialRole; | ||
show = false; | ||
loadRoles(); | ||
} else { | ||
apiError = response.status + ' ' + response.statusText; | ||
} | ||
}; | ||
</script> | ||
|
||
<Modal bind:show> | ||
<div class="flex flex-col p-2"> | ||
<h1 class="text-xl mb-3 font-serif">Create Role</h1> | ||
|
||
<form class="flex flex-col gap-5"> | ||
<SmartInput label="User Email" sublabel="Ensure correctness" bind:error={errors.email}> | ||
<input type="text" class="bg-gray-700 rounded-sm p-1" bind:value={roleData.email} /> | ||
</SmartInput> | ||
<SmartInput label="Role" bind:error={errors.role}> | ||
<select | ||
class="duration-300 p-3 bg-white | ||
text-center | ||
bg-opacity-10 hover:bg-opacity-20" | ||
bind:value={roleData.role} | ||
> | ||
{#each ['Admin', 'Corporate'] as role} | ||
<option value={role} class="bg-rp-blue"> | ||
{role} | ||
</option> | ||
{/each} | ||
</select> | ||
</SmartInput> | ||
|
||
<div class="w-32 md:w-96 flex flex-col"> | ||
<button | ||
class="p-2 rounded-md bg-green-600 hover:bg-green-500 duration-300" | ||
on:click={createEvent}>Go</button | ||
> | ||
<div class="text-red-500 ml-1 text-sm {apiError ? '' : 'hidden'}"> | ||
{apiError} | ||
</div> | ||
</div> | ||
</form> | ||
</div> | ||
</Modal> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<script lang="ts"> | ||
import Icon from '@iconify/svelte'; | ||
import GlassContainer from '../../components/glass-container.svelte'; | ||
</script> | ||
|
||
<main class="w-full md:w-2/3 lg:w-1/2 mx-auto"> | ||
<GlassContainer> | ||
<div class="flex flex-col gap-5"> | ||
<h1 class="text-2xl text-white font-serif">Staff Dashboard</h1> | ||
|
||
<div class="flex flex-row items-center text-gray-200 gap-5 flex-wrap"> | ||
<a | ||
href="/dash/events" | ||
class="flex flex-row gap-2 justify-center items-center text-lg md:text-xl w-60 p-2 h-20 bg-white bg-opacity-20 hover:bg-opacity-30 rounded-md duration-200" | ||
> | ||
<Icon icon="ic:outline-event" class="text-xl md:text-3xl" /> | ||
<p>Events Dashboard</p> | ||
</a> | ||
<a | ||
href="/dash/access" | ||
class="flex flex-row gap-2 justify-center items-center text-lg md:text-xl w-60 p-2 h-20 bg-white bg-opacity-20 hover:bg-opacity-30 rounded-md duration-200" | ||
> | ||
<Icon icon="eos-icons:role-binding" class="text-xl md:text-3xl" /> | ||
<p>Access Dashboard</p> | ||
</a> | ||
</div> | ||
</div> | ||
</GlassContainer> | ||
</main> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { error } from '@sveltejs/kit'; | ||
import { API_URL } from '../../constants'; | ||
import type { PageLoad } from './$types'; | ||
import { get } from 'svelte/store'; | ||
|
||
export const load: PageLoad<void> = async ({ fetch }) => { | ||
// TODO: Change to some access specific endpoint | ||
const url = get(API_URL); | ||
const check = await fetch(`${url}/auth/access/admin`, { | ||
credentials: 'include' | ||
}); | ||
|
||
// Not authorized or signed in: | ||
// They don't need to know this is an actual route. | ||
if (check.status == 403 || check.status == 401) { | ||
throw error(404, { message: 'Not found' }); | ||
} | ||
}; | ||
|
||
export const ssr = false; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
<script lang="ts"> | ||
import { onMount } from 'svelte'; | ||
import { API_URL } from '../../../constants'; | ||
import type { Role } from '$lib/types'; | ||
import Icon from '@iconify/svelte'; | ||
import RoleModal from '../../../lib/components/role-modal.svelte'; | ||
let showModal = false; | ||
let searchTerm = ''; | ||
const filter = (role: Role): boolean => { | ||
const search = searchTerm.toLowerCase(); | ||
return role.email.toLowerCase().includes(search) || role.role.toLowerCase().includes(search); | ||
}; | ||
let allRoles: Role[] = []; | ||
let roles: Role[] = []; | ||
let errorMessage: string; | ||
$: if (searchTerm.length > 0) { | ||
roles = allRoles.filter(filter); | ||
} else { | ||
roles = allRoles; | ||
} | ||
const loadRoles = async () => { | ||
const response = await fetch(`${$API_URL}/roles`, { | ||
credentials: 'include', | ||
cache: 'no-cache' | ||
}); | ||
const data = await response.json(); | ||
if (response.ok) { | ||
allRoles = data; | ||
} else { | ||
errorMessage = data.message; | ||
console.error(response.statusText); | ||
console.error(data.message); | ||
} | ||
}; | ||
const onDeleteEventClick = async (role: Role) => { | ||
const response = await fetch(`${$API_URL}/roles/${role._id}`, { | ||
method: 'DELETE', | ||
credentials: 'include' | ||
}); | ||
if (response.ok) { | ||
loadRoles(); | ||
} else { | ||
errorMessage = response.status + ' ' + response.statusText; | ||
} | ||
}; | ||
onMount(loadRoles); | ||
</script> | ||
|
||
<RoleModal bind:show={showModal} {loadRoles} /> | ||
<div class="h-full text-white flex justify-between flex-col my-20"> | ||
<div | ||
class="bg-black bg-opacity-10 rounded-lg p-2 md:p-7 mx-auto w-full md:w-10/12 lg:w-11/12 text-sm md:text-base" | ||
> | ||
<div class="flex flex-row justify-between items-baseline"> | ||
<h1 class="text-xl mb-3 p-2 font-serif">Access Dashboard</h1> | ||
<span class="flex flex-row"> | ||
<button | ||
class="py-1 px-3 bg-pink-600 bg-opacity-80 hover:bg-opacity-100 duration-300 rounded-md h-min" | ||
on:click={() => { | ||
showModal = true; | ||
}}>New</button | ||
> | ||
</span> | ||
</div> | ||
|
||
<div class="flex flex-row gap-2 mb-3 items-center w-full md:w-1/2"> | ||
<label for="search" class="text-3xl"> <Icon icon="material-symbols:search" /> </label> | ||
<input | ||
type="text" | ||
name="search" | ||
id="search" | ||
bind:value={searchTerm} | ||
placeholder="Email or Role" | ||
class="bg-transparent border border-gray-400 rounded-md p-1" | ||
/> | ||
</div> | ||
|
||
<div class="p-3 bg-white bg-opacity-10 rounded-md overflow-x-auto"> | ||
<table class="w-full table-auto border-spacing-10 border-collapse border-hidden"> | ||
<thead class="text-left tracking-wider"> | ||
<th>Email</th> | ||
<th>Role</th> | ||
<th>Revoke</th> | ||
</thead> | ||
<tbody> | ||
{#each roles as role} | ||
<tr | ||
class=" {role.role === 'Admin' && | ||
'text-red-200 font-bold'} bg-opacity-0 hover:bg-opacity-10 duration-300" | ||
> | ||
<td title={role.email}> | ||
{role.email} | ||
</td> | ||
<td title={role.role}> | ||
{role.role} | ||
</td> | ||
<td title="Revoke Role"> | ||
<button aria-label="Revoke Access" on:click={() => onDeleteEventClick(role)}> | ||
<Icon | ||
icon="ph:x-fill" | ||
class="text-white hover:text-red-600 duration-200 text-2xl" | ||
/> | ||
</button> | ||
</td> | ||
</tr> | ||
{/each} | ||
</tbody> | ||
</table> | ||
</div> | ||
<div class="p-1 flex w-full flex-row justify-between text-sm text-gray-300"> | ||
<div class="text-black {errorMessage ? 'visible' : 'invisible'}"> | ||
{errorMessage} | ||
</div> | ||
<div> | ||
Showing {roles.length} rows | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<style> | ||
th, | ||
td { | ||
padding: 0.5rem; | ||
padding-left: 0.75rem; | ||
padding-right: 0.75rem; | ||
border-width: 1px; | ||
--tw-border-opacity: 0.3; | ||
border-color: rgb(209 213 219 / var(--tw-border-opacity)); | ||
text-overflow: ellipsis; | ||
overflow: hidden; | ||
max-height: 8rem; | ||
white-space: nowrap; | ||
max-width: 280px; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { error } from '@sveltejs/kit'; | ||
import { API_URL } from '../../../constants'; | ||
import type { PageLoad } from './$types'; | ||
import { get } from 'svelte/store'; | ||
|
||
export const load: PageLoad<void> = async ({ fetch }) => { | ||
const url = get(API_URL); | ||
const check = await fetch(`${url}/auth/access/admin`, { | ||
credentials: 'include' | ||
}); | ||
|
||
// Not authorized or signed in: | ||
// They don't need to know this is an actual route. | ||
if (check.status == 403 || check.status == 401) { | ||
throw error(404, { message: 'Not found' }); | ||
} | ||
}; | ||
|
||
export const ssr = false; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters