diff --git a/apps/dashboard/src/@/api/team.ts b/apps/dashboard/src/@/api/team.ts index f0249b77f45..1759bb1624d 100644 --- a/apps/dashboard/src/@/api/team.ts +++ b/apps/dashboard/src/@/api/team.ts @@ -9,12 +9,12 @@ export type Team = { slug: string; createdAt: string; updatedAt: string; - deletedAt: string | null; - bannedAt: string | null; - // image: string; // -> TODO + deletedAt?: string; + bannedAt?: string; + image?: string; billingPlan: "pro" | "growth" | "free"; billingStatus: "validPayment" | (string & {}); // what's the other value? - // billingEmail: string; + billingEmail: string; // billingExternalId: string; // billingType: "STRIPE" | ?? // billingCustomerPayload: ?? | null diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/GeneralSettingsPage.stories.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/GeneralSettingsPage.stories.tsx index 7638a60ada7..1173b522a34 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/GeneralSettingsPage.stories.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/GeneralSettingsPage.stories.tsx @@ -38,10 +38,9 @@ const testTeam: Team = { slug: "team-slug-foo-bar", createdAt: "2023-07-07T19:21:33.604Z", updatedAt: "2024-07-11T00:01:02.241Z", - deletedAt: null, - bannedAt: null, billingStatus: "validPayment", billingPlan: "free", + billingEmail: "foo@example.com", }; function Story() { @@ -52,6 +51,10 @@ function Story() { updateTeamImage={async () => { await new Promise((resolve) => setTimeout(resolve, 1000)); }} + updateTeamField={async (value) => { + console.log(value); + await new Promise((resolve) => setTimeout(resolve, 1000)); + }} /> diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPage.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPage.tsx index 53e4d5fedbc..ab6885c0dd9 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPage.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPage.tsx @@ -1,32 +1,51 @@ "use client"; import type { Team } from "@/api/team"; -import type { ThirdwebClient } from "thirdweb"; +import { getThirdwebClient } from "@/constants/thirdweb.server"; +import { useDashboardRouter } from "@/lib/DashboardRouter"; import { upload } from "thirdweb/storage"; import { TeamGeneralSettingsPageUI } from "./TeamGeneralSettingsPageUI"; +import { updateTeam } from "./updateTeam"; export function TeamGeneralSettingsPage(props: { team: Team; - client: ThirdwebClient; + authToken: string; }) { + const router = useDashboardRouter(); + return ( { + await updateTeam({ + teamId: props.team.id, + value: teamValue, + }); + + // Current page's slug is updated + if (teamValue.slug) { + router.replace(`/team/${teamValue.slug}/~/settings`); + } else { + router.refresh(); + } + }} updateTeamImage={async (file) => { + let uri: string | undefined = undefined; + if (file) { // upload to IPFS - const uri = await upload({ - client: props.client, + uri = await upload({ + client: getThirdwebClient(props.authToken), files: [file], }); - - // TODO - Implement updating the account image with uri - console.log(uri); - } else { - // TODO - Implement deleting the account image } - throw new Error("Not implemented"); + await updateTeam({ + teamId: props.team.id, + value: { + image: uri, + }, + }); }} /> ); diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPageUI.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPageUI.tsx index 41b7a3bb681..6639e6492f3 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPageUI.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPageUI.tsx @@ -5,22 +5,37 @@ import { DangerSettingCard } from "@/components/blocks/DangerSettingCard"; import { SettingsCard } from "@/components/blocks/SettingsCard"; import { CopyTextButton } from "@/components/ui/CopyTextButton"; import { Input } from "@/components/ui/input"; +import { useThirdwebClient } from "@/constants/thirdweb.client"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { useMutation } from "@tanstack/react-query"; import { FileInput } from "components/shared/FileInput"; import { useState } from "react"; import { toast } from "sonner"; +import { resolveScheme } from "thirdweb/storage"; + +type UpdateTeamField = (team: Partial) => Promise; export function TeamGeneralSettingsPageUI(props: { team: Team; updateTeamImage: (file: File | undefined) => Promise; + updateTeamField: UpdateTeamField; }) { const hasPermissionToDelete = false; // TODO return (
- - - + + + {/* THIS IS NOT WORKING - CAN"T UPDATE IMAGE */} + { - await new Promise((resolve) => setTimeout(resolve, 3000)); - console.log("Updating team name to", teamName); - throw new Error("Not implemented"); - }, + mutationFn: (name: string) => props.updateTeamField({ name }), }); function handleSave() { @@ -82,20 +93,14 @@ function TeamNameFormControl(props: { function TeamSlugFormControl(props: { team: Team; + updateTeamField: (team: Partial) => Promise; }) { const [teamSlug, setTeamSlug] = useState(props.team.slug); const [isTeamTaken] = useState(false); const maxTeamURLLength = 48; - // TODO - implement const updateTeamMutation = useMutation({ - mutationFn: async (_slug: string) => { - // set isTeamTaken to true if team URL is taken - // Fake loading - await new Promise((resolve) => setTimeout(resolve, 3000)); - console.log("Updating team slug to", _slug); - throw new Error("Not implemented"); - }, + mutationFn: (slug: string) => props.updateTeamField({ slug: slug }), }); function handleSave() { @@ -144,8 +149,17 @@ function TeamSlugFormControl(props: { function TeamAvatarFormControl(props: { updateTeamImage: (file: File | undefined) => Promise; + avatar: string | undefined; }) { - const [teamAvatar, setTeamAvatar] = useState(); // TODO: prefill with team avatar + const client = useThirdwebClient(); + const teamUrl = props.avatar + ? resolveScheme({ + client: client, + uri: props.avatar, + }) + : undefined; + + const [teamAvatar, setTeamAvatar] = useState(); const updateTeamAvatarMutation = useMutation({ mutationFn: async (_avatar: File | undefined) => { @@ -186,6 +200,7 @@ function TeamAvatarFormControl(props: { setValue={setTeamAvatar} className="w-20 rounded-full lg:w-28" disableHelperText + fileUrl={teamUrl} />
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/updateTeam.ts b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/updateTeam.ts new file mode 100644 index 00000000000..5d2935b082e --- /dev/null +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/updateTeam.ts @@ -0,0 +1,29 @@ +"use server"; + +import type { Team } from "@/api/team"; +import { API_SERVER_URL } from "@/constants/env"; +import { getAuthToken } from "../../../../../../api/lib/getAuthToken"; + +export async function updateTeam(params: { + teamId: string; + value: Partial; +}) { + const authToken = getAuthToken(); + + if (!authToken) { + throw new Error("No auth token"); + } + + const res = await fetch(`${API_SERVER_URL}/v1/teams/${params.teamId}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${authToken}`, + }, + body: JSON.stringify(params.value), + }); + + if (!res.ok) { + throw new Error("failed to update team"); + } +} diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/members/TeamMembersSettingsPage.stories.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/members/TeamMembersSettingsPage.stories.tsx index 0ba312b85ef..3dbde02de80 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/members/TeamMembersSettingsPage.stories.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/members/TeamMembersSettingsPage.stories.tsx @@ -40,10 +40,9 @@ const freeTeam: Team = { slug: "team-slug-foo-bar", createdAt: "2023-07-07T19:21:33.604Z", updatedAt: "2024-07-11T00:01:02.241Z", - deletedAt: null, - bannedAt: null, billingStatus: "validPayment", billingPlan: "free", + billingEmail: "foo@example.com", }; const proTeam: Team = { @@ -52,10 +51,9 @@ const proTeam: Team = { slug: "team-slug-foo-bar", createdAt: "2023-07-07T19:21:33.604Z", updatedAt: "2024-07-11T00:01:02.241Z", - deletedAt: null, - bannedAt: null, billingStatus: "validPayment", billingPlan: "pro", + billingEmail: "foo@example.com", }; function createMemberStub(id: string, role: TeamAccountRole): TeamMember { diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/page.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/page.tsx index 6b9e1ba84a0..923626aa546 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/page.tsx @@ -1,5 +1,4 @@ import { getTeamBySlug } from "@/api/team"; -import { getThirdwebClient } from "@/constants/thirdweb.server"; import { notFound } from "next/navigation"; import { getAuthToken } from "../../../../../api/lib/getAuthToken"; import { TeamGeneralSettingsPage } from "./general/TeamGeneralSettingsPage"; @@ -15,7 +14,5 @@ export default async function Page(props: { notFound(); } - return ( - - ); + return ; } diff --git a/apps/dashboard/src/components/shared/FileInput.tsx b/apps/dashboard/src/components/shared/FileInput.tsx index d973b46716d..4ecccc04bb3 100644 --- a/apps/dashboard/src/components/shared/FileInput.tsx +++ b/apps/dashboard/src/components/shared/FileInput.tsx @@ -28,6 +28,7 @@ interface IFileInputProps { children?: React.ReactNode; className?: string; disableHelperText?: boolean; + fileUrl?: string; } export const FileInput: React.FC = ({ @@ -45,6 +46,7 @@ export const FileInput: React.FC = ({ className, previewMaxWidth, disableHelperText, + fileUrl: fileUrlFallback, }) => { const onDrop = useCallback< ( @@ -67,7 +69,7 @@ export const FileInput: React.FC = ({ const file: File | null = typeof window !== "undefined" && value instanceof File ? value : null; - const fileUrl = useImageFileOrUrl(value); + const fileUrl = useImageFileOrUrl(value) || fileUrlFallback || ""; const helperTextOrFile = helperText ? helperText diff --git a/apps/dashboard/src/stories/stubs.ts b/apps/dashboard/src/stories/stubs.ts index b5c3519c8b5..52cbb3a8059 100644 --- a/apps/dashboard/src/stories/stubs.ts +++ b/apps/dashboard/src/stories/stubs.ts @@ -45,10 +45,9 @@ export function teamStub( billingStatus: "validPayment", name: `Team ${id}`, slug: `team-${id}`, - bannedAt: null, createdAt: new Date().toISOString(), - deletedAt: null, updatedAt: new Date().toISOString(), + billingEmail: "foo@example.com", }; return team;