Mega push vol 3

This commit is contained in:
AlacrisDevs
2026-02-05 01:48:29 +02:00
parent 9af0ef5307
commit b517bb975c
6 changed files with 908 additions and 23 deletions

View File

@@ -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">

View File

@@ -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 -->