Mega push vol 3
This commit is contained in:
@@ -35,6 +35,8 @@
|
||||
let selectedCard = $state<KanbanCard | null>(null);
|
||||
let newBoardName = $state("");
|
||||
let editBoardName = $state("");
|
||||
let newBoardVisibility = $state<"team" | "personal">("team");
|
||||
let editBoardVisibility = $state<"team" | "personal">("team");
|
||||
let targetColumnId = $state<string | null>(null);
|
||||
let cardModalMode = $state<"edit" | "create">("edit");
|
||||
|
||||
@@ -153,6 +155,23 @@
|
||||
targetColumnId = null;
|
||||
}
|
||||
|
||||
async function handleDeleteColumn(columnId: string) {
|
||||
if (!selectedBoard) return;
|
||||
if (!confirm("Delete this column and all its cards?")) return;
|
||||
|
||||
const { error } = await supabase
|
||||
.from("kanban_columns")
|
||||
.delete()
|
||||
.eq("id", columnId);
|
||||
|
||||
if (!error) {
|
||||
selectedBoard = {
|
||||
...selectedBoard,
|
||||
columns: selectedBoard.columns.filter((c) => c.id !== columnId),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCardMove(
|
||||
cardId: string,
|
||||
toColumnId: string,
|
||||
@@ -363,6 +382,7 @@
|
||||
onAddCard={handleAddCard}
|
||||
onAddColumn={handleAddColumn}
|
||||
onDeleteCard={handleCardDelete}
|
||||
onDeleteColumn={handleDeleteColumn}
|
||||
/>
|
||||
{:else}
|
||||
<div class="h-full flex items-center justify-center text-light/40">
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
extractCalendarId,
|
||||
getCalendarSubscribeUrl,
|
||||
} from "$lib/api/google-calendar";
|
||||
import { theme, PRESET_COLORS, type ThemeMode } from "$lib/stores/theme";
|
||||
import type { SupabaseClient } from "@supabase/supabase-js";
|
||||
import type { Database } from "$lib/supabase/types";
|
||||
|
||||
@@ -69,9 +70,9 @@
|
||||
const supabase = getContext<SupabaseClient<Database>>("supabase");
|
||||
|
||||
// Active tab
|
||||
let activeTab = $state<"general" | "members" | "roles" | "integrations">(
|
||||
"general",
|
||||
);
|
||||
let activeTab = $state<
|
||||
"general" | "members" | "roles" | "integrations" | "appearance"
|
||||
>("general");
|
||||
|
||||
// General settings state
|
||||
let orgName = $state(data.org.name);
|
||||
@@ -194,21 +195,37 @@
|
||||
if (!inviteEmail.trim()) return;
|
||||
isSendingInvite = true;
|
||||
|
||||
const email = inviteEmail.toLowerCase().trim();
|
||||
|
||||
// Delete any existing invite for this email first (handles 409 conflict)
|
||||
await supabase
|
||||
.from("org_invites")
|
||||
.delete()
|
||||
.eq("org_id", data.org.id)
|
||||
.eq("email", email);
|
||||
|
||||
const { data: invite, error } = await supabase
|
||||
.from("org_invites")
|
||||
.insert({
|
||||
org_id: data.org.id,
|
||||
email: inviteEmail.toLowerCase().trim(),
|
||||
email,
|
||||
role: inviteRole,
|
||||
invited_by: data.user!.id,
|
||||
expires_at: new Date(
|
||||
Date.now() + 7 * 24 * 60 * 60 * 1000,
|
||||
).toISOString(),
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (!error && invite) {
|
||||
// Remove old invite from UI if exists
|
||||
invites = invites.filter((i) => i.email !== email);
|
||||
invites = [...invites, invite as Invite];
|
||||
inviteEmail = "";
|
||||
showInviteModal = false;
|
||||
} else if (error) {
|
||||
alert("Failed to send invite: " + error.message);
|
||||
}
|
||||
isSendingInvite = false;
|
||||
}
|
||||
@@ -480,6 +497,13 @@
|
||||
: 'text-light/50 hover:text-light'}"
|
||||
onclick={() => (activeTab = "integrations")}>Integrations</button
|
||||
>
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-medium transition-colors {activeTab ===
|
||||
'appearance'
|
||||
? 'text-primary border-b-2 border-primary'
|
||||
: 'text-light/50 hover:text-light'}"
|
||||
onclick={() => (activeTab = "appearance")}>Appearance</button
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- General Tab -->
|
||||
@@ -953,6 +977,198 @@
|
||||
</Card>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Appearance Tab -->
|
||||
{#if activeTab === "appearance"}
|
||||
<div class="space-y-6 max-w-2xl">
|
||||
<Card>
|
||||
<div class="p-6">
|
||||
<h2 class="text-lg font-semibold text-light mb-4">Theme</h2>
|
||||
<p class="text-sm text-light/50 mb-6">
|
||||
Customize the look and feel of your workspace.
|
||||
</p>
|
||||
|
||||
<!-- Mode Selector -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-light mb-3"
|
||||
>Mode</label
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
{#each ["dark", "light", "system"] as mode}
|
||||
<button
|
||||
class="flex-1 px-4 py-3 rounded-lg border transition-all {$theme.mode ===
|
||||
mode
|
||||
? 'border-primary bg-primary/10 text-primary'
|
||||
: 'border-light/20 text-light/60 hover:border-light/40'}"
|
||||
onclick={() =>
|
||||
theme.setMode(mode as ThemeMode)}
|
||||
>
|
||||
<div
|
||||
class="flex flex-col items-center gap-1"
|
||||
>
|
||||
{#if mode === "dark"}
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if mode === "light"}
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
<line
|
||||
x1="12"
|
||||
y1="1"
|
||||
x2="12"
|
||||
y2="3"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
y1="21"
|
||||
x2="12"
|
||||
y2="23"
|
||||
/>
|
||||
<line
|
||||
x1="4.22"
|
||||
y1="4.22"
|
||||
x2="5.64"
|
||||
y2="5.64"
|
||||
/>
|
||||
<line
|
||||
x1="18.36"
|
||||
y1="18.36"
|
||||
x2="19.78"
|
||||
y2="19.78"
|
||||
/>
|
||||
<line
|
||||
x1="1"
|
||||
y1="12"
|
||||
x2="3"
|
||||
y2="12"
|
||||
/>
|
||||
<line
|
||||
x1="21"
|
||||
y1="12"
|
||||
x2="23"
|
||||
y2="12"
|
||||
/>
|
||||
<line
|
||||
x1="4.22"
|
||||
y1="19.78"
|
||||
x2="5.64"
|
||||
y2="18.36"
|
||||
/>
|
||||
<line
|
||||
x1="18.36"
|
||||
y1="5.64"
|
||||
x2="19.78"
|
||||
y2="4.22"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect
|
||||
x="2"
|
||||
y="3"
|
||||
width="20"
|
||||
height="14"
|
||||
rx="2"
|
||||
/>
|
||||
<line
|
||||
x1="8"
|
||||
y1="21"
|
||||
x2="16"
|
||||
y2="21"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
y1="17"
|
||||
x2="12"
|
||||
y2="21"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
<span class="text-xs capitalize"
|
||||
>{mode}</span
|
||||
>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Accent Color -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-light mb-3"
|
||||
>Accent Color</label
|
||||
>
|
||||
<div class="grid grid-cols-4 gap-3">
|
||||
{#each PRESET_COLORS as color}
|
||||
<button
|
||||
class="group relative h-12 rounded-lg transition-all {$theme.primaryColor ===
|
||||
color.primary
|
||||
? 'ring-2 ring-offset-2 ring-offset-dark ring-white'
|
||||
: 'hover:scale-105'}"
|
||||
style="background-color: {color.primary}"
|
||||
onclick={() =>
|
||||
theme.setPrimaryColor(color.primary)}
|
||||
title={color.name}
|
||||
>
|
||||
{#if $theme.primaryColor === color.primary}
|
||||
<svg
|
||||
class="absolute inset-0 m-auto w-5 h-5 text-white"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="3"
|
||||
>
|
||||
<polyline points="20,6 9,17 4,12" />
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<p class="text-xs text-light/40 mt-3">
|
||||
Selected: {PRESET_COLORS.find(
|
||||
(c) => c.primary === $theme.primaryColor,
|
||||
)?.name || "Custom"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<div class="p-6">
|
||||
<h2 class="text-lg font-semibold text-light mb-2">
|
||||
Reset Theme
|
||||
</h2>
|
||||
<p class="text-sm text-light/50 mb-4">
|
||||
Reset to the default theme settings.
|
||||
</p>
|
||||
<Button variant="secondary" onclick={() => theme.reset()}>
|
||||
Reset to Default
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Invite Member Modal -->
|
||||
|
||||
Reference in New Issue
Block a user