Compare commits

..

3 commits

Author SHA1 Message Date
67dbfb16e1 fix(preferences): null-guard DropdownMenuRadioGroup handlers
Reka UI tightened model-value's type to AcceptableValue, which
includes null. The four inline (v: string) => ... handlers in
PreferencesRow.vue no longer satisfied the prop's expected signature,
breaking TS at the standalone-app build step (forum-app, others).

Drop the string annotation, guard the null case, and cast on the
forward call to preserve the intended narrowing.
2026-05-21 00:02:26 +02:00
a815442990 feat(base/profile): compress avatar uploads with tight 512px cap
Pass tuned compress options to <ImageUpload>: 512px max edge / 200 KB
target. Avatars don't need 1920px and the smaller cap meaningfully
reduces pict-rs storage for a high-volume content type. Closes #59
for the profile consumer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:48:48 +02:00
c1194dadbb feat(market): client-side compress product image uploads
Pass `:compress="true"` to <ImageUpload> so the up-to-5 product images
get resized to 1920px max edge and re-encoded as WebP before hitting
pict-rs. Closes #59 for the market consumer. Default knobs match the
events banner use case — both are gallery-scale images.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:48:48 +02:00
3 changed files with 9 additions and 5 deletions

View file

@ -52,7 +52,7 @@ function notImplemented() {
<DropdownMenuContent align="center" class="w-40"> <DropdownMenuContent align="center" class="w-40">
<DropdownMenuLabel>{{ t('common.nav.theme') }}</DropdownMenuLabel> <DropdownMenuLabel>{{ t('common.nav.theme') }}</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuRadioGroup :model-value="theme" @update:model-value="(v: string) => setTheme(v as 'dark' | 'light' | 'system')"> <DropdownMenuRadioGroup :model-value="theme" @update:model-value="(v) => v != null && setTheme(v as 'dark' | 'light' | 'system')">
<DropdownMenuRadioItem value="light"><Sun class="w-4 h-4 mr-2" />{{ t('common.nav.themeLight') }}</DropdownMenuRadioItem> <DropdownMenuRadioItem value="light"><Sun class="w-4 h-4 mr-2" />{{ t('common.nav.themeLight') }}</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="dark"><Moon class="w-4 h-4 mr-2" />{{ t('common.nav.themeDark') }}</DropdownMenuRadioItem> <DropdownMenuRadioItem value="dark"><Moon class="w-4 h-4 mr-2" />{{ t('common.nav.themeDark') }}</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="system"><Monitor class="w-4 h-4 mr-2" />{{ t('common.nav.themeSystem') }}</DropdownMenuRadioItem> <DropdownMenuRadioItem value="system"><Monitor class="w-4 h-4 mr-2" />{{ t('common.nav.themeSystem') }}</DropdownMenuRadioItem>
@ -71,7 +71,7 @@ function notImplemented() {
<DropdownMenuContent align="center" class="w-44"> <DropdownMenuContent align="center" class="w-44">
<DropdownMenuLabel>{{ t('common.nav.language') }}</DropdownMenuLabel> <DropdownMenuLabel>{{ t('common.nav.language') }}</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuRadioGroup :model-value="currentLocale" @update:model-value="(v: string) => setLocale(v)"> <DropdownMenuRadioGroup :model-value="currentLocale" @update:model-value="(v) => v != null && setLocale(v as string)">
<DropdownMenuRadioItem v-for="l in locales" :key="l.code" :value="l.code"> <DropdownMenuRadioItem v-for="l in locales" :key="l.code" :value="l.code">
<span class="mr-2">{{ l.flag }}</span>{{ l.name }} <span class="mr-2">{{ l.flag }}</span>{{ l.name }}
</DropdownMenuRadioItem> </DropdownMenuRadioItem>
@ -106,7 +106,7 @@ function notImplemented() {
</button> </button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" class="w-40"> <DropdownMenuContent align="end" class="w-40">
<DropdownMenuRadioGroup :model-value="theme" @update:model-value="(v: string) => setTheme(v as 'dark' | 'light' | 'system')"> <DropdownMenuRadioGroup :model-value="theme" @update:model-value="(v) => v != null && setTheme(v as 'dark' | 'light' | 'system')">
<DropdownMenuRadioItem value="light"><Sun class="w-4 h-4 mr-2" />{{ t('common.nav.themeLight') }}</DropdownMenuRadioItem> <DropdownMenuRadioItem value="light"><Sun class="w-4 h-4 mr-2" />{{ t('common.nav.themeLight') }}</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="dark"><Moon class="w-4 h-4 mr-2" />{{ t('common.nav.themeDark') }}</DropdownMenuRadioItem> <DropdownMenuRadioItem value="dark"><Moon class="w-4 h-4 mr-2" />{{ t('common.nav.themeDark') }}</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="system"><Monitor class="w-4 h-4 mr-2" />{{ t('common.nav.themeSystem') }}</DropdownMenuRadioItem> <DropdownMenuRadioItem value="system"><Monitor class="w-4 h-4 mr-2" />{{ t('common.nav.themeSystem') }}</DropdownMenuRadioItem>
@ -129,7 +129,7 @@ function notImplemented() {
</button> </button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" class="w-44"> <DropdownMenuContent align="end" class="w-44">
<DropdownMenuRadioGroup :model-value="currentLocale" @update:model-value="(v: string) => setLocale(v)"> <DropdownMenuRadioGroup :model-value="currentLocale" @update:model-value="(v) => v != null && setLocale(v as string)">
<DropdownMenuRadioItem v-for="l in locales" :key="l.code" :value="l.code"> <DropdownMenuRadioItem v-for="l in locales" :key="l.code" :value="l.code">
<span class="mr-2">{{ l.flag }}</span>{{ l.name }} <span class="mr-2">{{ l.flag }}</span>{{ l.name }}
</DropdownMenuRadioItem> </DropdownMenuRadioItem>

View file

@ -30,7 +30,9 @@
<User class="h-10 w-10 text-muted-foreground" /> <User class="h-10 w-10 text-muted-foreground" />
</div> </div>
<!-- Upload component --> <!-- Upload component. Avatars are small; tighten the
default compress knobs so a 4K phone photo lands as
a ~200 KB 512px WebP. -->
<ImageUpload <ImageUpload
v-model="uploadedPicture" v-model="uploadedPicture"
:multiple="false" :multiple="false"
@ -38,6 +40,7 @@
:max-size-mb="5" :max-size-mb="5"
:disabled="isUpdating" :disabled="isUpdating"
:allow-camera="true" :allow-camera="true"
:compress="{ maxWidthOrHeight: 512, maxSizeMB: 0.2 }"
placeholder="Upload picture" placeholder="Upload picture"
accept="image/*" accept="image/*"
/> />

View file

@ -136,6 +136,7 @@
:show-primary-button="true" :show-primary-button="true"
:disabled="isCreating" :disabled="isCreating"
:allow-camera="true" :allow-camera="true"
:compress="true"
placeholder="Add product photos" placeholder="Add product photos"
/> />
<FormMessage /> <FormMessage />