refactor to shadcn components, dynamically load extension frontend

add jetstream permissions, add dynamic inertia module loading, add shadcn components, change modals and dropdowns to shadcn dismissable layer,
This commit is contained in:
Gregor Vostrak
2025-04-13 23:06:58 +02:00
parent 4605aa75ff
commit 132b6cbe8f
204 changed files with 5458 additions and 1275 deletions

View File

@@ -49,7 +49,7 @@ function getNameForKey(key: BillableKey | undefined) {
<span>
{{ getNameForKey(model) }}
</span>
<ChevronDownIcon class="text-muted w-5"></ChevronDownIcon>
<ChevronDownIcon class="text-text-secondary w-5"></ChevronDownIcon>
</Badge>
</template>
</SelectDropdown>

View File

@@ -64,12 +64,12 @@ const currentValue = computed(() => {
<Badge
tag="button"
class="flex w-full text-base text-left space-x-3 px-3 text-text-secondary bg-input-background font-normal cursor py-1.5">
<UserIcon class="relative z-10 w-4 text-muted"></UserIcon>
<UserIcon class="relative z-10 w-4 text-text-secondary"></UserIcon>
<div v-if="currentValue" class="flex-1 truncate">
{{ currentValue }}
</div>
<div v-else class="flex-1">Select a member...</div>
<ChevronDownIcon class="w-4 text-muted"></ChevronDownIcon>
<ChevronDownIcon class="w-4 text-text-secondary"></ChevronDownIcon>
</Badge>
</template>
</SelectDropdown>

View File

@@ -194,7 +194,7 @@ useFocus(clientNameInput, { initialValue: true });
</div>
<!-- Role Description -->
<div class="mt-2 text-xs text-muted text-start">
<div class="mt-2 text-xs text-text-secondary text-start">
{{ role.description }}
</div>
</div>

View File

@@ -76,7 +76,7 @@ async function submit() {
<div class="py-5 flex flex-col md:flex-row gap-6 items-center">
<div class="flex-1">
<Badge class="flex w-full text-base text-left space-x-3 px-3 text-text-secondary font-normal cursor py-1.5">
<UserIcon class="relative z-10 w-4 text-muted"></UserIcon>
<UserIcon class="relative z-10 w-4 text-text-secondary"></UserIcon>
<div class="flex-1 font-medium truncate">
{{ member.name }}
</div>

View File

@@ -2,7 +2,12 @@
import { TrashIcon, UserCircleIcon, PencilSquareIcon, ArrowDownOnSquareStackIcon } from '@heroicons/vue/20/solid';
import type { Member } from '@/packages/api/src';
import {canDeleteMembers, canMakeMembersPlaceholders, canMergeMembers, canUpdateMembers} from '@/utils/permissions';
import MoreOptionsDropdown from '@/packages/ui/src/MoreOptionsDropdown.vue';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/Components/ui/dropdown-menu';
const emit = defineEmits<{
delete: [];
@@ -13,51 +18,65 @@ const emit = defineEmits<{
const props = defineProps<{
member: Member;
}>();
</script>
<template>
<MoreOptionsDropdown
v-if="canUpdateMembers() || canDeleteMembers()"
:label="'Actions for Member ' + props.member.name">
<div class="min-w-[150px]">
<DropdownMenu v-if="canUpdateMembers() || canDeleteMembers()">
<DropdownMenuTrigger as-child>
<button
class="focus-visible:outline-none focus-visible:bg-card-background rounded-full focus-visible:ring-2 focus-visible:ring-ring focus-visible:opacity-100 hover:bg-card-background group-hover:opacity-100 opacity-20 transition-opacity text-text-secondary"
:aria-label="'Actions for Member ' + props.member.name">
<svg
class="h-8 w-8 p-1 rounded-full"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M12 5.92A.96.96 0 1 0 12 4a.96.96 0 0 0 0 1.92m0 7.04a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92M12 20a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92" />
</svg>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent class="min-w-[150px]" align="end">
<DropdownMenuItem
v-if="canUpdateMembers()"
:aria-label="'Edit Member ' + props.member.name"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-text-primary hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
class="flex items-center space-x-3 cursor-pointer"
@click="emit('edit')">
<PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon>
<PencilSquareIcon class="w-5 text-icon-active" />
<span>Edit</span>
</button>
<button
</DropdownMenuItem>
<DropdownMenuItem
v-if="canDeleteMembers()"
:aria-label="'Delete Member ' + props.member.name"
data-testid="member_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-text-primary hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
class="flex items-center space-x-3 cursor-pointer text-destructive focus:text-destructive"
@click="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<TrashIcon class="w-5" />
<span>Delete</span>
</button>
<button
</DropdownMenuItem>
<DropdownMenuItem
v-if="props.member.role === 'placeholder' && canMergeMembers()"
:aria-label="'Merge Member ' + props.member.name"
data-testid="member_merge"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-text-primary hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
class="flex items-center space-x-3 cursor-pointer"
@click="emit('merge')">
<ArrowDownOnSquareStackIcon class="w-5 text-icon-active"></ArrowDownOnSquareStackIcon>
<ArrowDownOnSquareStackIcon class="w-5 text-icon-active" />
<span>Merge</span>
</button>
<button
</DropdownMenuItem>
<DropdownMenuItem
v-if="props.member.role !== 'placeholder' && canMakeMembersPlaceholders()"
:aria-label="'Make Member ' + props.member.name + ' a placeholder'"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-text-primary hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
class="flex items-center space-x-3 cursor-pointer"
@click="emit('makePlaceholder')">
<UserCircleIcon class="w-5 text-icon-active"></UserCircleIcon>
<UserCircleIcon class="w-5 text-icon-active" />
<span>Deactivate</span>
</button>
</div>
</MoreOptionsDropdown>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>
<style scoped></style>

View File

@@ -43,7 +43,7 @@ function getNameForKey(key: string | undefined) {
<span>
{{ getNameForKey(model) }}
</span>
<ChevronDownIcon class="text-muted w-5"></ChevronDownIcon>
<ChevronDownIcon class="text-text-secondary w-5"></ChevronDownIcon>
</Badge>
</template>
</SelectDropdown>

View File

@@ -64,13 +64,13 @@ const userHasValidMailAddress = computed(() => {
{{ member.name }}
</span>
</div>
<div class="whitespace-nowrap px-3 py-4 text-sm text-muted">
<div class="whitespace-nowrap px-3 py-4 text-sm text-text-secondary">
{{ member.email }}
</div>
<div class="whitespace-nowrap px-3 py-4 text-sm text-muted">
<div class="whitespace-nowrap px-3 py-4 text-sm text-text-secondary">
{{ capitalizeFirstLetter(member.role) }}
</div>
<div class="whitespace-nowrap px-3 py-4 text-sm text-muted">
<div class="whitespace-nowrap px-3 py-4 text-sm text-text-secondary">
{{
member.billable_rate
? formatCents(
@@ -81,7 +81,7 @@ const userHasValidMailAddress = computed(() => {
}}
</div>
<div
class="whitespace-nowrap px-3 py-4 text-sm text-muted flex space-x-1 items-center font-medium">
class="whitespace-nowrap px-3 py-4 text-sm text-text-secondary flex space-x-1 items-center font-medium">
<CheckCircleIcon
v-if="member.is_placeholder === false"
class="w-5"></CheckCircleIcon>