feat: banner setting
This commit is contained in:
parent
accd9ff183
commit
7d8b85bb0e
|
|
@ -76,9 +76,10 @@ export const SiteHeader: React.FC<{
|
|||
<div className="xlog-banner absolute top-0 bottom-0 left-0 right-0 -z-10 overflow-hidden">
|
||||
{site?.banners?.[0]?.mime_type.split("/")[0] === "image" && (
|
||||
<Image
|
||||
className="max-w-screen-md mx-auto object-cover h-full w-full"
|
||||
className="max-w-screen-md mx-auto object-cover"
|
||||
src={site?.banners?.[0]?.address}
|
||||
alt="banner"
|
||||
fill
|
||||
/>
|
||||
)}
|
||||
{site?.banners?.[0]?.mime_type.split("/")[0] === "video" && (
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export const Avatar: React.FC<
|
|||
}}
|
||||
>
|
||||
<Image
|
||||
className="overflow-hidden"
|
||||
className="overflow-hidden object-cover"
|
||||
src={image}
|
||||
width={size}
|
||||
height={size}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import React, { ChangeEvent, forwardRef, useEffect } from "react"
|
|||
import { useState } from "react"
|
||||
import { useUploadFile } from "~/hooks/useUploadFile"
|
||||
import { toGateway } from "~/lib/ipfs-parser"
|
||||
import { Image } from "~/components/ui/Image"
|
||||
import { XMarkIcon } from "@heroicons/react/20/solid"
|
||||
|
||||
const getBase64 = (img: File, callback: (url: string) => void) => {
|
||||
const reader = new FileReader()
|
||||
|
|
@ -16,12 +18,23 @@ export const ImageUploader = forwardRef(function ImageUploader(
|
|||
image,
|
||||
uploadStart,
|
||||
uploadEnd,
|
||||
withMimeType,
|
||||
hasClose,
|
||||
...inputProps
|
||||
}: {
|
||||
className?: string
|
||||
image?: string
|
||||
uploadStart?: () => void
|
||||
uploadEnd?: (key: string) => void
|
||||
uploadEnd?: (
|
||||
key:
|
||||
| string
|
||||
| {
|
||||
address?: string
|
||||
mime_type?: string
|
||||
},
|
||||
) => void
|
||||
withMimeType?: boolean
|
||||
hasClose?: boolean
|
||||
} & React.ComponentPropsWithRef<"input">,
|
||||
ref: React.ForwardedRef<HTMLInputElement>,
|
||||
) {
|
||||
|
|
@ -38,32 +51,94 @@ export const ImageUploader = forwardRef(function ImageUploader(
|
|||
setLoading(true)
|
||||
uploadStart?.()
|
||||
const key = (await uploadFile(event.target.files[0])).key
|
||||
uploadEnd?.(key)
|
||||
uploadEnd?.(
|
||||
withMimeType
|
||||
? {
|
||||
address: key,
|
||||
mime_type: event.target.files[0].type,
|
||||
}
|
||||
: key,
|
||||
)
|
||||
setLoading(false)
|
||||
} else if (event.target.value) {
|
||||
setImageUrl(event.target.value)
|
||||
}
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
setImageUrl(undefined)
|
||||
uploadEnd?.(withMimeType ? {} : "")
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!imageUrl) {
|
||||
setImageUrl((inputProps.value as string) || "")
|
||||
setImageUrl(
|
||||
((withMimeType
|
||||
? (inputProps.value as any)?.address
|
||||
: inputProps.value) as string) || "",
|
||||
)
|
||||
}
|
||||
}, [inputProps.value])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"relative flex-col overflow-hidden bg-gray-400",
|
||||
"relative flex-col overflow-hidden border border-gray-100",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<input {...inputProps} onChange={handleChange} className="hidden" />
|
||||
{imageUrl && <img src={toGateway(imageUrl)} className="w-full h-full" />}
|
||||
<input
|
||||
{...(withMimeType
|
||||
? {
|
||||
value: (inputProps.value as any)?.address,
|
||||
...inputProps,
|
||||
}
|
||||
: inputProps)}
|
||||
onChange={handleChange}
|
||||
className="hidden"
|
||||
/>
|
||||
{imageUrl && !withMimeType && (
|
||||
<Image
|
||||
className="mx-auto object-cover"
|
||||
src={toGateway(imageUrl)}
|
||||
alt="banner"
|
||||
fill
|
||||
/>
|
||||
)}
|
||||
{imageUrl &&
|
||||
withMimeType &&
|
||||
(inputProps.value as any)?.mime_type.split("/")[0] === "image" && (
|
||||
<Image
|
||||
className="mx-auto object-cover"
|
||||
src={toGateway(imageUrl)}
|
||||
alt="banner"
|
||||
fill
|
||||
/>
|
||||
)}
|
||||
{imageUrl &&
|
||||
withMimeType &&
|
||||
(inputProps.value as any)?.mime_type.split("/")[0] === "video" && (
|
||||
<video
|
||||
className="max-w-screen-md mx-auto object-cover h-full w-full"
|
||||
src={toGateway(imageUrl)}
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
onChange={handleChange}
|
||||
type="file"
|
||||
className="absolute top-0 bottom-0 left-0 right-0 opacity-0"
|
||||
/>
|
||||
{hasClose && (
|
||||
<div
|
||||
onClick={clear}
|
||||
className="w-8 h-8 absolute top-4 right-4 shadow-lg bg-white rounded-full cursor-pointer"
|
||||
>
|
||||
<XMarkIcon />
|
||||
</div>
|
||||
)}
|
||||
{loading && (
|
||||
<div className="absolute top-0 bottom-0 left-0 right-0 flex justify-center items-center bg-gray-500 opacity-50">
|
||||
<div className="loading flex justify-center items-center relative text-white"></div>
|
||||
|
|
|
|||
|
|
@ -238,6 +238,10 @@ export async function updateSite(
|
|||
css?: string
|
||||
ga?: string
|
||||
custom_domain?: string
|
||||
banner?: {
|
||||
address: string
|
||||
mime_type: string
|
||||
}
|
||||
},
|
||||
customUnidata?: Unidata,
|
||||
) {
|
||||
|
|
@ -252,6 +256,7 @@ export async function updateSite(
|
|||
...(payload.name && { name: payload.name }),
|
||||
...(payload.description && { bio: payload.description }),
|
||||
...(payload.icon && { avatars: [payload.icon] }),
|
||||
...(payload.banner && { banners: [payload.banner] }),
|
||||
...(payload.subdomain && { username: payload.subdomain }),
|
||||
...((payload.navigation !== undefined ||
|
||||
payload.css !== undefined ||
|
||||
|
|
|
|||
|
|
@ -22,15 +22,26 @@ export default function SiteSettingsGeneralPage() {
|
|||
const form = useForm({
|
||||
defaultValues: {
|
||||
icon: "",
|
||||
banner: undefined,
|
||||
name: "",
|
||||
description: "",
|
||||
ga: "",
|
||||
} as {
|
||||
icon: string
|
||||
banner?: {
|
||||
address: string
|
||||
mime_type: string
|
||||
}
|
||||
name: string
|
||||
description: string
|
||||
ga: string
|
||||
},
|
||||
})
|
||||
|
||||
const handleSubmit = form.handleSubmit((values) => {
|
||||
updateSite.mutate({
|
||||
icon: values.icon,
|
||||
banner: values.banner,
|
||||
site: subdomain,
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
|
|
@ -54,6 +65,16 @@ export default function SiteSettingsGeneralPage() {
|
|||
if (site.data) {
|
||||
!form.getValues("icon") &&
|
||||
form.setValue("icon", toIPFS(site.data?.avatars?.[0] || ""))
|
||||
!form.getValues("banner") &&
|
||||
form.setValue(
|
||||
"banner",
|
||||
site.data?.banners?.[0]
|
||||
? {
|
||||
address: toIPFS(site.data?.banners?.[0].address || ""),
|
||||
mime_type: site.data?.banners?.[0].mime_type,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
!form.getValues("name") && form.setValue("name", site.data.name || "")
|
||||
!form.getValues("description") &&
|
||||
form.setValue("description", site.data.bio || "")
|
||||
|
|
@ -62,6 +83,7 @@ export default function SiteSettingsGeneralPage() {
|
|||
}, [site.data, form])
|
||||
|
||||
const [iconUploading, setIconUploading] = useState(false)
|
||||
const [bannerUploading, setBannerUploading] = useState(false)
|
||||
|
||||
return (
|
||||
<DashboardLayout title="Site Settings">
|
||||
|
|
@ -82,7 +104,7 @@ export default function SiteSettingsGeneralPage() {
|
|||
setIconUploading(true)
|
||||
}}
|
||||
uploadEnd={(key) => {
|
||||
form.setValue("icon", key)
|
||||
form.setValue("icon", key as string)
|
||||
setIconUploading(false)
|
||||
}}
|
||||
{...field}
|
||||
|
|
@ -90,6 +112,37 @@ export default function SiteSettingsGeneralPage() {
|
|||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<label htmlFor="icon" className="form-label">
|
||||
Banner
|
||||
</label>
|
||||
<Controller
|
||||
name="banner"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<ImageUploader
|
||||
id="banner"
|
||||
className="max-w-screen-md h-[220px]"
|
||||
uploadStart={() => {
|
||||
setBannerUploading(true)
|
||||
}}
|
||||
uploadEnd={(key) => {
|
||||
form.setValue(
|
||||
"banner",
|
||||
key as { address: string; mime_type: string },
|
||||
)
|
||||
setBannerUploading(false)
|
||||
}}
|
||||
withMimeType={true}
|
||||
hasClose={true}
|
||||
{...(field as any)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="text-xs text-gray-400 mt-1">
|
||||
Click to upload. Supports both pictures and videos.
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Input required label="Name" id="name" {...form.register("name")} />
|
||||
</div>
|
||||
|
|
@ -131,7 +184,7 @@ export default function SiteSettingsGeneralPage() {
|
|||
<Button
|
||||
type="submit"
|
||||
isLoading={updateSite.isLoading}
|
||||
isDisabled={iconUploading}
|
||||
isDisabled={iconUploading || bannerUploading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
|
|
|
|||
Loading…
Reference in New Issue