admin page
This commit is contained in:
@@ -3,9 +3,11 @@ import type {
|
||||
AdminLoginResponse,
|
||||
AdminMeResponse,
|
||||
CreateOrganizationRequest,
|
||||
DocumentResponse,
|
||||
Organization,
|
||||
OrganizationListResponse,
|
||||
UpdateOrganizationRequest,
|
||||
WalletResponse,
|
||||
} from '../model/types'
|
||||
|
||||
const ADMIN_API_URL = 'https://app.admin.elcsa.ru'
|
||||
@@ -26,12 +28,15 @@ async function doAdminRequest<T>(
|
||||
allowRetry: boolean,
|
||||
): Promise<T> {
|
||||
const bearer = adminTokenStore.get()
|
||||
// For multipart uploads we must NOT set Content-Type — the browser adds the
|
||||
// boundary itself. Detect FormData bodies and skip the JSON header.
|
||||
const isFormData = options.body instanceof FormData
|
||||
|
||||
const res = await fetch(`${ADMIN_API_URL}${path}`, {
|
||||
...options,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
|
||||
...(bearer ? { Authorization: `Bearer ${bearer}` } : {}),
|
||||
...options.headers,
|
||||
},
|
||||
@@ -107,6 +112,58 @@ export function getOrganization(id: string): Promise<Organization> {
|
||||
return doAdminRequest<Organization>(`/v1/organizations/${id}`, {}, true)
|
||||
}
|
||||
|
||||
export function createOrganizationWallets(id: string): Promise<WalletResponse[]> {
|
||||
return doAdminRequest<WalletResponse[]>(
|
||||
`/v1/organizations/${id}/wallets/create`,
|
||||
{ method: 'POST' },
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
export async function getDocuments(orgId: string): Promise<DocumentResponse[]> {
|
||||
const data = await doAdminRequest<DocumentResponse[]>(
|
||||
`/v1/organizations/${orgId}/documents`,
|
||||
{},
|
||||
true,
|
||||
)
|
||||
// TEMP: inspect the real backend shape (download_url presence, fields).
|
||||
console.log('[documents] list response:', data)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function uploadDocument(
|
||||
orgId: string,
|
||||
documentType: string,
|
||||
file: File,
|
||||
): Promise<DocumentResponse> {
|
||||
const body = new FormData()
|
||||
body.append('document_type', documentType)
|
||||
body.append('file', file)
|
||||
|
||||
const data = await doAdminRequest<DocumentResponse>(
|
||||
`/v1/organizations/${orgId}/documents`,
|
||||
{ method: 'POST', body },
|
||||
true,
|
||||
)
|
||||
// TEMP: inspect the real backend shape after upload.
|
||||
console.log('[documents] upload response:', data)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getDocument(
|
||||
orgId: string,
|
||||
documentId: string,
|
||||
): Promise<DocumentResponse> {
|
||||
const data = await doAdminRequest<DocumentResponse>(
|
||||
`/v1/organizations/${orgId}/documents/${documentId}`,
|
||||
{},
|
||||
true,
|
||||
)
|
||||
// TEMP: inspect single-document shape (this is where download_url should appear).
|
||||
console.log('[documents] get-one response:', data)
|
||||
return data
|
||||
}
|
||||
|
||||
export function updateOrganization(
|
||||
id: string,
|
||||
payload: UpdateOrganizationRequest,
|
||||
|
||||
16
src/features/admin/hooks/useCreateOrganizationWallets.ts
Normal file
16
src/features/admin/hooks/useCreateOrganizationWallets.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { createOrganizationWallets } from '../api/adminApi'
|
||||
import { ORGANIZATIONS_QUERY_KEY } from './useOrganizations'
|
||||
import { ORGANIZATION_QUERY_KEY } from './useOrganization'
|
||||
|
||||
export function useCreateOrganizationWallets() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (organizationId: string) => createOrganizationWallets(organizationId),
|
||||
onSuccess: (_wallets, organizationId) => {
|
||||
// `has_wallets` flips to true server-side — refresh list and detail view.
|
||||
queryClient.invalidateQueries({ queryKey: ORGANIZATIONS_QUERY_KEY })
|
||||
queryClient.invalidateQueries({ queryKey: ORGANIZATION_QUERY_KEY(organizationId) })
|
||||
},
|
||||
})
|
||||
}
|
||||
12
src/features/admin/hooks/useDocuments.ts
Normal file
12
src/features/admin/hooks/useDocuments.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getDocuments } from '../api/adminApi'
|
||||
|
||||
export const DOCUMENTS_QUERY_KEY = (orgId: string) => ['admin-documents', orgId]
|
||||
|
||||
export function useDocuments(orgId: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: DOCUMENTS_QUERY_KEY(orgId ?? ''),
|
||||
queryFn: () => getDocuments(orgId as string),
|
||||
enabled: !!orgId,
|
||||
})
|
||||
}
|
||||
32
src/features/admin/hooks/useDownloadDocument.ts
Normal file
32
src/features/admin/hooks/useDownloadDocument.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useState } from 'react'
|
||||
import { getDocument } from '../api/adminApi'
|
||||
import type { DocumentResponse } from '../model/types'
|
||||
|
||||
/**
|
||||
* Resolves a document's download URL and opens it. The list endpoint may not
|
||||
* include a fresh `download_url`, so we fall back to fetching the single
|
||||
* document (which is where the presigned URL is expected to appear).
|
||||
*/
|
||||
export function useDownloadDocument(orgId: string) {
|
||||
const [downloadingId, setDownloadingId] = useState<string | null>(null)
|
||||
|
||||
async function download(doc: DocumentResponse) {
|
||||
setDownloadingId(doc.id)
|
||||
try {
|
||||
let url = doc.download_url
|
||||
if (!url) {
|
||||
const fresh = await getDocument(orgId, doc.id)
|
||||
url = fresh.download_url
|
||||
}
|
||||
if (url) {
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
} else {
|
||||
throw new Error('Сервер не вернул ссылку для скачивания')
|
||||
}
|
||||
} finally {
|
||||
setDownloadingId(null)
|
||||
}
|
||||
}
|
||||
|
||||
return { download, downloadingId }
|
||||
}
|
||||
19
src/features/admin/hooks/useUploadDocument.ts
Normal file
19
src/features/admin/hooks/useUploadDocument.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { uploadDocument } from '../api/adminApi'
|
||||
import { DOCUMENTS_QUERY_KEY } from './useDocuments'
|
||||
|
||||
interface UploadArgs {
|
||||
documentType: string
|
||||
file: File
|
||||
}
|
||||
|
||||
export function useUploadDocument(orgId: string) {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: ({ documentType, file }: UploadArgs) =>
|
||||
uploadDocument(orgId, documentType, file),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: DOCUMENTS_QUERY_KEY(orgId) })
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -5,7 +5,11 @@ export {
|
||||
getOrganizations,
|
||||
getOrganization,
|
||||
createOrganization,
|
||||
createOrganizationWallets,
|
||||
updateOrganization,
|
||||
getDocuments,
|
||||
uploadDocument,
|
||||
getDocument,
|
||||
refreshAdminToken,
|
||||
adminTokenStore,
|
||||
} from './api/adminApi'
|
||||
@@ -17,6 +21,8 @@ export type {
|
||||
OrganizationListResponse,
|
||||
CreateOrganizationRequest,
|
||||
UpdateOrganizationRequest,
|
||||
WalletResponse,
|
||||
DocumentResponse,
|
||||
BankDetails,
|
||||
} from './model/types'
|
||||
export { useAdminAuth, ADMIN_AUTH_QUERY_KEY } from './hooks/useAdminAuth'
|
||||
@@ -25,4 +31,8 @@ export { useAdminLogout } from './hooks/useAdminLogout'
|
||||
export { useOrganizations, ORGANIZATIONS_QUERY_KEY } from './hooks/useOrganizations'
|
||||
export { useOrganization, ORGANIZATION_QUERY_KEY } from './hooks/useOrganization'
|
||||
export { useCreateOrganization } from './hooks/useCreateOrganization'
|
||||
export { useCreateOrganizationWallets } from './hooks/useCreateOrganizationWallets'
|
||||
export { useUpdateOrganization } from './hooks/useUpdateOrganization'
|
||||
export { useDocuments, DOCUMENTS_QUERY_KEY } from './hooks/useDocuments'
|
||||
export { useUploadDocument } from './hooks/useUploadDocument'
|
||||
export { useDownloadDocument } from './hooks/useDownloadDocument'
|
||||
|
||||
@@ -50,6 +50,26 @@ export interface OrganizationListResponse {
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface WalletResponse {
|
||||
id: string
|
||||
chain: string
|
||||
address: string
|
||||
derivation_path: string
|
||||
created_at: string | null
|
||||
}
|
||||
|
||||
export interface DocumentResponse {
|
||||
id: string
|
||||
organization_id: string
|
||||
document_type: string
|
||||
file_name: string
|
||||
content_type: string
|
||||
file_size_bytes: number
|
||||
uploaded_by: string | null
|
||||
created_at: string | null
|
||||
download_url: string | null
|
||||
}
|
||||
|
||||
export interface UpdateOrganizationRequest {
|
||||
name?: string | null
|
||||
short_name?: string | null
|
||||
|
||||
Reference in New Issue
Block a user