diff --git a/packages/client/src/components/app/ChatApp.svelte b/packages/client/src/components/app/ChatApp.svelte index 2db66f0..4194cf4 100644 --- a/packages/client/src/components/app/ChatApp.svelte +++ b/packages/client/src/components/app/ChatApp.svelte @@ -4,13 +4,18 @@ import { goto } from "$app/navigation"; import Channel from "../channels/Channel.svelte"; import ChannelList from "../channels/ChannelList.svelte"; + import Personalization from "../chat/Personalization.svelte"; + + type Selection = + | { type: "chat"; selectedChannelId: Id<"channels"> | undefined } + | { type: "personalization"; selectedChannelId: undefined }; interface Props { organizationId: Id<"organizations">; - channelId?: Id<"channels">; + screenMode: Selection; } - const { organizationId, channelId }: Props = $props(); + const { organizationId, screenMode }: Props = $props(); const organization = useQuery(api.organizations.get, () => ({ id: organizationId, @@ -18,7 +23,7 @@
-
+
@@ -74,29 +79,42 @@ channelId, - (id) => { - goto(`/orgs/${organizationId}/chat/${id}`); + bind:screenMode={ + () => screenMode, + (screenMode) => { + if (screenMode.type === "chat") { + goto( + `/orgs/${organizationId}/chat/${screenMode.selectedChannelId}`, + ); + } else if (screenMode.type === "personalization") { + goto(`/orgs/${organizationId}/personalization`); + } } } />
- {#if channelId} - - {:else} -
-
-

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

-

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

+ {#if screenMode.type == "chat"} + {#if screenMode.selectedChannelId} + + {:else} +
+
+

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

+

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

+
-
+ {/if} + {:else if screenMode.type == "personalization"} + {/if}
diff --git a/packages/client/src/components/channels/ChannelList.svelte b/packages/client/src/components/channels/ChannelList.svelte index e833c66..d5a3331 100644 --- a/packages/client/src/components/channels/ChannelList.svelte +++ b/packages/client/src/components/channels/ChannelList.svelte @@ -3,12 +3,25 @@ import { useQuery } from "convex-svelte"; import CreateChannelButton from "./CreateChannelButton.svelte"; + type Selection = + | { type: "chat"; selectedChannelId: Id<"channels"> | undefined } + | { type: "personalization"; selectedChannelId: undefined }; + + /*<<<<<<< HEAD:packages/client/src/components/chat/ChannelList.svelte + screenMode: Selection; + } + + let { + screenMode = $bindable(), + }: Props = $props(); +======= +*/ interface Props { organizationId: Id<"organizations">; - selectedChannelId?: Id<"channels">; + screenMode: Selection; } - let { organizationId, selectedChannelId = $bindable() }: Props = $props(); + let { organizationId, screenMode = $bindable() }: Props = $props(); const channels = useQuery(api.channels.list, () => ({ organizationId, @@ -27,11 +40,13 @@
{/if}
+
diff --git a/packages/client/src/components/chat/Personalization.svelte b/packages/client/src/components/chat/Personalization.svelte new file mode 100644 index 0000000..98718d3 --- /dev/null +++ b/packages/client/src/components/chat/Personalization.svelte @@ -0,0 +1,132 @@ + + +

アイコンの変更

+
+
+

変更前

+ googleアイコン +
+ {#if changedImage} +
+

変更後

+ 変更後 +
+ {/if} +
+ { + const input = event.target as HTMLInputElement; + const file = input.files?.[0]; + if (file) { + changedImage = URL.createObjectURL(file); + changedImageFile = file; + } + }} +/> +

名前の変更

+
+
+

変更前

+

{userName}

+
+
+

変更後

+ +
+
+ + diff --git a/packages/client/src/components/organization/OrganizationSelector.svelte b/packages/client/src/components/organization/OrganizationSelector.svelte index 9c3dc1a..776b34c 100644 --- a/packages/client/src/components/organization/OrganizationSelector.svelte +++ b/packages/client/src/components/organization/OrganizationSelector.svelte @@ -54,9 +54,7 @@ {#if organizations.data && organizations.data.length === 0}

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

- - 新しい組織を作成 - + 新しい組織を作成
{/if}
diff --git a/packages/client/src/routes/orgs/[orgId]/+page.svelte b/packages/client/src/routes/orgs/[orgId]/+page.svelte index 47437e0..70162ea 100644 --- a/packages/client/src/routes/orgs/[orgId]/+page.svelte +++ b/packages/client/src/routes/orgs/[orgId]/+page.svelte @@ -6,4 +6,7 @@ const orgId = $derived(page.params.orgId as Id<"organizations">); - + diff --git a/packages/client/src/routes/orgs/[orgId]/chat/[channelId]/+page.svelte b/packages/client/src/routes/orgs/[orgId]/chat/[channelId]/+page.svelte index 12225ac..e9e55a2 100644 --- a/packages/client/src/routes/orgs/[orgId]/chat/[channelId]/+page.svelte +++ b/packages/client/src/routes/orgs/[orgId]/chat/[channelId]/+page.svelte @@ -8,5 +8,5 @@ } - channelId={channelId as Id<"channels">} + screenMode={{ type: "chat", selectedChannelId: channelId as Id<"channels"> }} /> diff --git a/packages/client/src/routes/orgs/[orgId]/personalization/+page.svelte b/packages/client/src/routes/orgs/[orgId]/personalization/+page.svelte new file mode 100644 index 0000000..90afbd0 --- /dev/null +++ b/packages/client/src/routes/orgs/[orgId]/personalization/+page.svelte @@ -0,0 +1,12 @@ + + +} + screenMode={{ type: "personalization", selectedChannelId: undefined }} +/> diff --git a/packages/convex/src/convex/personalization.ts b/packages/convex/src/convex/personalization.ts new file mode 100644 index 0000000..96384b0 --- /dev/null +++ b/packages/convex/src/convex/personalization.ts @@ -0,0 +1,96 @@ +import { getAuthUserId } from "@convex-dev/auth/server"; +import { v } from "convex/values"; +import { mutation, query } from "./_generated/server"; + +export const save = mutation({ + args: { + nickName: v.string(), + organizationId: v.id("organizations"), + }, + handler: async (ctx, args): Promise => { + const userId = await getAuthUserId(ctx); + if (userId) { + const data = await ctx.db + .query("personalization") + .filter((q) => q.eq(q.field("userId"), userId)) + .filter((q) => q.eq(q.field("organizationId"), args.organizationId)) + .unique(); + + if (data) { + await ctx.db.patch(data._id, { + nickname: args.nickName, + }); + } else { + await ctx.db.insert("personalization", { + userId: userId, + organizationId: args.organizationId, + nickname: args.nickName, + icon: null, + }); + } + } + }, +}); + +export const generateUploadUrl = mutation({ + handler: async (ctx) => { + return await ctx.storage.generateUploadUrl(); + }, +}); + +export const saveImage = mutation({ + args: { + icon: v.id("_storage"), + organizationId: v.id("organizations"), + }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + if (userId) { + const data = await ctx.db + .query("personalization") + .filter((q) => q.eq(q.field("userId"), userId)) + .filter((q) => q.eq(q.field("organizationId"), args.organizationId)) + .unique(); + + if (data) { + if (data.icon) { + await ctx.storage.delete(data.icon); + } + await ctx.db.patch(data._id, { + icon: args.icon, + }); + } else { + await ctx.db.insert("personalization", { + userId: userId, + nickname: "", + organizationId: args.organizationId, + icon: args.icon, + }); + } + } + }, +}); + +export const getPersonalization = query({ + args: { organizationId: v.id("organizations") }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + if (userId) { + const user = await ctx.db + .query("personalization") + .filter((q) => q.eq(q.field("userId"), userId)) + .filter((q) => q.eq(q.field("organizationId"), args.organizationId)) + .unique(); + if (user) { + return await ctx.db.get(user._id); + } + } + }, +}); + +export const getImageUrl = mutation({ + args: { storageId: v.id("_storage") }, + handler: async (ctx, args) => { + return await ctx.storage.getUrl(args.storageId); + }, +}); diff --git a/packages/convex/src/convex/schema.ts b/packages/convex/src/convex/schema.ts index d4391d2..11e4212 100644 --- a/packages/convex/src/convex/schema.ts +++ b/packages/convex/src/convex/schema.ts @@ -42,6 +42,12 @@ export default defineSchema({ // 添付ファイル attachments: v.optional(v.array(v.id("files"))), }).index("by_channel", ["channelId"]), + personalization: defineTable({ + userId: v.id("users"), + organizationId: v.id("organizations"), + nickname: v.string(), + icon: v.union(v.id("_storage"), v.null()), + }), files: defineTable({ // Convex Storage ID storageId: v.string(),