Just a test release
This commit is contained in:
@@ -159,6 +159,7 @@ export const accountActions = {
|
|||||||
.optional()
|
.optional()
|
||||||
.nullable(),
|
.nullable(),
|
||||||
pictureFile: imageFileSchema,
|
pictureFile: imageFileSchema,
|
||||||
|
removePicture: z.coerce.boolean().default(false),
|
||||||
}),
|
}),
|
||||||
handler: async (input, context) => {
|
handler: async (input, context) => {
|
||||||
if (input.id !== context.locals.user.id) {
|
if (input.id !== context.locals.user.id) {
|
||||||
@@ -204,14 +205,14 @@ export const accountActions = {
|
|||||||
input.pictureFile.name,
|
input.pictureFile.name,
|
||||||
`users/pictures/${String(context.locals.user.id)}`
|
`users/pictures/${String(context.locals.user.id)}`
|
||||||
)
|
)
|
||||||
: null
|
: undefined
|
||||||
|
|
||||||
const user = await prisma.user.update({
|
const user = await prisma.user.update({
|
||||||
where: { id: context.locals.user.id },
|
where: { id: context.locals.user.id },
|
||||||
data: {
|
data: {
|
||||||
displayName: input.displayName ?? null,
|
displayName: input.displayName ?? null,
|
||||||
link: input.link ?? null,
|
link: input.link ?? null,
|
||||||
picture: pictureUrl,
|
picture: input.removePicture ? null : (pictureUrl ?? undefined),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,27 @@ type Props = Omit<ComponentProps<typeof InputWrapper>, 'children' | 'inputId'> &
|
|||||||
accept?: string
|
accept?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
multiple?: boolean
|
multiple?: boolean
|
||||||
|
removeCheckbox?: {
|
||||||
|
name: string
|
||||||
|
label: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { accept, disabled, multiple, ...wrapperProps } = Astro.props
|
const { accept, disabled, multiple, removeCheckbox, ...wrapperProps } = Astro.props
|
||||||
|
|
||||||
const inputId = Astro.locals.makeId(`input-${wrapperProps.name}`)
|
const inputId = Astro.locals.makeId(`input-${wrapperProps.name}`)
|
||||||
const hasError = !!wrapperProps.error && wrapperProps.error.length > 0
|
const hasError = !!wrapperProps.error && wrapperProps.error.length > 0
|
||||||
---
|
---
|
||||||
|
|
||||||
<InputWrapper inputId={inputId} {...wrapperProps}>
|
<InputWrapper inputId={inputId} {...wrapperProps}>
|
||||||
|
{
|
||||||
|
!!removeCheckbox && (
|
||||||
|
<label class="flex cursor-pointer items-center gap-2 py-1 pl-1 text-sm leading-none">
|
||||||
|
<input transition:persist type="checkbox" name={removeCheckbox.name} data-remove-checkbox />
|
||||||
|
{removeCheckbox.label || 'Remove'}
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
<input
|
<input
|
||||||
transition:persist
|
transition:persist
|
||||||
type="file"
|
type="file"
|
||||||
@@ -27,7 +39,8 @@ const hasError = !!wrapperProps.error && wrapperProps.error.length > 0
|
|||||||
baseInputClassNames.input,
|
baseInputClassNames.input,
|
||||||
baseInputClassNames.file,
|
baseInputClassNames.file,
|
||||||
hasError && baseInputClassNames.error,
|
hasError && baseInputClassNames.error,
|
||||||
disabled && baseInputClassNames.disabled
|
disabled && baseInputClassNames.disabled,
|
||||||
|
'[&:is(:has([data-remove-checkbox]:checked)_~_*)]:hidden'
|
||||||
)}
|
)}
|
||||||
required={wrapperProps.required}
|
required={wrapperProps.required}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -8,20 +8,26 @@ import type { ComponentProps } from 'astro/types'
|
|||||||
|
|
||||||
type Props = Omit<ComponentProps<typeof InputFile>, 'accept'> & {
|
type Props = Omit<ComponentProps<typeof InputFile>, 'accept'> & {
|
||||||
square?: boolean
|
square?: boolean
|
||||||
|
value?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const { class: className, square, ...inputFileProps } = Astro.props
|
const { class: className, square, value, ...inputFileProps } = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class={cn('flex flex-wrap items-center justify-center gap-4', className)} data-preview-image>
|
<div class={cn('flex flex-wrap items-center justify-center gap-4', className)} data-preview-image>
|
||||||
<InputFile accept={ACCEPTED_IMAGE_TYPES.join(',')} class="min-w-0 flex-1 basis-2xs" {...inputFileProps} />
|
<InputFile
|
||||||
|
accept={ACCEPTED_IMAGE_TYPES.join(',')}
|
||||||
|
class="min-w-0 flex-1 basis-[calc(var(--spacing)*32)]"
|
||||||
|
{...inputFileProps}
|
||||||
|
/>
|
||||||
<img
|
<img
|
||||||
src="#"
|
src={value}
|
||||||
alt="Preview"
|
alt="Preview"
|
||||||
class={cn(
|
class={cn(
|
||||||
'block w-26.5 rounded object-cover',
|
'block h-24 rounded object-cover',
|
||||||
'no-js:hidden not-[[src]]:hidden [&[src=""]]:hidden [&[src="#"]]:hidden',
|
square && 'aspect-square',
|
||||||
square && 'aspect-square'
|
'no-js:hidden not-[[src]]:hidden [&[src=""]]:hidden',
|
||||||
|
'[&:is(:has([data-remove-checkbox]:checked)_~_*)]:hidden'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const hasError = !!error && error.length > 0
|
|||||||
|
|
||||||
{
|
{
|
||||||
!!description && (
|
!!description && (
|
||||||
<div class="prose prose-sm prose-invert text-day-400 text-xs text-pretty">
|
<div class="prose prose-sm prose-invert prose-a:text-current prose-a:font-normal hover:prose-a:text-day-300 prose-a:transition-colors text-day-400 text-xs text-pretty">
|
||||||
<Markdown content={description} />
|
<Markdown content={description} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from 'astro-icon/components'
|
|
||||||
import { actions, isInputError } from 'astro:actions'
|
import { actions, isInputError } from 'astro:actions'
|
||||||
|
|
||||||
import Button from '../../components/Button.astro'
|
import Button from '../../components/Button.astro'
|
||||||
|
import InputImageFile from '../../components/InputImageFile.astro'
|
||||||
|
import InputText from '../../components/InputText.astro'
|
||||||
import { karmaUnlocksById } from '../../constants/karmaUnlocks'
|
import { karmaUnlocksById } from '../../constants/karmaUnlocks'
|
||||||
import MiniLayout from '../../layouts/MiniLayout.astro'
|
import MiniLayout from '../../layouts/MiniLayout.astro'
|
||||||
import { makeKarmaUnlockMessage } from '../../lib/karmaUnlocks'
|
import { makeKarmaUnlockMessage } from '../../lib/karmaUnlocks'
|
||||||
@@ -42,95 +43,52 @@ const inputErrors = isInputError(result?.error) ? result.error.fields : {}
|
|||||||
<form method="POST" action={actions.account.update} class="space-y-4" enctype="multipart/form-data">
|
<form method="POST" action={actions.account.update} class="space-y-4" enctype="multipart/form-data">
|
||||||
<input transition:persist type="hidden" name="id" value={user.id} />
|
<input transition:persist type="hidden" name="id" value={user.id} />
|
||||||
|
|
||||||
<div>
|
<InputText
|
||||||
<label class="text-day-200 mb-2 block text-sm" for="displayName">Display Name</label>
|
label="Display Name"
|
||||||
<input
|
name="displayName"
|
||||||
transition:persist
|
error={inputErrors.displayName}
|
||||||
type="text"
|
inputProps={{
|
||||||
id="displayName"
|
value: user.displayName ?? '',
|
||||||
name="displayName"
|
maxlength: 100,
|
||||||
value={user.displayName ?? ''}
|
disabled: !user.karmaUnlocks.displayName,
|
||||||
maxlength={100}
|
}}
|
||||||
class="border-day-500/30 bg-night-800 text-day-300 placeholder:text-day-500 focus:border-day-500 focus:ring-day-500 w-full rounded-md border px-3 py-2 disabled:cursor-not-allowed disabled:opacity-60"
|
description={!user.karmaUnlocks.displayName
|
||||||
disabled={!user.karmaUnlocks.displayName}
|
? `${makeKarmaUnlockMessage(karmaUnlocksById.displayName)} [Learn more](/karma)`
|
||||||
/>
|
: undefined}
|
||||||
{
|
/>
|
||||||
inputErrors.displayName && (
|
|
||||||
<p class="mt-1 text-sm text-red-400">{inputErrors.displayName.join(', ')}</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
!user.karmaUnlocks.displayName && (
|
|
||||||
<p class="text-day-400 mt-2 flex items-center gap-2 rounded-md text-sm">
|
|
||||||
<Icon name="ri:information-line" class="size-4" />
|
|
||||||
{makeKarmaUnlockMessage(karmaUnlocksById.displayName)}
|
|
||||||
<a href="/karma" class="hover:text-day-300 underline">
|
|
||||||
Learn about karma
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<InputText
|
||||||
<label class="text-day-200 mb-2 block text-sm" for="link">Website URL</label>
|
label="Website URL"
|
||||||
<input
|
name="link"
|
||||||
transition:persist
|
error={inputErrors.link}
|
||||||
type="url"
|
inputProps={{
|
||||||
id="link"
|
value: user.link ?? '',
|
||||||
name="link"
|
type: 'url',
|
||||||
value={user.link ?? ''}
|
placeholder: 'https://example.com',
|
||||||
placeholder="https://example.com"
|
disabled: !user.karmaUnlocks.websiteLink,
|
||||||
class="border-day-500/30 bg-night-800 text-day-300 placeholder:text-day-500 focus:border-day-500 focus:ring-day-500 w-full rounded-md border px-3 py-2 disabled:cursor-not-allowed disabled:opacity-60"
|
}}
|
||||||
disabled={!user.karmaUnlocks.websiteLink}
|
description={!user.karmaUnlocks.websiteLink
|
||||||
/>
|
? `${makeKarmaUnlockMessage(karmaUnlocksById.websiteLink)} [Learn more](/karma)`
|
||||||
{inputErrors.link && <p class="mt-1 text-sm text-red-400">{inputErrors.link.join(', ')}</p>}
|
: undefined}
|
||||||
{
|
/>
|
||||||
!user.karmaUnlocks.websiteLink && (
|
|
||||||
<p class="text-day-400 mt-2 flex items-center gap-2 rounded-md text-sm">
|
|
||||||
<Icon name="ri:information-line" class="size-4" />
|
|
||||||
{makeKarmaUnlockMessage(karmaUnlocksById.websiteLink)}
|
|
||||||
<a href="/karma" class="hover:text-day-300 underline">
|
|
||||||
Learn about karma
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<InputImageFile
|
||||||
<label class="text-day-200 mb-2 block text-sm" for="pictureFile"> Profile Picture </label>
|
label="Profile Picture"
|
||||||
<div class="mt-2 space-y-2">
|
name="pictureFile"
|
||||||
<input
|
value={user.picture}
|
||||||
transition:persist
|
error={inputErrors.pictureFile}
|
||||||
type="file"
|
square
|
||||||
name="pictureFile"
|
disabled={!user.karmaUnlocks.profilePicture}
|
||||||
id="pictureFile"
|
description={!user.karmaUnlocks.profilePicture
|
||||||
accept="image/*"
|
? `${makeKarmaUnlockMessage(karmaUnlocksById.profilePicture)} [Learn more](/karma)`
|
||||||
class="border-day-500/30 bg-night-800 text-day-300 file:bg-day-500/30 file:text-day-300 focus:border-day-500 focus:ring-day-500 block w-full rounded-md border p-2 file:mr-3 file:rounded-md file:border-0 file:px-3 file:py-1 disabled:cursor-not-allowed disabled:opacity-60"
|
: undefined}
|
||||||
disabled={!user.karmaUnlocks.profilePicture}
|
removeCheckbox={user.picture
|
||||||
/>
|
? {
|
||||||
<p class="text-day-400 text-xs">
|
name: 'removePicture',
|
||||||
Upload a square image for best results. Supported formats: JPG, PNG, WebP, AVIF, JXL. Max size: 5MB.
|
label: 'Remove profile picture',
|
||||||
</p>
|
}
|
||||||
</div>
|
: undefined}
|
||||||
{
|
/>
|
||||||
inputErrors.pictureFile && (
|
|
||||||
<p class="mt-1 text-sm text-red-400">{inputErrors.pictureFile.join(', ')}</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
!user.karmaUnlocks.profilePicture && (
|
|
||||||
<p class="text-day-400 mt-2 flex items-center gap-2 rounded-md text-sm">
|
|
||||||
<Icon name="ri:information-line" class="size-4" />
|
|
||||||
You need 200 karma to have a profile picture.
|
|
||||||
<a href="/karma" class="hover:text-day-300 underline">
|
|
||||||
Learn about karma
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
Reference in New Issue
Block a user