diff --git a/packages/admin-next/dashboard/src/hooks/api/auth.tsx b/packages/admin-next/dashboard/src/hooks/api/auth.tsx index b149c07bca..29271daf3b 100644 --- a/packages/admin-next/dashboard/src/hooks/api/auth.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/auth.tsx @@ -21,3 +21,15 @@ export const useLogout = (options?: UseMutationOptions) => { ...options, }) } + +export const useCreateAuthUser = ( + options?: UseMutationOptions<{ token: string }, Error, EmailPassReq> +) => { + return useMutation({ + mutationFn: (payload) => sdk.auth.create("admin", "emailpass", payload), + onSuccess: async (data, variables, context) => { + options?.onSuccess?.(data, variables, context) + }, + ...options, + }) +} diff --git a/packages/admin-next/dashboard/src/hooks/api/invites.tsx b/packages/admin-next/dashboard/src/hooks/api/invites.tsx index 9058b9f558..661ee58e7c 100644 --- a/packages/admin-next/dashboard/src/hooks/api/invites.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/invites.tsx @@ -5,15 +5,15 @@ import { useMutation, useQuery, } from "@tanstack/react-query" -import { client } from "../../lib/client" +import { sdk } from "../../lib/client" import { queryClient } from "../../lib/medusa" import { queryKeysFactory } from "../../lib/query-key-factory" -import { CreateInviteReq } from "../../types/api-payloads" import { - InviteDeleteRes, - InviteListRes, - InviteRes, -} from "../../types/api-responses" + AdminInviteResponse, + DeleteResponse, + HttpTypes, + PaginatedResponse, +} from "@medusajs/types" const INVITES_QUERY_KEY = "invites" as const const invitesQueryKeys = queryKeysFactory(INVITES_QUERY_KEY) @@ -21,13 +21,18 @@ const invitesQueryKeys = queryKeysFactory(INVITES_QUERY_KEY) export const useInvite = ( id: string, options?: Omit< - UseQueryOptions, + UseQueryOptions< + { invite: HttpTypes.AdminInviteResponse }, + Error, + { invite: HttpTypes.AdminInviteResponse }, + QueryKey + >, "queryFn" | "queryKey" > ) => { const { data, ...rest } = useQuery({ queryKey: invitesQueryKeys.detail(id), - queryFn: async () => client.invites.retrieve(id), + queryFn: async () => sdk.admin.invites.retrieve(id), ...options, }) @@ -37,12 +42,17 @@ export const useInvite = ( export const useInvites = ( query?: Record, options?: Omit< - UseQueryOptions, + UseQueryOptions< + PaginatedResponse<{ invites: HttpTypes.AdminInviteResponse[] }>, + Error, + PaginatedResponse<{ invites: HttpTypes.AdminInviteResponse[] }>, + QueryKey + >, "queryFn" | "queryKey" > ) => { const { data, ...rest } = useQuery({ - queryFn: () => client.invites.list(query), + queryFn: () => sdk.admin.invites.list(query), queryKey: invitesQueryKeys.list(query), ...options, }) @@ -51,10 +61,14 @@ export const useInvites = ( } export const useCreateInvite = ( - options?: UseMutationOptions + options?: UseMutationOptions< + { invite: AdminInviteResponse }, + Error, + HttpTypes.AdminCreateInvite + > ) => { return useMutation({ - mutationFn: (payload) => client.invites.create(payload), + mutationFn: (payload) => sdk.admin.invites.create(payload), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: invitesQueryKeys.lists() }) options?.onSuccess?.(data, variables, context) @@ -65,10 +79,10 @@ export const useCreateInvite = ( export const useResendInvite = ( id: string, - options?: UseMutationOptions + options?: UseMutationOptions<{ invite: AdminInviteResponse }, Error, void> ) => { return useMutation({ - mutationFn: () => client.invites.resend(id), + mutationFn: () => sdk.admin.invites.resend(id), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: invitesQueryKeys.lists() }) queryClient.invalidateQueries({ queryKey: invitesQueryKeys.detail(id) }) @@ -80,10 +94,10 @@ export const useResendInvite = ( export const useDeleteInvite = ( id: string, - options?: UseMutationOptions + options?: UseMutationOptions, Error, void> ) => { return useMutation({ - mutationFn: () => client.invites.delete(id), + mutationFn: () => sdk.admin.invites.delete(id), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: invitesQueryKeys.lists() }) queryClient.invalidateQueries({ queryKey: invitesQueryKeys.detail(id) }) @@ -92,3 +106,30 @@ export const useDeleteInvite = ( ...options, }) } + +export const useAcceptInvite = ( + inviteToken: string, + options?: UseMutationOptions< + { user: HttpTypes.AdminUserResponse }, + Error, + HttpTypes.AdminAcceptInvite & { auth_token: string } + > +) => { + return useMutation({ + mutationFn: (payload) => { + const { auth_token, ...rest } = payload + + return sdk.admin.invites.accept( + { invite_token: inviteToken, ...rest }, + {}, + { + Authorization: `Bearer ${auth_token}`, + } + ) + }, + onSuccess: (data, variables, context) => { + options?.onSuccess?.(data, variables, context) + }, + ...options, + }) +} diff --git a/packages/admin-next/dashboard/src/lib/api-v2/auth.ts b/packages/admin-next/dashboard/src/lib/api-v2/auth.ts deleted file mode 100644 index 99de422251..0000000000 --- a/packages/admin-next/dashboard/src/lib/api-v2/auth.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useMutation } from "@tanstack/react-query" -import { medusa } from "../medusa" -import { AcceptInviteInput, CreateAuthUserInput } from "./types/auth" - -export const useV2CreateAuthUser = (provider = "emailpass") => { - // TODO: Migrate type to work for other providers, e.g. Google - return useMutation((args: CreateAuthUserInput) => - medusa.client.request("POST", `/auth/admin/${provider}`, args) - ) -} - -export const useV2AcceptInvite = (inviteToken: string) => { - return useMutation((input: AcceptInviteInput) => - medusa.client.request( - "POST", - `/admin/invites/accept?token=${inviteToken}`, - input.payload, - {}, - { - Authorization: `Bearer ${input.token}`, - } - ) - ) -} diff --git a/packages/admin-next/dashboard/src/v2-routes/invite/invite.tsx b/packages/admin-next/dashboard/src/v2-routes/invite/invite.tsx index 87ceb5cd37..ba083dae65 100644 --- a/packages/admin-next/dashboard/src/v2-routes/invite/invite.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/invite/invite.tsx @@ -5,7 +5,6 @@ import { AnimatePresence, motion } from "framer-motion" import { Trans, useTranslation } from "react-i18next" import { Link, useSearchParams } from "react-router-dom" import * as z from "zod" - import i18n from "i18next" import { useState } from "react" import { useForm } from "react-hook-form" @@ -13,7 +12,8 @@ import { decodeToken } from "react-jwt" import { Form } from "../../components/common/form" import { LogoBox } from "../../components/common/logo-box" import { isAxiosError } from "../../lib/is-axios-error" -import { useV2AcceptInvite, useV2CreateAuthUser } from "../../lib/api-v2" +import { useAcceptInvite } from "../../hooks/api/invites" +import { useCreateAuthUser } from "../../hooks/api/auth" const CreateAccountSchema = z .object({ @@ -205,10 +205,10 @@ const CreateView = ({ }) const { mutateAsync: createAuthUser, isPending: isCreatingAuthUser } = - useV2CreateAuthUser() + useCreateAuthUser() const { mutateAsync: acceptInvite, isPending: isAcceptingInvite } = - useV2AcceptInvite(token) + useAcceptInvite(token) const handleSubmit = form.handleSubmit(async (data) => { try { @@ -224,8 +224,8 @@ const CreateView = ({ } await acceptInvite({ - payload: invitePayload, - token: authToken, + ...invitePayload, + auth_token: authToken, }) onSuccess() toast.success(t("general.success"), { diff --git a/packages/core/js-sdk/src/admin/index.ts b/packages/core/js-sdk/src/admin/index.ts index 1f328ebe02..dcd2120c69 100644 --- a/packages/core/js-sdk/src/admin/index.ts +++ b/packages/core/js-sdk/src/admin/index.ts @@ -80,4 +80,76 @@ export class Admin { ) }, } + + public invites = { + accept: async ( + input: HttpTypes.AdminAcceptInvite & { invite_token: string }, + query?: SelectParams, + headers?: ClientHeaders + ) => { + const { invite_token, ...rest } = input + return this.client.fetch<{ user: HttpTypes.AdminUserResponse }>( + `/admin/invites/accept?token=${input.invite_token}`, + { + method: "POST", + headers, + body: rest, + query, + } + ) + }, + create: async ( + body: HttpTypes.AdminCreateInvite, + query?: SelectParams, + headers?: ClientHeaders + ) => { + return this.client.fetch<{ invite: HttpTypes.AdminInviteResponse }>( + `/admin/invites`, + { + method: "POST", + headers, + body, + query, + } + ) + }, + retrieve: async ( + id: string, + query?: SelectParams, + headers?: ClientHeaders + ) => { + return this.client.fetch<{ invite: HttpTypes.AdminInviteResponse }>( + `/admin/invites/${id}`, + { + headers, + query, + } + ) + }, + list: async (queryParams?: FindParams, headers?: ClientHeaders) => { + return this.client.fetch< + PaginatedResponse<{ invites: HttpTypes.AdminInviteResponse[] }> + >(`/admin/invites`, { + headers, + query: queryParams, + }) + }, + resend: async (id: string, headers?: ClientHeaders) => { + return this.client.fetch<{ invite: HttpTypes.AdminInviteResponse }>( + `/admin/invites/${id}/resend`, + { + headers, + } + ) + }, + delete: async (id: string, headers?: ClientHeaders) => { + return this.client.fetch>( + `/admin/invites/${id}`, + { + method: "DELETE", + headers, + } + ) + }, + } } diff --git a/packages/core/js-sdk/src/auth/index.ts b/packages/core/js-sdk/src/auth/index.ts index a746734034..5f844fa415 100644 --- a/packages/core/js-sdk/src/auth/index.ts +++ b/packages/core/js-sdk/src/auth/index.ts @@ -10,7 +10,7 @@ export class Auth { this.config = config } - login = async ( + public login = async ( scope: "admin" | "store", method: "emailpass", payload: { email: string; password: string } @@ -43,4 +43,15 @@ export class Auth { this.client.clearToken() } + + create = async ( + scope: "admin" | "store", + method: "emailpass", + payload: { email: string; password: string } + ): Promise<{ token: string }> => { + return await this.client.fetch(`/auth/${scope}/${method}`, { + method: "POST", + body: payload, + }) + } } diff --git a/packages/core/js-sdk/src/client.ts b/packages/core/js-sdk/src/client.ts index ba7cb3aab9..89b4058a22 100644 --- a/packages/core/js-sdk/src/client.ts +++ b/packages/core/js-sdk/src/client.ts @@ -161,8 +161,10 @@ export class Client { if (input instanceof URL || typeof input === "string") { normalizedInput = new URL(input, this.config.baseUrl) if (init?.query) { - const existing = qs.parse(normalizedInput.search) - const stringifiedQuery = qs.stringify({ existing, ...init.query }) + const params = Object.fromEntries( + normalizedInput.searchParams.entries() + ) + const stringifiedQuery = qs.stringify({ ...params, ...init.query }) normalizedInput.search = stringifiedQuery } } diff --git a/packages/core/types/src/http/index.ts b/packages/core/types/src/http/index.ts index bb78bc5018..5d363bd696 100644 --- a/packages/core/types/src/http/index.ts +++ b/packages/core/types/src/http/index.ts @@ -1,20 +1,22 @@ export * from "./api-key" export * from "./campaign" +export * from "./cart" +export * from "./collection" +export * from "./common" export * from "./customer" export * from "./fulfillment" export * from "./inventory" +export * from "./invite" export * from "./order" +export * from "./payment" export * from "./pricing" +export * from "./product" export * from "./product-category" export * from "./promotion" +export * from "./region" +export * from "./reservation" export * from "./sales-channel" export * from "./stock-locations" export * from "./tax" -export * from "./product-category" -export * from "./reservation" -export * from "./region" -export * from "./product" -export * from "./cart" -export * from "./payment" -export * from "./collection" -export * from "./common" +export * from "./user" + diff --git a/packages/core/types/src/http/invite/admin.ts b/packages/core/types/src/http/invite/admin.ts new file mode 100644 index 0000000000..cd7da54c7d --- /dev/null +++ b/packages/core/types/src/http/invite/admin.ts @@ -0,0 +1,20 @@ +export type AdminAcceptInvite = { + first_name: string + last_name: string +} + +export type AdminCreateInvite = { + email: string + metadata?: Record +} + +export type AdminInviteResponse = { + id: string + email: string + accepted: boolean + token: string + expires_at?: Date + metadata?: Record + created_at?: Date + updated_at?: Date +} diff --git a/packages/core/types/src/http/invite/index.ts b/packages/core/types/src/http/invite/index.ts new file mode 100644 index 0000000000..26b8eb9dad --- /dev/null +++ b/packages/core/types/src/http/invite/index.ts @@ -0,0 +1 @@ +export * from "./admin" diff --git a/packages/core/types/src/http/user/admin.ts b/packages/core/types/src/http/user/admin.ts new file mode 100644 index 0000000000..cf1c6a09ad --- /dev/null +++ b/packages/core/types/src/http/user/admin.ts @@ -0,0 +1,3 @@ +import { BaseUserResponse } from "./common" + +export type AdminUserResponse = BaseUserResponse diff --git a/packages/core/types/src/http/user/common.ts b/packages/core/types/src/http/user/common.ts new file mode 100644 index 0000000000..6994de5784 --- /dev/null +++ b/packages/core/types/src/http/user/common.ts @@ -0,0 +1,11 @@ +export type BaseUserResponse = { + id: string + email: string + first_name: string | null + last_name: string | null + avatar_url: string | null + metadata: Record | null + created_at: Date + updated_at: Date + deleted_at: Date | null +} diff --git a/packages/core/types/src/http/user/index.ts b/packages/core/types/src/http/user/index.ts new file mode 100644 index 0000000000..9335a78736 --- /dev/null +++ b/packages/core/types/src/http/user/index.ts @@ -0,0 +1,2 @@ +export * from "./admin"; +