From 1bd228a938a5aeb024ae420dd8427a59048fd9bd Mon Sep 17 00:00:00 2001 From: aster <137767097+aster-void@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:39:34 +0900 Subject: [PATCH 1/7] feat: implement Organization functionality with multi-tenant architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add organizations and organizationMembers tables to schema with proper indexing - Implement organization CRUD operations with role-based permissions (admin/member/visitor) - Update channels to require organizationId and enforce permissions - Create organization selection UI as entry point to application - Add organization-scoped chat interface with member management - Deprecate old ChannelList component in favor of organization-scoped version - Enforce proper authentication and authorization throughout 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 4 + .../src/components/chat/ChannelList.svelte | 33 +-- .../OrganizationChannelList.svelte | 67 +++++ .../organization/OrganizationChatApp.svelte | 91 +++++++ .../organization/OrganizationSelector.svelte | 59 +++++ packages/client/src/routes/+page.svelte | 25 +- packages/convex/src/convex/channels.ts | 73 +++++- packages/convex/src/convex/organizations.ts | 248 ++++++++++++++++++ packages/convex/src/convex/schema.ts | 21 +- 9 files changed, 587 insertions(+), 34 deletions(-) create mode 100644 packages/client/src/components/organization/OrganizationChannelList.svelte create mode 100644 packages/client/src/components/organization/OrganizationChatApp.svelte create mode 100644 packages/client/src/components/organization/OrganizationSelector.svelte create mode 100644 packages/convex/src/convex/organizations.ts diff --git a/CLAUDE.md b/CLAUDE.md index 9a1871b..80d929d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -163,3 +163,7 @@ bun dev:tauri ``` Tauri conflicts with the web development server and requires more resources for compilation. + +## Code Architecture Best Practices + +- Separate components into smallest pieces for readability. diff --git a/packages/client/src/components/chat/ChannelList.svelte b/packages/client/src/components/chat/ChannelList.svelte index ba79353..2866d23 100644 --- a/packages/client/src/components/chat/ChannelList.svelte +++ b/packages/client/src/components/chat/ChannelList.svelte @@ -1,6 +1,5 @@ @@ -28,27 +26,8 @@
- {#if channels.data} - {#each channels.data as channel (channel._id)} - - {/each} - {:else} -
- チャンネルを読み込み中... -
- {/if} +
+ この機能は廃止されました。OrganizationChannelListを使用してください。 +
diff --git a/packages/client/src/components/organization/OrganizationChannelList.svelte b/packages/client/src/components/organization/OrganizationChannelList.svelte new file mode 100644 index 0000000..9999e1e --- /dev/null +++ b/packages/client/src/components/organization/OrganizationChannelList.svelte @@ -0,0 +1,67 @@ + + +
+
+

チャンネル

+ +
+ +
+ {#if channels.data} + {#each channels.data as channel (channel._id)} + + {/each} + {:else} +
+ チャンネルを読み込み中... +
+ {/if} + + {#if channels.data && channels.data.length === 0} +
+ まだチャンネルがありません +
+ {/if} +
+
diff --git a/packages/client/src/components/organization/OrganizationChatApp.svelte b/packages/client/src/components/organization/OrganizationChatApp.svelte new file mode 100644 index 0000000..9059f9f --- /dev/null +++ b/packages/client/src/components/organization/OrganizationChatApp.svelte @@ -0,0 +1,91 @@ + + +
+
+
+
+
+

+ {organization.data?.name || "組織"} +

+ {#if organization.data?.description} +

+ {organization.data.description} +

+ {/if} +
+ +
+ {#if organization.data?.role} +
+ {organization.data.role} +
+ {/if} +
+ + +
+ +
+ {#if selectedChannelId} + + {:else} +
+
+

+ {organization.data?.name || "組織"}へようこそ +

+

+ 左からチャンネルを選択して会話を始めましょう +

+
+
+ {/if} +
+
diff --git a/packages/client/src/components/organization/OrganizationSelector.svelte b/packages/client/src/components/organization/OrganizationSelector.svelte new file mode 100644 index 0000000..ac22211 --- /dev/null +++ b/packages/client/src/components/organization/OrganizationSelector.svelte @@ -0,0 +1,59 @@ + + +
+
+
+

組織を選択

+

+ 参加している組織からチャットする組織を選んでください +

+
+ +
+ {#if organizations.data} + {#each organizations.data as org} + + {/each} + {:else} +
+ +
+ {/if} +
+ + {#if organizations.data && organizations.data.length === 0} +
+

参加している組織がありません

+ +
+ {/if} +
+
diff --git a/packages/client/src/routes/+page.svelte b/packages/client/src/routes/+page.svelte index 0176d4a..8e7ba08 100644 --- a/packages/client/src/routes/+page.svelte +++ b/packages/client/src/routes/+page.svelte @@ -1,8 +1,22 @@ {#if auth.isLoading} @@ -10,7 +24,14 @@ {:else if auth.isAuthenticated} - + {#if selectedOrganizationId} + + {:else} + + {/if} {:else}
diff --git a/packages/convex/src/convex/channels.ts b/packages/convex/src/convex/channels.ts index 4c3d8f4..8535ea0 100644 --- a/packages/convex/src/convex/channels.ts +++ b/packages/convex/src/convex/channels.ts @@ -1,10 +1,34 @@ +import { getAuthUserId } from "@convex-dev/auth/server"; import { v } from "convex/values"; import { mutation, query } from "./_generated/server"; export const list = query({ - args: {}, - handler: async (ctx) => { - return await ctx.db.query("channels").order("desc").collect(); + args: { organizationId: v.id("organizations") }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + if (!userId) { + return []; + } + + const membership = await ctx.db + .query("organizationMembers") + .withIndex("by_organization", (q) => + q.eq("organizationId", args.organizationId), + ) + .filter((q) => q.eq(q.field("userId"), userId)) + .first(); + + if (!membership) { + return []; + } + + return await ctx.db + .query("channels") + .withIndex("by_organization", (q) => + q.eq("organizationId", args.organizationId), + ) + .order("desc") + .collect(); }, }); @@ -12,11 +36,30 @@ export const create = mutation({ args: { name: v.string(), description: v.optional(v.string()), + organizationId: v.id("organizations"), }, handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + if (!userId) { + throw new Error("Not authenticated"); + } + + const membership = await ctx.db + .query("organizationMembers") + .withIndex("by_organization", (q) => + q.eq("organizationId", args.organizationId), + ) + .filter((q) => q.eq(q.field("userId"), userId)) + .first(); + + if (!membership || membership.role === "visitor") { + throw new Error("Insufficient permissions"); + } + const channelId = await ctx.db.insert("channels", { name: args.name, description: args.description, + organizationId: args.organizationId, createdAt: Date.now(), }); return channelId; @@ -26,6 +69,28 @@ export const create = mutation({ export const get = query({ args: { id: v.id("channels") }, handler: async (ctx, args) => { - return await ctx.db.get(args.id); + const userId = await getAuthUserId(ctx); + if (!userId) { + return null; + } + + const channel = await ctx.db.get(args.id); + if (!channel) { + return null; + } + + const membership = await ctx.db + .query("organizationMembers") + .withIndex("by_organization", (q) => + q.eq("organizationId", channel.organizationId), + ) + .filter((q) => q.eq(q.field("userId"), userId)) + .first(); + + if (!membership) { + return null; + } + + return channel; }, }); diff --git a/packages/convex/src/convex/organizations.ts b/packages/convex/src/convex/organizations.ts new file mode 100644 index 0000000..1b848e8 --- /dev/null +++ b/packages/convex/src/convex/organizations.ts @@ -0,0 +1,248 @@ +import { getAuthUserId } from "@convex-dev/auth/server"; +import { v } from "convex/values"; +import { mutation, query } from "./_generated/server"; + +export const create = mutation({ + args: { + name: v.string(), + description: v.optional(v.string()), + }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + if (!userId) { + throw new Error("Not authenticated"); + } + + const organizationId = await ctx.db.insert("organizations", { + name: args.name, + description: args.description, + createdAt: Date.now(), + ownerId: userId, + }); + + await ctx.db.insert("organizationMembers", { + organizationId, + userId, + role: "admin", + joinedAt: Date.now(), + }); + + return organizationId; + }, +}); + +export const list = query({ + args: {}, + handler: async (ctx) => { + const userId = await getAuthUserId(ctx); + if (!userId) { + return []; + } + + const memberships = await ctx.db + .query("organizationMembers") + .withIndex("by_user", (q) => q.eq("userId", userId)) + .collect(); + + const organizations = await Promise.all( + memberships.map(async (membership) => { + const org = await ctx.db.get(membership.organizationId); + return { + ...org, + role: membership.role, + }; + }), + ); + + return organizations.filter((org) => org !== null); + }, +}); + +export const get = query({ + args: { id: v.id("organizations") }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + if (!userId) { + return null; + } + + const membership = await ctx.db + .query("organizationMembers") + .withIndex("by_organization", (q) => q.eq("organizationId", args.id)) + .filter((q) => q.eq(q.field("userId"), userId)) + .first(); + + if (!membership) { + return null; + } + + const organization = await ctx.db.get(args.id); + if (!organization) { + return null; + } + + return { + ...organization, + role: membership.role, + }; + }, +}); + +export const update = mutation({ + args: { + id: v.id("organizations"), + name: v.optional(v.string()), + description: v.optional(v.string()), + }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + if (!userId) { + throw new Error("Not authenticated"); + } + + const membership = await ctx.db + .query("organizationMembers") + .withIndex("by_organization", (q) => q.eq("organizationId", args.id)) + .filter((q) => q.eq(q.field("userId"), userId)) + .first(); + + if (!membership || membership.role !== "admin") { + throw new Error("Insufficient permissions"); + } + + const updates: { name?: string; description?: string } = {}; + if (args.name !== undefined) updates.name = args.name; + if (args.description !== undefined) updates.description = args.description; + + await ctx.db.patch(args.id, updates); + }, +}); + +export const addMember = mutation({ + args: { + organizationId: v.id("organizations"), + userId: v.id("users"), + role: v.union( + v.literal("admin"), + v.literal("member"), + v.literal("visitor"), + ), + }, + handler: async (ctx, args) => { + const currentUserId = await getAuthUserId(ctx); + if (!currentUserId) { + throw new Error("Not authenticated"); + } + + const currentMembership = await ctx.db + .query("organizationMembers") + .withIndex("by_organization", (q) => + q.eq("organizationId", args.organizationId), + ) + .filter((q) => q.eq(q.field("userId"), currentUserId)) + .first(); + + if (!currentMembership || currentMembership.role !== "admin") { + throw new Error("Insufficient permissions"); + } + + const existingMembership = await ctx.db + .query("organizationMembers") + .withIndex("by_organization", (q) => + q.eq("organizationId", args.organizationId), + ) + .filter((q) => q.eq(q.field("userId"), args.userId)) + .first(); + + if (existingMembership) { + throw new Error("User is already a member"); + } + + await ctx.db.insert("organizationMembers", { + organizationId: args.organizationId, + userId: args.userId, + role: args.role, + joinedAt: Date.now(), + }); + }, +}); + +export const removeMember = mutation({ + args: { + organizationId: v.id("organizations"), + userId: v.id("users"), + }, + handler: async (ctx, args) => { + const currentUserId = await getAuthUserId(ctx); + if (!currentUserId) { + throw new Error("Not authenticated"); + } + + const currentMembership = await ctx.db + .query("organizationMembers") + .withIndex("by_organization", (q) => + q.eq("organizationId", args.organizationId), + ) + .filter((q) => q.eq(q.field("userId"), currentUserId)) + .first(); + + if (!currentMembership || currentMembership.role !== "admin") { + throw new Error("Insufficient permissions"); + } + + const targetMembership = await ctx.db + .query("organizationMembers") + .withIndex("by_organization", (q) => + q.eq("organizationId", args.organizationId), + ) + .filter((q) => q.eq(q.field("userId"), args.userId)) + .first(); + + if (!targetMembership) { + throw new Error("User is not a member"); + } + + await ctx.db.delete(targetMembership._id); + }, +}); + +export const getMembers = query({ + args: { organizationId: v.id("organizations") }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + if (!userId) { + return []; + } + + const currentMembership = await ctx.db + .query("organizationMembers") + .withIndex("by_organization", (q) => + q.eq("organizationId", args.organizationId), + ) + .filter((q) => q.eq(q.field("userId"), userId)) + .first(); + + if (!currentMembership) { + return []; + } + + const memberships = await ctx.db + .query("organizationMembers") + .withIndex("by_organization", (q) => + q.eq("organizationId", args.organizationId), + ) + .collect(); + + const members = await Promise.all( + memberships.map(async (membership) => { + const user = await ctx.db.get(membership.userId); + return { + ...membership, + user, + }; + }), + ); + + return members.filter((member) => member.user !== null); + }, +}); diff --git a/packages/convex/src/convex/schema.ts b/packages/convex/src/convex/schema.ts index 777bb6e..71b3ae4 100644 --- a/packages/convex/src/convex/schema.ts +++ b/packages/convex/src/convex/schema.ts @@ -8,11 +8,30 @@ export default defineSchema({ isCompleted: v.boolean(), assigner: v.string(), }), - channels: defineTable({ + organizations: defineTable({ name: v.string(), description: v.optional(v.string()), createdAt: v.number(), + ownerId: v.id("users"), }), + organizationMembers: defineTable({ + organizationId: v.id("organizations"), + userId: v.id("users"), + role: v.union( + v.literal("admin"), + v.literal("member"), + v.literal("visitor"), + ), + joinedAt: v.number(), + }) + .index("by_organization", ["organizationId"]) + .index("by_user", ["userId"]), + channels: defineTable({ + name: v.string(), + description: v.optional(v.string()), + organizationId: v.id("organizations"), + createdAt: v.number(), + }).index("by_organization", ["organizationId"]), messages: defineTable({ channelId: v.id("channels"), content: v.string(), From ec6c56524df6b3353aa4f5f0e41e6fa4b4e46f39 Mon Sep 17 00:00:00 2001 From: aster <137767097+aster-void@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:59:26 +0900 Subject: [PATCH 2/7] feat: add comprehensive organization management pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add organization listing page at /organizations with grid layout - Add individual organization details page with admin editing capabilities - Add organization creation form with validation - Implement custom useMutation utility for convex-svelte compatibility - Add member management with role-based permissions - Update CLAUDE.md with useMutation documentation - Fix import ordering and linting issues 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 16 +- .../organization/OrganizationSelector.svelte | 4 +- packages/client/src/lib/useMutation.ts | 12 ++ .../src/routes/organizations/+page.svelte | 82 +++++++ .../routes/organizations/[id]/+page.svelte | 203 ++++++++++++++++++ .../routes/organizations/create/+page.svelte | 109 ++++++++++ 6 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/lib/useMutation.ts create mode 100644 packages/client/src/routes/organizations/+page.svelte create mode 100644 packages/client/src/routes/organizations/[id]/+page.svelte create mode 100644 packages/client/src/routes/organizations/create/+page.svelte diff --git a/CLAUDE.md b/CLAUDE.md index 80d929d..ae30a88 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -118,6 +118,19 @@ bun paraglide ``` +### Mutations with useMutation + +Since `convex-svelte` doesn't export `useMutation`, we have a custom utility at `src/lib/useMutation.ts`: + +```typescript +import { useMutation } from "~/lib/useMutation"; + +const createOrganization = useMutation(api.organizations.create); + +// Use like any mutation hook +await createOrganization({ name: "New Org", description: "..." }); +``` + ### Backend (Convex) - **Schema**: Defined in `packages/convex/src/convex/schema.ts` @@ -164,6 +177,7 @@ bun dev:tauri Tauri conflicts with the web development server and requires more resources for compilation. -## Code Architecture Best Practices +## Coding Instructions +- Always prefer using DaisyUI classes, and use minimal Tailwind classes. - Separate components into smallest pieces for readability. diff --git a/packages/client/src/components/organization/OrganizationSelector.svelte b/packages/client/src/components/organization/OrganizationSelector.svelte index ac22211..4a0bf5b 100644 --- a/packages/client/src/components/organization/OrganizationSelector.svelte +++ b/packages/client/src/components/organization/OrganizationSelector.svelte @@ -52,7 +52,9 @@ {#if organizations.data && organizations.data.length === 0}

