Add calendar integrations and collaboration updates
Some checks failed
Backend CI/CD / build_and_deploy (push) Has been cancelled
Frontend CI/CD / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-05-05 15:25:53 -04:00
parent c49f03ec06
commit b66c10b681
82 changed files with 8420 additions and 2048 deletions

View File

@@ -69,6 +69,33 @@
{ key: 'workflow', label: t('workspaceSettings.tabs.workflow'), icon: mdiTuneVariant },
{ key: 'connectors', label: t('workspaceSettings.tabs.connectors'), icon: mdiFolderGoogleDrive },
]);
const activeTabDetail = computed(() => {
if (activeTab.value === 'members') {
return {
title: t('workspaceSettings.tabs.members'),
description: t('workspaceSettings.inviteDescription'),
};
}
if (activeTab.value === 'workflow') {
return {
title: t('workspaceSettings.tabs.workflow'),
description: t('workspaceSettings.approvals.flowDescription'),
};
}
if (activeTab.value === 'connectors') {
return {
title: t('workspaceSettings.tabs.connectors'),
description: t('workspaceSettings.connectors.description'),
};
}
return {
title: t('workspaceSettings.tabs.general'),
description: t('workspaceSettings.general.detailsDescription'),
};
});
const approvalModeOptions = computed(() => [
{ value: 'None', label: t('workspaceSettings.approvals.modes.none'), description: t('workspaceSettings.approvals.modeHelp.none') },
{ value: 'Optional', label: t('workspaceSettings.approvals.modes.optional'), description: t('workspaceSettings.approvals.modeHelp.optional') },
@@ -380,8 +407,16 @@
<h1>{{ workspaceStore.activeWorkspace?.name || t('workspaceSettings.noWorkspaceSelected') }}</h1>
<p>{{ t('workspaceSettings.description') }}</p>
</div>
</div>
<div class="tab-strip">
<div
v-if="workspaceStore.activeWorkspace"
class="workspace-settings-page"
>
<nav
class="tab-strip"
aria-label="Workspace settings sections"
>
<button
v-for="tab in settingsTabs"
:key="tab.key"
@@ -393,38 +428,42 @@
<v-icon :icon="tab.icon" />
<span>{{ tab.label }}</span>
</button>
</div>
</div>
</nav>
<div
v-if="activeTab === 'general'"
class="workspace-settings-grid workspace-settings-grid-single"
>
<article class="settings-card">
<div class="section-copy">
<span class="section-kicker">{{ t('workspaceSettings.general.detailsTitle') }}</span>
<p>{{ t('workspaceSettings.general.detailsDescription') }}</p>
<div class="tab-content">
<div class="tab-heading">
<h2>{{ activeTabDetail.title }}</h2>
<p>{{ activeTabDetail.description }}</p>
</div>
<div
v-if="settingsError"
class="page-message error"
v-if="activeTab === 'general'"
class="workspace-settings-grid workspace-settings-grid-single"
>
{{ settingsError }}
</div>
<article class="settings-card">
<div class="section-copy">
<span class="section-kicker">{{ t('workspaceSettings.general.detailsTitle') }}</span>
<p>{{ t('workspaceSettings.general.detailsDescription') }}</p>
</div>
<div
v-if="settingsStatus"
class="page-message success"
>
{{ settingsStatus }}
</div>
<div
v-if="settingsError"
class="page-message error"
>
{{ settingsError }}
</div>
<form
v-if="workspaceStore.activeWorkspace"
class="form-stack"
@submit.prevent="submitWorkspaceSettings"
>
<div
v-if="settingsStatus"
class="page-message success"
>
{{ settingsStatus }}
</div>
<form
class="form-stack"
@submit.prevent="submitWorkspaceSettings"
>
<div class="logo-picker-card">
<AppAvatar
:name="settingsForm.name || workspaceStore.activeWorkspace.name"
@@ -481,22 +520,16 @@
>
{{ workspaceStore.isUpdating ? t('common.saving') : t('workspaceSettings.general.saveAction') }}
</button>
</form>
</form>
</article>
</div>
<div
v-else
class="empty-state"
v-else-if="activeTab === 'members'"
class="workspace-settings-grid workspace-settings-grid-single"
>
{{ t('workspaceSettings.noWorkspaceSelected') }}
</div>
</article>
</div>
<div
v-else-if="activeTab === 'members'"
class="workspace-settings-grid workspace-settings-grid-single"
>
<article class="settings-card">
<article class="settings-card">
<div class="section-copy">
<span class="section-kicker">{{ t('workspaceSettings.members.inviteTitle') }}</span>
<p>{{ t('workspaceSettings.inviteDescription') }}</p>
@@ -530,9 +563,9 @@
{{ workspaceStore.isInviting ? t('common.creating') : t('workspaceSettings.sendInvite') }}
</button>
</form>
</article>
</article>
<article class="settings-card">
<article class="settings-card">
<div class="section-copy">
<span class="section-kicker">{{ t('workspaceSettings.members.pendingTitle') }}</span>
<p>{{ t('workspaceSettings.members.pendingDescription') }}</p>
@@ -568,9 +601,9 @@
>
{{ t('workspaceSettings.inviteEmpty') }}
</div>
</article>
</article>
<article class="settings-card">
<article class="settings-card">
<div class="section-copy">
<span class="section-kicker">{{ t('workspaceSettings.members.activeTitle') }}</span>
<p>{{ t('workspaceSettings.members.activeDescription') }}</p>
@@ -606,14 +639,14 @@
>
{{ t('workspaceSettings.members.activeEmpty') }}
</div>
</article>
</div>
</article>
</div>
<div
v-else-if="activeTab === 'workflow'"
class="workflow-grid"
>
<article class="settings-card">
<div
v-else-if="activeTab === 'workflow'"
class="workflow-grid"
>
<article class="settings-card">
<div class="section-copy">
<span class="section-kicker">{{ t('workspaceSettings.approvals.flowTitle') }}</span>
<p>{{ t('workspaceSettings.approvals.flowDescription') }}</p>
@@ -709,9 +742,9 @@
{{ workspaceStore.isUpdating ? t('common.saving') : t('workspaceSettings.approvals.saveAction') }}
</button>
</div>
</article>
</article>
<article class="settings-card">
<article class="settings-card">
<div class="section-copy">
<span class="section-kicker">{{ t('workspaceSettings.approvals.previewTitle') }}</span>
<p>{{ t('workspaceSettings.approvals.previewDescription') }}</p>
@@ -732,14 +765,14 @@
</div>
</div>
</div>
</article>
</div>
</article>
</div>
<div
v-else
class="workspace-settings-grid"
>
<article class="settings-card">
<div
v-else
class="workspace-settings-grid"
>
<article class="settings-card">
<div class="section-copy">
<span class="section-kicker">{{ t('workspaceSettings.connectors.title') }}</span>
<p>{{ t('workspaceSettings.connectors.description') }}</p>
@@ -771,7 +804,16 @@
<v-icon :icon="mdiImageMultipleOutline" />
<span>{{ t('workspaceSettings.connectors.openMediaLibrary') }}</span>
</router-link>
</article>
</article>
</div>
</div>
</div>
<div
v-else
class="empty-state"
>
{{ t('workspaceSettings.noWorkspaceSelected') }}
</div>
<ImageCropperDialog
@@ -792,11 +834,12 @@
}
.workspace-settings-hero {
@apply flex flex-col gap-5 rounded-[1.75rem] border p-5 md:p-6;
background:
radial-gradient(circle at top left, rgba(15, 118, 110, 0.16), transparent 38%),
linear-gradient(135deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.94));
border-color: rgba(23, 32, 51, 0.08);
@apply flex flex-col;
}
.workspace-settings-page,
.tab-content {
@apply flex flex-col gap-4;
}
.workspace-settings-grid {
@@ -812,10 +855,9 @@
}
.settings-card {
@apply flex flex-col gap-5 rounded-[1.75rem] border p-5;
background: rgba(255, 255, 255, 0.92);
@apply flex flex-col gap-5 rounded-[0.75rem] border p-5;
background: rgba(255, 255, 255, 0.94);
border-color: rgba(23, 32, 51, 0.08);
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
}
.section-copy {
@@ -823,26 +865,45 @@
}
.tab-strip {
@apply flex flex-wrap gap-3;
@apply flex flex-wrap gap-2 border-b pb-3;
border-color: rgba(23, 32, 51, 0.1);
}
.tab-button {
@apply inline-flex items-center gap-3 rounded-full px-4 py-3 text-sm font-semibold transition;
background: rgba(23, 32, 51, 0.06);
@apply inline-flex h-10 items-center gap-2 rounded-[0.75rem] px-3 text-sm font-semibold transition-colors;
color: #526178;
}
.tab-button:hover {
background: rgba(23, 32, 51, 0.06);
color: #172033;
}
.tab-button-active {
background: #172033;
color: #fffaf2;
}
.tab-button :deep(.v-icon) {
@apply text-lg;
}
.tab-heading {
@apply flex flex-col gap-1;
}
.tab-heading h2 {
@apply text-2xl font-black;
color: #172033;
}
.section-kicker {
@apply text-xs font-bold uppercase tracking-[0.2em];
color: #0f766e;
color: #c2410c;
}
.section-copy h1,
.tab-heading h2,
.invite-row strong,
.connector-copy strong,
.connector-status,
@@ -852,10 +913,11 @@
}
.section-copy h1 {
@apply text-3xl font-black;
@apply text-3xl font-black md:text-4xl;
}
.section-copy p,
.tab-heading p,
.invite-row span,
.invite-row small,
.empty-state,
@@ -868,8 +930,8 @@
}
.logo-picker-card {
@apply flex flex-col gap-4 rounded-[1rem] border p-4 sm:flex-row sm:items-center;
background: #fffaf2;
@apply flex flex-col gap-4 rounded-[0.75rem] border p-4 sm:flex-row sm:items-center;
background: rgba(23, 32, 51, 0.04);
border-color: rgba(23, 32, 51, 0.08);
}
@@ -910,25 +972,40 @@
.field input,
.field select {
@apply rounded-[1rem] border px-4 py-3 text-sm;
background: #fffdf8;
@apply h-11 rounded-[0.5rem] border px-3 text-sm outline-none transition-colors;
background: #ffffff;
border-color: rgba(23, 32, 51, 0.1);
color: #172033;
outline: none;
}
.field input:focus,
.field select:focus {
border-color: #0f766e;
box-shadow: 0 0 0 3px rgba(15, 118, 110, 0.12);
}
.primary-button {
@apply inline-flex items-center justify-center rounded-full px-5 py-3 text-sm font-semibold;
@apply inline-flex h-11 items-center justify-center rounded-[0.5rem] px-4 text-sm font-bold transition-colors;
background: #172033;
color: #fffaf2;
}
.secondary-button {
@apply inline-flex items-center justify-center rounded-full px-4 py-2 text-sm font-semibold;
background: rgba(23, 32, 51, 0.08);
@apply inline-flex h-11 items-center justify-center rounded-[0.5rem] border px-4 text-sm font-bold transition-colors;
background: #ffffff;
border-color: rgba(23, 32, 51, 0.14);
color: #172033;
}
.primary-button:hover:not(:disabled) {
background: #0f766e;
}
.secondary-button:hover:not(:disabled) {
border-color: #0f766e;
color: #0f766e;
}
.primary-button:disabled,
.secondary-button:disabled {
cursor: not-allowed;
@@ -948,8 +1025,8 @@
.workflow-rule,
.workflow-toggle,
.workflow-step {
@apply rounded-[1rem] border px-4 py-4;
background: #fffaf2;
@apply rounded-[0.75rem] border px-4 py-4;
background: rgba(23, 32, 51, 0.04);
border-color: rgba(23, 32, 51, 0.08);
}
@@ -984,7 +1061,7 @@
.connector-icon,
.workflow-step-icon {
@apply inline-flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-2xl;
@apply inline-flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-[0.75rem];
background: rgba(15, 118, 110, 0.1);
color: #0f766e;
}
@@ -995,12 +1072,12 @@
}
.connector-link {
@apply inline-flex w-fit items-center gap-3 rounded-full px-5 py-3 text-sm font-semibold no-underline transition;
@apply inline-flex h-11 w-fit items-center gap-3 rounded-[0.5rem] px-4 text-sm font-bold no-underline transition;
background: #172033;
color: #fffaf2;
}
.connector-link:hover {
background: #0f172a;
background: #0f766e;
}
</style>