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 ae00fdb0e9
commit e78a551098
204 changed files with 5458 additions and 1275 deletions

View File

@@ -129,7 +129,7 @@ const deleteApiToken = () => {
createApiTokenForm.permissions
"
:value="permission" />
<span class="ms-2 text-sm text-muted">{{
<span class="ms-2 text-sm text-text-secondary">{{
permission
}}</span>
</label>

View File

@@ -33,7 +33,7 @@ const submit = () => {
<AuthenticationCardLogo />
</template>
<div class="mb-4 text-sm text-muted">
<div class="mb-4 text-sm text-text-secondary">
This is a secure area of the application. Please confirm your
password before continuing.
</div>

View File

@@ -28,7 +28,7 @@ const submit = () => {
<AuthenticationCardLogo />
</template>
<div class="mb-4 text-sm text-muted">
<div class="mb-4 text-sm text-text-secondary">
Forgot your password? No problem. Just let us know your email
address and we will email you a password reset link that will allow
you to choose a new one.

View File

@@ -44,7 +44,7 @@ const page = usePage<{
<template #actions>
<Link
class="py-8 text-muted text-sm font-medium opacity-90 hover:opacity-100 transition"
class="py-8 text-text-secondary text-sm font-medium opacity-90 hover:opacity-100 transition"
:href="route('register')">
No account yet? <span class="text-text-primary">Register here!</span>
</Link>
@@ -89,7 +89,7 @@ const page = usePage<{
<Link
v-if="canResetPassword"
:href="route('password.request')"
class="underline text-sm text-muted hover:text-text-primary rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
class="underline text-sm text-text-secondary hover:text-text-primary rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Forgot your password?
</Link>

View File

@@ -49,7 +49,7 @@ const page = usePage<{
<template #actions>
<Link
class="py-8 text-muted text-sm font-medium opacity-90 hover:opacity-100 transition"
class="py-8 text-text-secondary text-sm font-medium opacity-90 hover:opacity-100 transition"
:href="route('login')">
Already have an account?
<span class="text-text-primary">Login here!</span>
@@ -135,14 +135,14 @@ const page = usePage<{
<a
target="_blank"
:href="page.props.terms_url"
class="underline text-sm text-muted hover:text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
class="underline text-sm text-text-secondary hover:text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>Terms of Service</a
>
and
<a
target="_blank"
:href="page.props.privacy_policy_url"
class="underline text-sm text-muted hover:text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
class="underline text-sm text-text-secondary hover:text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>Privacy Policy</a
>
</div>
@@ -173,7 +173,7 @@ const page = usePage<{
<div class="flex items-center justify-end mt-4">
<Link
:href="route('login')"
class="underline text-sm text-muted hover:text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
class="underline text-sm text-text-secondary hover:text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Already registered?
</Link>

View File

@@ -45,7 +45,7 @@ const submit = () => {
<AuthenticationCardLogo />
</template>
<div class="mb-4 text-sm text-muted">
<div class="mb-4 text-sm text-text-secondary">
<template v-if="!recovery">
Please confirm access to your account by entering the
authentication code provided by your authenticator application.
@@ -87,7 +87,7 @@ const submit = () => {
<div class="flex items-center justify-end mt-4">
<button
type="button"
class="text-sm text-muted hover:text-text-primary underline cursor-pointer"
class="text-sm text-text-secondary hover:text-text-primary underline cursor-pointer"
@click.prevent="toggleRecovery">
<template v-if="!recovery"> Use a recovery code</template>

View File

@@ -27,7 +27,7 @@ const verificationLinkSent = computed(
<AuthenticationCardLogo />
</template>
<div class="mb-4 text-sm text-muted">
<div class="mb-4 text-sm text-text-secondary">
Before continuing, could you verify your email address by clicking
on the link we just emailed to you? If you didn't receive the email,
we will gladly send you another.
@@ -53,7 +53,7 @@ const verificationLinkSent = computed(
:href="route('logout')"
method="post"
as="button"
class="underline text-sm text-muted hover:text-text-secondary rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800 ms-2">
class="underline text-sm text-text-secondary hover:text-text-secondary rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800 ms-2">
Log Out
</Link>
</div>

View File

@@ -20,10 +20,6 @@ onMounted(() => {
const activeTab = ref<'active' | 'archived'>('active');
function isActiveTab(tab: string) {
return activeTab.value === tab;
}
const createClient = ref(false);
const { clients } = storeToRefs(useClientsStore());
@@ -44,15 +40,16 @@ const shownClients = computed(() => {
class="py-5 border-b border-default-background-separator flex justify-between items-center">
<div class="flex items-center space-x-3 sm:space-x-6">
<PageTitle :icon="UserCircleIcon" title="Clients"> </PageTitle>
<TabBar>
<TabBar
v-model="activeTab"
>
<TabBarItem
:active="isActiveTab('active')"
@click="activeTab = 'active'"
value="active"
>Active</TabBarItem
>
<TabBarItem
:active="isActiveTab('archived')"
@click="activeTab = 'archived'">
value="archived"
>
Archived
</TabBarItem>
</TabBar>

View File

@@ -21,9 +21,6 @@ defineProps<{
}>();
const activeTab = ref<'all' | 'invitations'>('all');
function isActiveTab(tab: string) {
return activeTab.value === tab;
}
</script>
<template>
@@ -32,15 +29,15 @@ function isActiveTab(tab: string) {
class="py-5 border-b border-default-background-separator flex justify-between items-center">
<div class="flex items-center space-x-4 sm:space-x-6">
<PageTitle :icon="UserGroupIcon" title="Members"> </PageTitle>
<TabBar>
<TabBar
v-model="activeTab"
>
<TabBarItem
:active="isActiveTab('all')"
@click="activeTab = 'all'"
value="all"
>All</TabBarItem
>
<TabBarItem
:active="isActiveTab('invitations')"
@click="activeTab = 'invitations'"
value="invitations"
>Invitations</TabBarItem
>
</TabBar>

View File

@@ -44,7 +44,7 @@ const closeModal = () => {
<template #description> Permanently delete your account. </template>
<template #content>
<div class="max-w-xl text-sm text-muted">
<div class="max-w-xl text-sm text-text-secondary">
Once your account is deleted, all of its resources and data will
be permanently deleted. Before deleting your account, please
download any data or information that you wish to retain.

View File

@@ -53,7 +53,7 @@ const closeModal = () => {
</template>
<template #content>
<div class="max-w-xl text-sm text-muted">
<div class="max-w-xl text-sm text-text-secondary">
If necessary, you may log out of all of your other browser
sessions across all of your devices. Some of your recent
sessions are listed below; however, this list may not be
@@ -113,7 +113,7 @@ const closeModal = () => {
</div>
<div>
<div class="text-xs text-muted">
<div class="text-xs text-text-secondary">
{{ session.ip_address }},
<span

View File

@@ -141,7 +141,7 @@ const disableTwoFactorAuthentication = () => {
You have not enabled two factor authentication.
</h3>
<div class="mt-3 max-w-xl text-sm text-muted">
<div class="mt-3 max-w-xl text-sm text-text-secondary">
<p>
When two factor authentication is enabled, you will be
prompted for a secure, random token during authentication.
@@ -152,7 +152,7 @@ const disableTwoFactorAuthentication = () => {
<div v-if="twoFactorEnabled">
<div v-if="qrCode">
<div class="mt-4 max-w-xl text-sm text-muted">
<div class="mt-4 max-w-xl text-sm text-text-secondary">
<p v-if="confirming" class="font-semibold">
To finish enabling two factor authentication, scan
the following QR code using your phone's
@@ -173,7 +173,7 @@ const disableTwoFactorAuthentication = () => {
<div
v-if="setupKey"
class="mt-4 max-w-xl text-sm text-muted">
class="mt-4 max-w-xl text-sm text-text-secondary">
<p class="font-semibold">
Setup Key: <span v-html="setupKey"></span>
</p>

View File

@@ -185,7 +185,7 @@ const page = usePage<{
:href="route('verification.send')"
method="post"
as="button"
class="underline text-sm text-muted hover:text-text-secondary rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800"
class="underline text-sm text-text-secondary hover:text-text-secondary rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800"
@click.prevent="sendEmailVerification">
Click here to re-send the verification email.
</Link>

View File

@@ -60,10 +60,6 @@ const showEditProjectModal = ref(false);
const activeTab = ref<'active' | 'done'>('active');
function isActiveTab(tab: string) {
return activeTab.value === tab;
}
const { tasks } = storeToRefs(useTasksStore());
const shownTasks = computed(() => {
@@ -97,7 +93,7 @@ const shownTasks = computed(() => {
<div
class="flex items-center space-x-3 text-text-primary font-semibold text-base">
<ChevronRightIcon
class="h-5 w-5 flex-shrink-0 text-muted"
class="h-5 w-5 flex-shrink-0 text-text-secondary"
aria-hidden="true" />
<div class="flex space-x-3 items-center">
<div
@@ -149,15 +145,15 @@ const shownTasks = computed(() => {
<div
class="w-full items-center flex justify-between">
<div class="pl-6">
<TabBar>
<TabBar
v-model="activeTab"
>
<TabBarItem
:active="isActiveTab('active')"
@click="activeTab = 'active'"
value="active"
>Active
</TabBarItem>
<TabBarItem
:active="isActiveTab('done')"
@click="activeTab = 'done'"
value="done"
>Done
</TabBarItem>
</TabBar>

View File

@@ -35,10 +35,6 @@ const { organization } = storeToRefs(useOrganizationStore());
const activeTab = ref<'active' | 'archived'>('active');
function isActiveTab(tab: string) {
return activeTab.value === tab;
}
const { projects } = storeToRefs(useProjectsStore());
const shownProjects = computed(() => {
@@ -74,17 +70,16 @@ const showBillableRate = computed(() => {
class="py-3 sm:py-5 border-b border-default-background-separator flex justify-between items-center">
<div class="flex items-center space-x-3 sm:space-x-6">
<PageTitle :icon="FolderIcon" title="Projects"></PageTitle>
<TabBar>
<TabBar
v-model="activeTab"
>
<TabBarItem
:active="isActiveTab('active')"
@click="activeTab = 'active'"
value="active"
>Active</TabBarItem
>
<TabBarItem
:active="isActiveTab('archived')"
@click="activeTab = 'archived'">
Archived
</TabBarItem>
value="archived"
>Archived</TabBarItem>
</TabBar>
</div>
<SecondaryButton

View File

@@ -430,7 +430,7 @@ const tableData = computed(() => {
class="grid items-center"
style="grid-template-columns: 1fr 100px 150px">
<div
class="contents [&>*]:border-card-background-separator [&>*]:border-b [&>*]:bg-tertiary [&>*]:pb-1.5 [&>*]:pt-1 text-muted text-sm">
class="contents [&>*]:border-card-background-separator [&>*]:border-b [&>*]:bg-tertiary [&>*]:pb-1.5 [&>*]:pt-1 text-text-secondary text-sm">
<div class="pl-6">Name</div>
<div class="text-right">Duration</div>
<div class="text-right pr-6">Cost</div>

View File

@@ -141,7 +141,7 @@ function getGroupLabel(key: string) {
<template>
<Head :title="sharedReportResponseData?.name" />
<div class="text-muted">
<div class="text-text-secondary">
<MainContainer
class="py-3 sm:py-5 border-b border-default-background-separator flex justify-between items-center">
<div class="flex items-center space-x-3 sm:space-x-6">
@@ -174,7 +174,7 @@ function getGroupLabel(key: string) {
class="grid items-center"
style="grid-template-columns: 1fr 100px 150px">
<div
class="contents [&>*]:border-card-background-separator [&>*]:border-b [&>*]:bg-tertiary [&>*]:pb-1.5 [&>*]:pt-1 text-muted text-sm">
class="contents [&>*]:border-card-background-separator [&>*]:border-b [&>*]:bg-tertiary [&>*]:pb-1.5 [&>*]:pt-1 text-text-secondary text-sm">
<div class="pl-6">Name</div>
<div class="text-right">Duration</div>
<div class="text-right pr-6">Cost</div>

View File

@@ -50,7 +50,7 @@ const page = usePage<{
<div class="text-text-primary">
{{ page.props.auth.user.name }}
</div>
<div class="text-sm text-muted">
<div class="text-sm text-text-secondary">
{{ page.props.auth.user.email }}
</div>
</div>

View File

@@ -33,7 +33,7 @@ const deleteTeam = () => {
</template>
<template #content>
<div class="max-w-xl text-sm text-muted">
<div class="max-w-xl text-sm text-text-secondary">
Once a organization is deleted, all of its resources and data
will be permanently deleted. Before deleting this organization,
please download any data or information regarding this

View File

@@ -208,7 +208,7 @@ const showResultModal = ref(false);
<div
v-if="currentImporterDescription"
class="py-3 text-text-primary">
<div class="font-semibold text-muted py-1">
<div class="font-semibold text-text-secondary py-1">
Instructions:
</div>
<div
@@ -221,10 +221,10 @@ const showResultModal = ref(false);
class="mt-2 flex justify-center rounded-lg border border-dashed border-border-primary px-6 py-10">
<div class="text-center">
<DocumentIcon
class="mx-auto h-8 w-8 text-muted"
class="mx-auto h-8 w-8 text-text-secondary"
aria-hidden="true" />
<div class="mt-4 flex text-sm leading-6 text-muted">
<div class="mt-4 flex text-sm leading-6 text-text-secondary">
<label
for="file-upload"
class="relative cursor-pointer rounded-md font-semibold text-text-primary focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 focus-within:ring-offset-gray-900 hover:text-indigo-500">
@@ -241,7 +241,7 @@ const showResultModal = ref(false);
@change="updateFiles" />
</label>
</div>
<p class="text-xs leading-5 text-muted">
<p class="text-xs leading-5 text-text-secondary">
CSV and ZIP are supported
</p>
</div>

View File

@@ -54,7 +54,7 @@ const updateTeamName = () => {
<div class="text-text-primary">
{{ team.owner.name }}
</div>
<div class="text-muted text-sm">
<div class="text-text-secondary text-sm">
{{ team.owner.email }}
</div>
</div>

View File

@@ -203,7 +203,7 @@ function deleteSelected() {
</div>
<div
v-else-if="allTimeEntriesLoaded"
class="flex justify-center items-center py-5 text-muted font-medium">
class="flex justify-center items-center py-5 text-text-secondary font-medium">
All time entries are loaded!
</div>
</div>