参加している組織がありません

- + 新しい組織を作成
{/if}
diff --git a/packages/client/src/lib/useMutation.ts b/packages/client/src/lib/useMutation.ts new file mode 100644 index 0000000..e07b1ec --- /dev/null +++ b/packages/client/src/lib/useMutation.ts @@ -0,0 +1,12 @@ +import type { FunctionReference, OptionalRestArgs } from "convex/server"; +import { useConvexClient } from "convex-svelte"; + +export function useMutation>( + mutationFunction: T, +) { + const convex = useConvexClient(); + + return async (...args: OptionalRestArgs) => { + return await convex.mutation(mutationFunction, ...args); + }; +} diff --git a/packages/client/src/routes/organizations/+page.svelte b/packages/client/src/routes/organizations/+page.svelte new file mode 100644 index 0000000..47a7d55 --- /dev/null +++ b/packages/client/src/routes/organizations/+page.svelte @@ -0,0 +1,82 @@ + + +
+
+

組織管理

+

参加している組織の管理と新しい組織の作成

+
+ +
+ {#if organizations.data} + {#each organizations.data as org} +
+
+

{org.name}

+ {#if org.description} +

{org.description}

+ {/if} +
+
{org.role}
+
+ +
+
+
+
+ {/each} + {:else} +
+ +
+ {/if} +
+ + {#if organizations.data && organizations.data.length === 0} +
+

参加している組織がありません

+ +
+ {/if} + +
+ +
+
diff --git a/packages/client/src/routes/organizations/[id]/+page.svelte b/packages/client/src/routes/organizations/[id]/+page.svelte new file mode 100644 index 0000000..9847cf0 --- /dev/null +++ b/packages/client/src/routes/organizations/[id]/+page.svelte @@ -0,0 +1,203 @@ + + +
+
+ + + {#if organization.data} +
+
+ {#if isEditing} + + + {:else} +

+ {organization.data.name} +

+ {#if organization.data.description} +

+ {organization.data.description} +

+ {/if} + {/if} +
+ + {#if organization.data.role === "admin"} +
+ {#if isEditing} + + + {:else} + + {/if} +
+ {/if} +
+ {:else} +
+ +
+ {/if} +
+ +
+ +
+
+

組織情報

+ {#if organization.data} +
+
+ 作成日: + {new Date(organization.data.createdAt).toLocaleDateString( + "ja-JP", + )} +
+
+ あなたの役割: +
+ {organization.data.role} +
+
+
+ {/if} +
+
+ + +
+
+
+

メンバー

+ {#if organization.data?.role === "admin"} + + {/if} +
+ + {#if members.data} +
+ {#each members.data as member} +
+
+
+
+ {member.user?.name?.[0] || "?"} +
+
+
+
+ {member.user?.name || "Unknown User"} +
+
+ {member.user?.email} +
+
+
+
+
+ {member.role} +
+ {#if organization.data?.role === "admin" && member.userId !== organization.data?.ownerId} + + {/if} +
+
+ {/each} +
+ {:else} +
+ +
+ {/if} +
+
+
+
diff --git a/packages/client/src/routes/organizations/create/+page.svelte b/packages/client/src/routes/organizations/create/+page.svelte new file mode 100644 index 0000000..048a8eb --- /dev/null +++ b/packages/client/src/routes/organizations/create/+page.svelte @@ -0,0 +1,109 @@ + + +
+
+ + +

新しい組織を作成

+

+ 新しい組織を作成して、メンバーとコラボレーションを始めましょう +

+
+ +
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+
From a8cca4db9b2685e7f6f2b6e552c6b61324259906 Mon Sep 17 00:00:00 2001 From: aster <137767097+aster-void@users.noreply.github.com> Date: Wed, 13 Aug 2025 21:41:15 +0900 Subject: [PATCH 3/7] wip --- CLAUDE.md | 8 ++ .../organization/OrganizationChatApp.svelte | 7 +- .../organization/OrganizationSelector.svelte | 4 +- packages/client/src/routes/+page.svelte | 21 +---- .../[id] => [orgId]/settings}/+page.svelte | 18 ++-- .../client/src/routes/chat/[id]/+page.svelte | 17 ++++ .../src/routes/organizations/+page.svelte | 82 ------------------- .../routes/organizations/create/+page.svelte | 6 +- packages/convex/src/convex/channels.ts | 2 +- packages/convex/src/convex/organizations.ts | 14 ++-- packages/convex/src/convex/schema.ts | 3 +- 11 files changed, 60 insertions(+), 122 deletions(-) rename packages/client/src/routes/{organizations/[id] => [orgId]/settings}/+page.svelte (91%) create mode 100644 packages/client/src/routes/chat/[id]/+page.svelte delete mode 100644 packages/client/src/routes/organizations/+page.svelte diff --git a/CLAUDE.md b/CLAUDE.md index ae30a88..6897f0a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,6 +9,8 @@ This is a TypeScript monorepo using a Convex backend and SvelteKit frontend with ### Stack - **Frontend**: SvelteKit with Svelte 5, TypeScript, TailwindCSS, DaisyUI + - **CRITICAL**: This project uses Svelte 5 RUNES MODE - NEVER use legacy reactive statements (`$:`) + - **ALWAYS use**: `$state`, `$derived`, `$effect` instead of legacy syntax - **Backend**: Convex (real-time database and functions) - **Desktop**: Tauri (optional, conflicts with web dev server) - **Internationalization**: Paraglide for i18n (English/Japanese) @@ -179,5 +181,11 @@ Tauri conflicts with the web development server and requires more resources for ## Coding Instructions +- **🚫 NEVER USE LEGACY SVELTE SYNTAX**: This project uses Svelte 5 runes mode + - ❌ FORBIDDEN: `$: reactiveVar = ...` (reactive statements) + - ❌ FORBIDDEN: `let count = 0` for reactive state + - ✅ REQUIRED: `const reactiveVar = $derived(...)` + - ✅ REQUIRED: `let count = $state(0)` for reactive state + - ✅ REQUIRED: `$effect(() => { ... })` for side effects - Always prefer using DaisyUI classes, and use minimal Tailwind classes. - Separate components into smallest pieces for readability. diff --git a/packages/client/src/components/organization/OrganizationChatApp.svelte b/packages/client/src/components/organization/OrganizationChatApp.svelte index 9059f9f..5db947a 100644 --- a/packages/client/src/components/organization/OrganizationChatApp.svelte +++ b/packages/client/src/components/organization/OrganizationChatApp.svelte @@ -56,15 +56,18 @@ tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow" > +
  • + 組織設定 +
  • - {#if organization.data?.role} + {#if organization.data?.permission}
    - {organization.data.role} + {organization.data.permission}
    {/if} diff --git a/packages/client/src/components/organization/OrganizationSelector.svelte b/packages/client/src/components/organization/OrganizationSelector.svelte index 4a0bf5b..3cb117e 100644 --- a/packages/client/src/components/organization/OrganizationSelector.svelte +++ b/packages/client/src/components/organization/OrganizationSelector.svelte @@ -37,7 +37,9 @@

    {/if} -
    {org.role}
    +
    + {org.permission} +
    diff --git a/packages/client/src/routes/+page.svelte b/packages/client/src/routes/+page.svelte index 8e7ba08..784c810 100644 --- a/packages/client/src/routes/+page.svelte +++ b/packages/client/src/routes/+page.svelte @@ -1,21 +1,13 @@ @@ -24,14 +16,7 @@ {:else if auth.isAuthenticated} - {#if selectedOrganizationId} - - {:else} - - {/if} + {:else}
    diff --git a/packages/client/src/routes/organizations/[id]/+page.svelte b/packages/client/src/routes/[orgId]/settings/+page.svelte similarity index 91% rename from packages/client/src/routes/organizations/[id]/+page.svelte rename to packages/client/src/routes/[orgId]/settings/+page.svelte index 9847cf0..adc0d60 100644 --- a/packages/client/src/routes/organizations/[id]/+page.svelte +++ b/packages/client/src/routes/[orgId]/settings/+page.svelte @@ -5,7 +5,7 @@ import { page } from "$app/stores"; import { useMutation } from "~/lib/useMutation"; - $: organizationId = $page.params.id as Id<"organizations">; + const organizationId = $derived($page.params.orgId as Id<"organizations">); const organization = useQuery(api.organizations.get, () => ({ id: organizationId, @@ -57,14 +57,14 @@ } function goBack() { - goto("/organizations"); + goto(`/chat/${organizationId}`); }
    {#if organization.data} @@ -93,7 +93,7 @@ {/if}
    - {#if organization.data.role === "admin"} + {#if organization.data.permission === "admin"}
    {#if isEditing}
    @@ -146,7 +146,7 @@

    メンバー

    - {#if organization.data?.role === "admin"} + {#if organization.data?.permission === "admin"} {/if}
    @@ -178,9 +178,9 @@
    - {member.role} + {member.permission}
    - {#if organization.data?.role === "admin" && member.userId !== organization.data?.ownerId} + {#if organization.data?.permission === "admin" && member.userId !== organization.data?.ownerId} -
    -
    -
    -
    - {/each} - {:else} -
    - -
    - {/if} - - - {#if organizations.data && organizations.data.length === 0} -
    -

    参加している組織がありません

    - -
    - {/if} - -
    - -
    - diff --git a/packages/client/src/routes/organizations/create/+page.svelte b/packages/client/src/routes/organizations/create/+page.svelte index 048a8eb..bbdc069 100644 --- a/packages/client/src/routes/organizations/create/+page.svelte +++ b/packages/client/src/routes/organizations/create/+page.svelte @@ -22,7 +22,7 @@ description: form.description.trim() || undefined, }); - goto(`/organizations/${organizationId}`); + goto(`/${organizationId}/settings`); } catch (error) { console.error("Failed to create organization:", error); alert("組織の作成に失敗しました"); @@ -32,14 +32,14 @@ } function goBack() { - goto("/organizations"); + goto("/"); }

    新しい組織を作成

    diff --git a/packages/convex/src/convex/channels.ts b/packages/convex/src/convex/channels.ts index 8535ea0..76075b5 100644 --- a/packages/convex/src/convex/channels.ts +++ b/packages/convex/src/convex/channels.ts @@ -52,7 +52,7 @@ export const create = mutation({ .filter((q) => q.eq(q.field("userId"), userId)) .first(); - if (!membership || membership.role === "visitor") { + if (!membership || membership.permission === "visitor") { throw new Error("Insufficient permissions"); } diff --git a/packages/convex/src/convex/organizations.ts b/packages/convex/src/convex/organizations.ts index 1b848e8..b87a4e1 100644 --- a/packages/convex/src/convex/organizations.ts +++ b/packages/convex/src/convex/organizations.ts @@ -23,7 +23,7 @@ export const create = mutation({ await ctx.db.insert("organizationMembers", { organizationId, userId, - role: "admin", + permission: "admin", joinedAt: Date.now(), }); @@ -49,6 +49,7 @@ export const list = query({ const org = await ctx.db.get(membership.organizationId); return { ...org, + permission: membership.permission, role: membership.role, }; }), @@ -83,6 +84,7 @@ export const get = query({ return { ...organization, + permission: membership.permission, role: membership.role, }; }, @@ -106,7 +108,7 @@ export const update = mutation({ .filter((q) => q.eq(q.field("userId"), userId)) .first(); - if (!membership || membership.role !== "admin") { + if (!membership || membership.permission !== "admin") { throw new Error("Insufficient permissions"); } @@ -122,7 +124,8 @@ export const addMember = mutation({ args: { organizationId: v.id("organizations"), userId: v.id("users"), - role: v.union( + role: v.optional(v.string()), + permission: v.union( v.literal("admin"), v.literal("member"), v.literal("visitor"), @@ -142,7 +145,7 @@ export const addMember = mutation({ .filter((q) => q.eq(q.field("userId"), currentUserId)) .first(); - if (!currentMembership || currentMembership.role !== "admin") { + if (!currentMembership || currentMembership.permission !== "admin") { throw new Error("Insufficient permissions"); } @@ -162,6 +165,7 @@ export const addMember = mutation({ organizationId: args.organizationId, userId: args.userId, role: args.role, + permission: args.permission, joinedAt: Date.now(), }); }, @@ -186,7 +190,7 @@ export const removeMember = mutation({ .filter((q) => q.eq(q.field("userId"), currentUserId)) .first(); - if (!currentMembership || currentMembership.role !== "admin") { + if (!currentMembership || currentMembership.permission !== "admin") { throw new Error("Insufficient permissions"); } diff --git a/packages/convex/src/convex/schema.ts b/packages/convex/src/convex/schema.ts index 71b3ae4..e55f27c 100644 --- a/packages/convex/src/convex/schema.ts +++ b/packages/convex/src/convex/schema.ts @@ -17,7 +17,8 @@ export default defineSchema({ organizationMembers: defineTable({ organizationId: v.id("organizations"), userId: v.id("users"), - role: v.union( + role: v.optional(v.string()), + permission: v.union( v.literal("admin"), v.literal("member"), v.literal("visitor"), From 722467096e2388123e549db364170ce6d5a12f1d Mon Sep 17 00:00:00 2001 From: aster <137767097+aster-void@users.noreply.github.com> Date: Thu, 14 Aug 2025 01:40:57 +0900 Subject: [PATCH 4/7] feat: organization and permission control --- CLAUDE.md | 12 +- README.md | 13 ++ biome.jsonc | 1 - bun.lock | 43 +++++ package.json | 28 +-- packages/client/package.json | 7 +- packages/client/src/app.d.ts | 3 + .../ChatApp.svelte} | 34 ++-- .../{chat => app}/MessageInput.svelte | 19 +- .../{chat => app}/MessageList.svelte | 0 .../{chat => channels}/Channel.svelte | 6 +- .../ChannelList.svelte} | 21 +-- .../channels/CreateChannelButton.svelte | 72 +++++++ .../src/components/chat/ChannelList.svelte | 33 ---- .../client/src/components/chat/ChatApp.svelte | 28 --- .../src/components/example/TaskList.svelte | 2 - .../organization/OrganizationSelector.svelte | 12 +- packages/client/src/icons/mdi-close.svelte | 5 + packages/client/src/lib/modal/modal.svelte | 40 ++++ .../client/src/lib/svelte-robot.svelte.ts | 29 +++ packages/client/src/lib/useMutation.svelte.ts | 35 ++++ packages/client/src/lib/useMutation.ts | 12 -- packages/client/src/routes/+page.svelte | 8 +- .../client/src/routes/chat/[id]/+page.svelte | 17 -- .../src/routes/orgs/[orgId]/+page.svelte | 9 + .../[orgId]/chat/[channelId]/+page.svelte | 12 ++ .../{ => orgs}/[orgId]/settings/+page.svelte | 18 +- .../create => orgs/new}/+page.svelte | 19 +- packages/client/svelte.config.js | 5 +- packages/client/vite.config.ts | 4 + packages/convex/src/convex/channels.ts | 63 ++----- packages/convex/src/convex/messages.ts | 13 ++ packages/convex/src/convex/organizations.ts | 106 +++-------- packages/convex/src/convex/perms.ts | 178 ++++++++++++++++++ .../src/lib/components/ThemeToggle.svelte | 2 +- packages/markdown/src/routes/+page.svelte | 6 +- 36 files changed, 591 insertions(+), 324 deletions(-) rename packages/client/src/components/{organization/OrganizationChatApp.svelte => app/ChatApp.svelte} (77%) rename packages/client/src/components/{chat => app}/MessageInput.svelte (76%) rename packages/client/src/components/{chat => app}/MessageList.svelte (100%) rename packages/client/src/components/{chat => channels}/Channel.svelte (85%) rename packages/client/src/components/{organization/OrganizationChannelList.svelte => channels/ChannelList.svelte} (71%) create mode 100644 packages/client/src/components/channels/CreateChannelButton.svelte delete mode 100644 packages/client/src/components/chat/ChannelList.svelte delete mode 100644 packages/client/src/components/chat/ChatApp.svelte create mode 100644 packages/client/src/icons/mdi-close.svelte create mode 100644 packages/client/src/lib/modal/modal.svelte create mode 100644 packages/client/src/lib/svelte-robot.svelte.ts create mode 100644 packages/client/src/lib/useMutation.svelte.ts delete mode 100644 packages/client/src/lib/useMutation.ts delete mode 100644 packages/client/src/routes/chat/[id]/+page.svelte create mode 100644 packages/client/src/routes/orgs/[orgId]/+page.svelte create mode 100644 packages/client/src/routes/orgs/[orgId]/chat/[channelId]/+page.svelte rename packages/client/src/routes/{ => orgs}/[orgId]/settings/+page.svelte (94%) rename packages/client/src/routes/{organizations/create => orgs/new}/+page.svelte (86%) create mode 100644 packages/convex/src/convex/perms.ts diff --git a/CLAUDE.md b/CLAUDE.md index 6897f0a..45e1425 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -122,15 +122,18 @@ bun paraglide ### Mutations with useMutation -Since `convex-svelte` doesn't export `useMutation`, we have a custom utility at `src/lib/useMutation.ts`: +Since `convex-svelte` doesn't export `useMutation`, we have a custom utility at `src/lib/useMutation.svelte.ts`: ```typescript -import { useMutation } from "~/lib/useMutation"; +import { useMutation } from "~/lib/useMutation.svelte.ts"; const createOrganization = useMutation(api.organizations.create); -// Use like any mutation hook -await createOrganization({ name: "New Org", description: "..." }); +// Use like this +await createOrganization.run({ name: "New Org", description: "..." }); +// which exposes these properties +createOrganization.processing; // boolean, use for button disabled state / loading spinners +createOrganization.error; // string | null, use for error messages ``` ### Backend (Convex) @@ -189,3 +192,4 @@ Tauri conflicts with the web development server and requires more resources for - ✅ REQUIRED: `$effect(() => { ... })` for side effects - Always prefer using DaisyUI classes, and use minimal Tailwind classes. - Separate components into smallest pieces for readability. +- Name snippets with camelCase instead of PascalCase to avoid confusion with components. diff --git a/README.md b/README.md index af5e7b9..293d179 100644 --- a/README.md +++ b/README.md @@ -72,3 +72,16 @@ bun dev:tauri const selectedChannel = useQuery(api.channels.get, { id: selectedChannelId }); ``` + +### (client) Icon の使用について + +- unplugin-icons を使っています。 +- Usage Example: `import MdiClose from "~icons/mdi/close"` +- 現在インストールされているアイコンセットは以下のとおりです: + - mdi (Material Design Icons) +- 新規アイコンセットを追加する場合は、`cd packages/client; bun add @iconify-json/[iconset]` で追加できます。 +- icon の一覧はここで見れます。: https://icones.js.org/ + +### 独自命名規則 + +- Snippet の命名は camelCase で行います。 (PascalCase はコンポーネントと混同されるため) diff --git a/biome.jsonc b/biome.jsonc index 9b3aa33..2c47572 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -26,7 +26,6 @@ "noUnusedTemplateLiteral": "error", "useNumberNamespace": "error", "noInferrableTypes": "error", - "noUselessElse": "error", }, "correctness": { "useImportExtensions": { diff --git a/bun.lock b/bun.lock index b9bef02..365738b 100644 --- a/bun.lock +++ b/bun.lock @@ -20,6 +20,7 @@ "dependencies": { "@auth/core": "^0.40.0", "@convex-dev/auth": "^0.0.87", + "@iconify-json/mdi": "^1.2.3", "@inlang/paraglide-js": "^2.2.0", "@mmailaender/convex-auth-svelte": "^0.0.2", "@packages/convex": "workspace:*", @@ -29,7 +30,9 @@ "@tauri-apps/plugin-opener": "^2.4.0", "convex": "^1.25.4", "convex-svelte": "^0.0.11", + "robot3": "^1.1.1", "runed": "^0.31.0", + "unplugin-icons": "^22.2.0", }, "devDependencies": { "@chromatic-com/storybook": "^4.0.1", @@ -121,6 +124,10 @@ "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], + + "@antfu/utils": ["@antfu/utils@8.1.1", "", {}, "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ=="], + "@auth/core": ["@auth/core@0.40.0", "", { "dependencies": { "@panva/hkdf": "^1.2.1", "jose": "^6.0.6", "oauth4webapi": "^3.3.0", "preact": "10.24.3", "preact-render-to-string": "6.5.11" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "nodemailer": "^6.8.0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-n53uJE0RH5SqZ7N1xZoMKekbHfQgjd0sAEyUbE+IYJnmuQkbvuZnXItCU7d+i7Fj8VGOgqvNO7Mw4YfBTlZeQw=="], "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], @@ -237,6 +244,12 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], + "@iconify-json/mdi": ["@iconify-json/mdi@1.2.3", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg=="], + + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@iconify/utils": ["@iconify/utils@2.3.0", "", { "dependencies": { "@antfu/install-pkg": "^1.0.0", "@antfu/utils": "^8.1.0", "@iconify/types": "^2.0.0", "debug": "^4.4.0", "globals": "^15.14.0", "kolorist": "^1.8.0", "local-pkg": "^1.0.0", "mlly": "^1.7.4" } }, "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA=="], + "@inlang/paraglide-js": ["@inlang/paraglide-js@2.2.0", "", { "dependencies": { "@inlang/recommend-sherlock": "0.2.1", "@inlang/sdk": "2.4.9", "commander": "11.1.0", "consola": "3.4.0", "json5": "2.2.3", "unplugin": "^2.1.2", "urlpattern-polyfill": "^10.0.0" }, "bin": { "paraglide-js": "bin/run.js" } }, "sha512-pkpXu1LanvpcAbvpVPf7PgF11Uq7DliSEBngrcUN36l4ZOOpzn3QBTvVr/tJxvks0O67WseQgiMHet8KH7Oz5A=="], "@inlang/recommend-sherlock": ["@inlang/recommend-sherlock@0.2.1", "", { "dependencies": { "comment-json": "^4.2.3" } }, "sha512-ckv8HvHy/iTqaVAEKrr+gnl+p3XFNwe5D2+6w6wJk2ORV2XkcRkKOJ/XsTUJbPSiyi4PI+p+T3bqbmNx/rDUlg=="], @@ -611,6 +624,8 @@ "comment-json": ["comment-json@4.2.5", "", { "dependencies": { "array-timsort": "^1.0.3", "core-util-is": "^1.0.3", "esprima": "^4.0.1", "has-own-prop": "^2.0.0", "repeat-string": "^1.6.1" } }, "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw=="], + "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], + "consola": ["consola@3.4.0", "", {}, "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA=="], "convex": ["convex@1.25.4", "", { "dependencies": { "esbuild": "0.25.4", "jwt-decode": "^4.0.0", "prettier": "3.5.3" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react"], "bin": { "convex": "bin/main.js" } }, "sha512-LiGZZTmbe5iHWwDOYfSA00w+uDM8kgLC0ohFJW0VgQlKcs8famHCE6yuplk4wwXyj9Lhb1+yMRfrAD2ZEquqHg=="], @@ -685,6 +700,8 @@ "expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="], + "exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="], + "fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="], "fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], @@ -699,6 +716,8 @@ "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + "globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "happy-dom": ["happy-dom@18.0.1", "", { "dependencies": { "@types/node": "^20.0.0", "@types/whatwg-mimetype": "^3.0.2", "whatwg-mimetype": "^3.0.0" } }, "sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA=="], @@ -755,6 +774,8 @@ "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], + "kysely": ["kysely@0.27.6", "", {}, "sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ=="], "leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="], @@ -803,6 +824,8 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], + "local-pkg": ["local-pkg@1.1.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.0.1", "quansync": "^0.2.8" } }, "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg=="], + "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], @@ -837,6 +860,8 @@ "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + "mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="], + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], @@ -855,6 +880,8 @@ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="], + "parseley": ["parseley@0.12.1", "", { "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" } }, "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw=="], "pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="], @@ -875,6 +902,8 @@ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "pkg-types": ["pkg-types@2.2.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ=="], + "playwright": ["playwright@1.54.1", "", { "dependencies": { "playwright-core": "1.54.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g=="], "playwright-core": ["playwright-core@1.54.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA=="], @@ -895,6 +924,8 @@ "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + "quansync": ["quansync@0.2.10", "", {}, "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A=="], + "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], @@ -913,6 +944,8 @@ "resend": ["resend@4.7.0", "", { "dependencies": { "@react-email/render": "1.1.2" } }, "sha512-30IbXGBUbmDweQH2IlO53XOXX7ndjYV9xFZ8IEBiWqefqQ/qmTsgrX0Ab6MUnmobJXbpdReVv+iXGRQPubQL5Q=="], + "robot3": ["robot3@1.1.1", "", {}, "sha512-kuD0oQg2KUE74FCQ1a5uoRsEJ/bUKrU1D3vnluop9X7LSiGLndejQgjUEcMqJMVzUA836HSXhtY7XNtQiPTCLQ=="], + "rollup": ["rollup@4.45.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.45.1", "@rollup/rollup-android-arm64": "4.45.1", "@rollup/rollup-darwin-arm64": "4.45.1", "@rollup/rollup-darwin-x64": "4.45.1", "@rollup/rollup-freebsd-arm64": "4.45.1", "@rollup/rollup-freebsd-x64": "4.45.1", "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", "@rollup/rollup-linux-arm-musleabihf": "4.45.1", "@rollup/rollup-linux-arm64-gnu": "4.45.1", "@rollup/rollup-linux-arm64-musl": "4.45.1", "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-musl": "4.45.1", "@rollup/rollup-linux-s390x-gnu": "4.45.1", "@rollup/rollup-linux-x64-gnu": "4.45.1", "@rollup/rollup-linux-x64-musl": "4.45.1", "@rollup/rollup-win32-arm64-msvc": "4.45.1", "@rollup/rollup-win32-ia32-msvc": "4.45.1", "@rollup/rollup-win32-x64-msvc": "4.45.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw=="], "runed": ["runed@0.31.1", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-v3czcTnO+EJjiPvD4dwIqfTdHLZ8oH0zJheKqAHh9QMViY7Qb29UlAMRpX7ZtHh7AFqV60KmfxaJ9QMy+L1igQ=="], @@ -1009,12 +1042,16 @@ "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], "unplugin": ["unplugin@2.3.5", "", { "dependencies": { "acorn": "^8.14.1", "picomatch": "^4.0.2", "webpack-virtual-modules": "^0.6.2" } }, "sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw=="], + "unplugin-icons": ["unplugin-icons@22.2.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/utils": "^2.3.0", "debug": "^4.4.1", "local-pkg": "^1.1.1", "unplugin": "^2.3.5" }, "peerDependencies": { "@svgr/core": ">=7.0.0", "@svgx/core": "^1.0.1", "@vue/compiler-sfc": "^3.0.2 || ^2.7.0", "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0", "vue-template-compiler": "^2.6.12", "vue-template-es2015-compiler": "^1.9.0" }, "optionalPeers": ["@svgr/core", "@svgx/core", "@vue/compiler-sfc", "svelte", "vue-template-compiler", "vue-template-es2015-compiler"] }, "sha512-OdrXCiXexC1rFd0QpliAgcd4cMEEEQtoCf2WIrRIGu4iW6auBPpQKMCBeWxoe55phYdRyZLUWNOtzyTX+HOFSA=="], + "urlpattern-polyfill": ["urlpattern-polyfill@10.1.0", "", {}, "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw=="], "uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], @@ -1047,6 +1084,8 @@ "zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="], + "@antfu/install-pkg/tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="], + "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "@convex-dev/auth/cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], @@ -1081,6 +1120,8 @@ "convex/prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], + "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], "pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -1107,6 +1148,8 @@ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], diff --git a/package.json b/package.json index 4e419ed..d813b59 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,16 @@ { "name": "prism", - "type": "module", + "devDependencies": { + "@biomejs/biome": "^2.1.2", + "lefthook": "^1.12.2", + "prettier": "^3.6.2", + "prettier-plugin-svelte": "^3.4.0", + "prettier-plugin-tailwindcss": "^0.6.14" + }, + "peerDependencies": { + "typescript": "^5.8.3" + }, "private": true, - "workspaces": [ - "packages/*" - ], "scripts": { "dev": "bun run --filter='@packages/{client,convex}' dev", "dev:all": "(trap 'kill 0' EXIT; bun run dev:convex & bun run dev:web & bun run dev:storybook & wait", @@ -24,14 +30,8 @@ "convex": "cd packages/convex && bun run convex", "paraglide": "cd packages/client && bun run paraglide" }, - "peerDependencies": { - "typescript": "^5.8.3" - }, - "devDependencies": { - "@biomejs/biome": "^2.1.2", - "lefthook": "^1.12.2", - "prettier": "^3.6.2", - "prettier-plugin-svelte": "^3.4.0", - "prettier-plugin-tailwindcss": "^0.6.14" - } + "type": "module", + "workspaces": [ + "packages/*" + ] } diff --git a/packages/client/package.json b/packages/client/package.json index 4af5bd4..dedcc02 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -41,15 +41,18 @@ "dependencies": { "@auth/core": "^0.40.0", "@convex-dev/auth": "^0.0.87", + "@iconify-json/mdi": "^1.2.3", + "@inlang/paraglide-js": "^2.2.0", "@mmailaender/convex-auth-svelte": "^0.0.2", "@packages/convex": "workspace:*", - "@inlang/paraglide-js": "^2.2.0", "@playwright/test": "^1.54.1", "@sveltejs/adapter-static": "^3.0.8", "@tauri-apps/api": "^2.6.0", "@tauri-apps/plugin-opener": "^2.4.0", "convex": "^1.25.4", "convex-svelte": "^0.0.11", - "runed": "^0.31.0" + "robot3": "^1.1.1", + "runed": "^0.31.0", + "unplugin-icons": "^22.2.0" } } diff --git a/packages/client/src/app.d.ts b/packages/client/src/app.d.ts index 520c421..989a13c 100644 --- a/packages/client/src/app.d.ts +++ b/packages/client/src/app.d.ts @@ -1,3 +1,6 @@ +/// +/// + // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { diff --git a/packages/client/src/components/organization/OrganizationChatApp.svelte b/packages/client/src/components/app/ChatApp.svelte similarity index 77% rename from packages/client/src/components/organization/OrganizationChatApp.svelte rename to packages/client/src/components/app/ChatApp.svelte index 5db947a..0b61ed0 100644 --- a/packages/client/src/components/organization/OrganizationChatApp.svelte +++ b/packages/client/src/components/app/ChatApp.svelte @@ -1,17 +1,16 @@

    チャンネル

    - +
    diff --git a/packages/client/src/components/channels/CreateChannelButton.svelte b/packages/client/src/components/channels/CreateChannelButton.svelte new file mode 100644 index 0000000..d86d28c --- /dev/null +++ b/packages/client/src/components/channels/CreateChannelButton.svelte @@ -0,0 +1,72 @@ + + + + + + +{#snippet createChannelModalContent()} +
    + + {#if disabled} + + {:else} + + {/if} +
    +{/snippet} diff --git a/packages/client/src/components/chat/ChannelList.svelte b/packages/client/src/components/chat/ChannelList.svelte deleted file mode 100644 index 2866d23..0000000 --- a/packages/client/src/components/chat/ChannelList.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - -
    -
    -

    チャンネル

    - -
    - -
    -
    - この機能は廃止されました。OrganizationChannelListを使用してください。 -
    -
    -
    diff --git a/packages/client/src/components/chat/ChatApp.svelte b/packages/client/src/components/chat/ChatApp.svelte deleted file mode 100644 index 870e977..0000000 --- a/packages/client/src/components/chat/ChatApp.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - -
    - - -
    - {#if selectedChannelId} - - {:else} -
    -
    -

    - チャットアプリへようこそ -

    -

    - 左からチャンネルを選択して会話を始めましょう -

    -
    -
    - {/if} -
    -
    diff --git a/packages/client/src/components/example/TaskList.svelte b/packages/client/src/components/example/TaskList.svelte index 36d9b00..bf91d77 100644 --- a/packages/client/src/components/example/TaskList.svelte +++ b/packages/client/src/components/example/TaskList.svelte @@ -7,9 +7,7 @@ const todos = useQuery(api.tasks.get, () => ({})); async function updateTodo(id: Id<"tasks">, data: Partial) { - console.log("mutation start"); await convex.mutation(api.tasks.update, { id, ...data }); - console.log("mutation end"); } async function createTodo() { await convex.mutation(api.tasks.create, { text: "", assigner: "" }); diff --git a/packages/client/src/components/organization/OrganizationSelector.svelte b/packages/client/src/components/organization/OrganizationSelector.svelte index 3cb117e..9c3dc1a 100644 --- a/packages/client/src/components/organization/OrganizationSelector.svelte +++ b/packages/client/src/components/organization/OrganizationSelector.svelte @@ -3,10 +3,10 @@ import { useQuery } from "convex-svelte"; interface Props { - onSelect: (organizationId: Id<"organizations">) => void; + onselect: (organizationId: Id<"organizations">) => void; } - const { onSelect }: Props = $props(); + const { onselect }: Props = $props(); const organizations = useQuery(api.organizations.list, () => ({})); @@ -25,7 +25,7 @@ {#each organizations.data as org} + + {/if} + diff --git a/packages/client/src/lib/svelte-robot.svelte.ts b/packages/client/src/lib/svelte-robot.svelte.ts new file mode 100644 index 0000000..12318a1 --- /dev/null +++ b/packages/client/src/lib/svelte-robot.svelte.ts @@ -0,0 +1,29 @@ +import { interpret, type Machine, type MachineStates } from "robot3"; +export function useMachine< + S extends MachineStates, + C extends {}, + K extends string, + F extends string, +>(machine: Machine, context: C) { + let service = $state( + interpret( + machine, + (tx) => { + service = tx; + }, + context, + ), + ); + + return { + get machine() { + return service.machine; + }, + get context() { + return service.context; + }, + send(...args: Parameters) { + service.send(...args); + }, + }; +} diff --git a/packages/client/src/lib/useMutation.svelte.ts b/packages/client/src/lib/useMutation.svelte.ts new file mode 100644 index 0000000..a5f9e97 --- /dev/null +++ b/packages/client/src/lib/useMutation.svelte.ts @@ -0,0 +1,35 @@ +import type { FunctionReference, OptionalRestArgs } from "convex/server"; +import { useConvexClient } from "convex-svelte"; + +export function useMutation>( + mutationFunction: T, +) { + const convex = useConvexClient(); + + let processing = $state(false); + let error = $state(null); + + return { + run: async (args: OptionalRestArgs[0]) => { + if (processing) return; // Prevent multiple runs at the same time + processing = true; + error = null; + try { + console.log("running mutation..."); + return await convex.mutation(mutationFunction, args); + } catch (e) { + console.log("mutation failed:", e); + error = e instanceof Error ? e.message : String(e); + } finally { + console.log("mutation finished"); + processing = false; + } + }, + get processing() { + return processing; + }, + get error() { + return error; + }, + }; +} diff --git a/packages/client/src/lib/useMutation.ts b/packages/client/src/lib/useMutation.ts deleted file mode 100644 index e07b1ec..0000000 --- a/packages/client/src/lib/useMutation.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { FunctionReference, OptionalRestArgs } from "convex/server"; -import { useConvexClient } from "convex-svelte"; - -export function useMutation>( - mutationFunction: T, -) { - const convex = useConvexClient(); - - return async (...args: OptionalRestArgs) => { - return await convex.mutation(mutationFunction, ...args); - }; -} diff --git a/packages/client/src/routes/+page.svelte b/packages/client/src/routes/+page.svelte index 784c810..bab5fe5 100644 --- a/packages/client/src/routes/+page.svelte +++ b/packages/client/src/routes/+page.svelte @@ -1,13 +1,13 @@ @@ -16,7 +16,7 @@
    {:else if auth.isAuthenticated} - + {:else}
    diff --git a/packages/client/src/routes/chat/[id]/+page.svelte b/packages/client/src/routes/chat/[id]/+page.svelte deleted file mode 100644 index cc68180..0000000 --- a/packages/client/src/routes/chat/[id]/+page.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/packages/client/src/routes/orgs/[orgId]/+page.svelte b/packages/client/src/routes/orgs/[orgId]/+page.svelte new file mode 100644 index 0000000..47437e0 --- /dev/null +++ b/packages/client/src/routes/orgs/[orgId]/+page.svelte @@ -0,0 +1,9 @@ + + + diff --git a/packages/client/src/routes/orgs/[orgId]/chat/[channelId]/+page.svelte b/packages/client/src/routes/orgs/[orgId]/chat/[channelId]/+page.svelte new file mode 100644 index 0000000..12225ac --- /dev/null +++ b/packages/client/src/routes/orgs/[orgId]/chat/[channelId]/+page.svelte @@ -0,0 +1,12 @@ + + +} + channelId={channelId as Id<"channels">} +/> diff --git a/packages/client/src/routes/[orgId]/settings/+page.svelte b/packages/client/src/routes/orgs/[orgId]/settings/+page.svelte similarity index 94% rename from packages/client/src/routes/[orgId]/settings/+page.svelte rename to packages/client/src/routes/orgs/[orgId]/settings/+page.svelte index adc0d60..2e2cbc3 100644 --- a/packages/client/src/routes/[orgId]/settings/+page.svelte +++ b/packages/client/src/routes/orgs/[orgId]/settings/+page.svelte @@ -1,9 +1,8 @@
    - + + ← 戻る + {#if organization.data}
    diff --git a/packages/client/src/routes/organizations/create/+page.svelte b/packages/client/src/routes/orgs/new/+page.svelte similarity index 86% rename from packages/client/src/routes/organizations/create/+page.svelte rename to packages/client/src/routes/orgs/new/+page.svelte index bbdc069..676d076 100644 --- a/packages/client/src/routes/organizations/create/+page.svelte +++ b/packages/client/src/routes/orgs/new/+page.svelte @@ -1,7 +1,7 @@