11 changed files with 196 additions and 39 deletions
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
import { Label } from '@/components/ui/label' |
||||
import { |
||||
Select, |
||||
SelectContent, |
||||
SelectItem, |
||||
SelectTrigger, |
||||
SelectValue |
||||
} from '@/components/ui/select' |
||||
import { DEFAULT_NIP_96_SERVICE, NIP_96_SERVICE } from '@/constants' |
||||
import { simplifyUrl } from '@/lib/url' |
||||
import { useMediaUploadService } from '@/providers/MediaUploadServiceProvider' |
||||
import { useTranslation } from 'react-i18next' |
||||
|
||||
export default function MediaUploadServiceSetting() { |
||||
const { t } = useTranslation() |
||||
const { service, updateService } = useMediaUploadService() |
||||
|
||||
return ( |
||||
<div className="space-y-2"> |
||||
<Label htmlFor="media-upload-service-select">{t('Media upload service')}</Label> |
||||
<Select defaultValue={DEFAULT_NIP_96_SERVICE} value={service} onValueChange={updateService}> |
||||
<SelectTrigger id="media-upload-service-select" className="w-48"> |
||||
<SelectValue /> |
||||
</SelectTrigger> |
||||
<SelectContent> |
||||
{NIP_96_SERVICE.map((url) => ( |
||||
<SelectItem key={url} value={url}> |
||||
{simplifyUrl(url)} |
||||
</SelectItem> |
||||
))} |
||||
</SelectContent> |
||||
</Select> |
||||
</div> |
||||
) |
||||
} |
||||
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' |
||||
import { forwardRef } from 'react' |
||||
import { useTranslation } from 'react-i18next' |
||||
import MediaUploadServiceSetting from './MediaUploadServiceSetting' |
||||
|
||||
const PostSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { |
||||
const { t } = useTranslation() |
||||
|
||||
return ( |
||||
<SecondaryPageLayout ref={ref} index={index} title={t('Wallet')}> |
||||
<div className="px-4 pt-2 space-y-4"> |
||||
<MediaUploadServiceSetting /> |
||||
</div> |
||||
</SecondaryPageLayout> |
||||
) |
||||
}) |
||||
PostSettingsPage.displayName = 'PostSettingsPage' |
||||
export default PostSettingsPage |
||||
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
import { simplifyUrl } from '@/lib/url' |
||||
import storage from '@/services/local-storage.service' |
||||
import { createContext, useContext, useState } from 'react' |
||||
import { z } from 'zod' |
||||
import { useNostr } from './NostrProvider' |
||||
|
||||
type TMediaUploadServiceContext = { |
||||
service: string |
||||
updateService: (service: string) => void |
||||
upload: (file: File) => Promise<{ url: string; tags: string[][] }> |
||||
} |
||||
|
||||
const MediaUploadServiceContext = createContext<TMediaUploadServiceContext | undefined>(undefined) |
||||
|
||||
export const useMediaUploadService = () => { |
||||
const context = useContext(MediaUploadServiceContext) |
||||
if (!context) { |
||||
throw new Error('useMediaUploadService must be used within MediaUploadServiceProvider') |
||||
} |
||||
return context |
||||
} |
||||
|
||||
const ServiceUploadUrlMap = new Map<string, string | undefined>() |
||||
|
||||
export function MediaUploadServiceProvider({ children }: { children: React.ReactNode }) { |
||||
const { signHttpAuth } = useNostr() |
||||
const [service, setService] = useState(storage.getMediaUploadService()) |
||||
|
||||
const updateService = (newService: string) => { |
||||
setService(newService) |
||||
storage.setMediaUploadService(newService) |
||||
} |
||||
|
||||
const upload = async (file: File) => { |
||||
let uploadUrl = ServiceUploadUrlMap.get(service) |
||||
if (!uploadUrl) { |
||||
const response = await fetch(`${service}/.well-known/nostr/nip96.json`) |
||||
if (!response.ok) { |
||||
throw new Error( |
||||
`${simplifyUrl(service)} does not work, please try another service in your settings` |
||||
) |
||||
} |
||||
const data = await response.json() |
||||
uploadUrl = data?.api_url |
||||
if (!uploadUrl) { |
||||
throw new Error( |
||||
`${simplifyUrl(service)} does not work, please try another service in your settings` |
||||
) |
||||
} |
||||
ServiceUploadUrlMap.set(service, uploadUrl) |
||||
} |
||||
|
||||
const formData = new FormData() |
||||
formData.append('file', file) |
||||
|
||||
const auth = await signHttpAuth(uploadUrl, 'POST') |
||||
const response = await fetch(uploadUrl, { |
||||
method: 'POST', |
||||
body: formData, |
||||
headers: { |
||||
Authorization: auth |
||||
} |
||||
}) |
||||
|
||||
if (!response.ok) { |
||||
throw new Error(response.status.toString()) |
||||
} |
||||
|
||||
const data = await response.json() |
||||
const tags = z.array(z.array(z.string())).parse(data.nip94_event?.tags ?? []) |
||||
const imageUrl = tags.find(([tagName]) => tagName === 'url')?.[1] |
||||
if (imageUrl) { |
||||
return { url: imageUrl, tags } |
||||
} else { |
||||
throw new Error('No image url found') |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<MediaUploadServiceContext.Provider value={{ service, updateService, upload }}> |
||||
{children} |
||||
</MediaUploadServiceContext.Provider> |
||||
) |
||||
} |
||||
Loading…
Reference in new issue