Merge cleanup-batch-post-review — Tier 1 + 2 UAT-Fixes
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 29s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 29s
Sechs atomare Commits aus der Post-Review-Roadmap: - I: RecipeEditor form-lokale Snapshots via untrack() (10 svelte-check WARNINGs weg) - H: Bild-Upload/Delete auf asyncFetch Wrapper - F: --pill-radius CSS-Variable (15 Sites dedupliziert) - G: requireProfile(message?) mit optionalem Parameter - Preview-Guard wenn ?url= fehlt (UAT-Finding) - Kommentar-Delete-Button fuer eigene Kommentare (UAT-Finding) Alles 184/184 Tests gruen, svelte-check 0 Warnings, UAT auf kochwas-dev durchgeklickt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,12 +66,14 @@ export const profileStore = new ProfileStore();
|
|||||||
* Returns the active profile, or null after showing the standard
|
* Returns the active profile, or null after showing the standard
|
||||||
* "kein Profil gewählt" dialog. Use as the first line of any per-profile
|
* "kein Profil gewählt" dialog. Use as the first line of any per-profile
|
||||||
* action so we don't repeat the guard at every call-site.
|
* action so we don't repeat the guard at every call-site.
|
||||||
|
*
|
||||||
|
* `message` ueberschreibt den Default, wenn eine Aktion einen spezifischen
|
||||||
|
* Hinweis braucht (z. B. „um mitzuwünschen" auf der Wunschliste).
|
||||||
*/
|
*/
|
||||||
export async function requireProfile(): Promise<Profile | null> {
|
export async function requireProfile(
|
||||||
|
message = 'Tippe oben rechts auf „Profil wählen", dann klappt die Aktion.'
|
||||||
|
): Promise<Profile | null> {
|
||||||
if (profileStore.active) return profileStore.active;
|
if (profileStore.active) return profileStore.active;
|
||||||
await alertAction({
|
await alertAction({ title: 'Kein Profil gewählt', message });
|
||||||
title: 'Kein Profil gewählt',
|
|
||||||
message: 'Tippe oben rechts auf „Profil wählen", dann klappt die Aktion.'
|
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
padding: 0.5rem 0.9rem;
|
padding: 0.5rem 0.9rem;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
border: 1px solid #cfd9d1;
|
border: 1px solid #cfd9d1;
|
||||||
background: white;
|
background: white;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { untrack } from 'svelte';
|
||||||
import { Plus, Trash2, ChevronUp, ChevronDown, ImagePlus, ImageOff } from 'lucide-svelte';
|
import { Plus, Trash2, ChevronUp, ChevronDown, ImagePlus, ImageOff } from 'lucide-svelte';
|
||||||
import type { Recipe, Ingredient, Step } from '$lib/types';
|
import type { Recipe, Ingredient, Step } from '$lib/types';
|
||||||
import { alertAction, confirmAction } from '$lib/client/confirm.svelte';
|
import { confirmAction } from '$lib/client/confirm.svelte';
|
||||||
|
import { asyncFetch } from '$lib/client/api-fetch-wrapper';
|
||||||
import { requireOnline } from '$lib/client/require-online';
|
import { requireOnline } from '$lib/client/require-online';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -25,7 +27,7 @@
|
|||||||
|
|
||||||
let { recipe, saving = false, onsave, oncancel, onimagechange }: Props = $props();
|
let { recipe, saving = false, onsave, oncancel, onimagechange }: Props = $props();
|
||||||
|
|
||||||
let imagePath = $state<string | null>(recipe.image_path);
|
let imagePath = $state<string | null>(untrack(() => recipe.image_path));
|
||||||
let uploading = $state(false);
|
let uploading = $state(false);
|
||||||
let fileInput: HTMLInputElement | null = $state(null);
|
let fileInput: HTMLInputElement | null = $state(null);
|
||||||
|
|
||||||
@@ -47,18 +49,12 @@
|
|||||||
try {
|
try {
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
fd.append('file', file);
|
fd.append('file', file);
|
||||||
const res = await fetch(`/api/recipes/${recipe.id}/image`, {
|
const res = await asyncFetch(
|
||||||
method: 'POST',
|
`/api/recipes/${recipe.id}/image`,
|
||||||
body: fd
|
{ method: 'POST', body: fd },
|
||||||
});
|
'Upload fehlgeschlagen'
|
||||||
if (!res.ok) {
|
);
|
||||||
const body = await res.json().catch(() => ({}));
|
if (!res) return;
|
||||||
await alertAction({
|
|
||||||
title: 'Upload fehlgeschlagen',
|
|
||||||
message: body.message ?? `HTTP ${res.status}`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const body = await res.json();
|
const body = await res.json();
|
||||||
imagePath = body.image_path;
|
imagePath = body.image_path;
|
||||||
onimagechange?.(imagePath);
|
onimagechange?.(imagePath);
|
||||||
@@ -79,14 +75,12 @@
|
|||||||
if (!requireOnline('Das Entfernen')) return;
|
if (!requireOnline('Das Entfernen')) return;
|
||||||
uploading = true;
|
uploading = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/recipes/${recipe.id}/image`, { method: 'DELETE' });
|
const res = await asyncFetch(
|
||||||
if (!res.ok) {
|
`/api/recipes/${recipe.id}/image`,
|
||||||
await alertAction({
|
{ method: 'DELETE' },
|
||||||
title: 'Entfernen fehlgeschlagen',
|
'Entfernen fehlgeschlagen'
|
||||||
message: `HTTP ${res.status}`
|
);
|
||||||
});
|
if (!res) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
imagePath = null;
|
imagePath = null;
|
||||||
onimagechange?.(null);
|
onimagechange?.(null);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -94,12 +88,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = $state(recipe.title);
|
// Form-lokaler Zustand: Initialwerte aus dem Prop snapshotten (untrack),
|
||||||
let description = $state(recipe.description ?? '');
|
// damit User-Edits nicht von prop-Updates ueberschrieben werden.
|
||||||
let servings = $state<number | ''>(recipe.servings_default ?? '');
|
let title = $state(untrack(() => recipe.title));
|
||||||
let prepMin = $state<number | ''>(recipe.prep_time_min ?? '');
|
let description = $state(untrack(() => recipe.description ?? ''));
|
||||||
let cookMin = $state<number | ''>(recipe.cook_time_min ?? '');
|
let servings = $state<number | ''>(untrack(() => recipe.servings_default ?? ''));
|
||||||
let totalMin = $state<number | ''>(recipe.total_time_min ?? '');
|
let prepMin = $state<number | ''>(untrack(() => recipe.prep_time_min ?? ''));
|
||||||
|
let cookMin = $state<number | ''>(untrack(() => recipe.cook_time_min ?? ''));
|
||||||
|
let totalMin = $state<number | ''>(untrack(() => recipe.total_time_min ?? ''));
|
||||||
|
|
||||||
type DraftIng = {
|
type DraftIng = {
|
||||||
qty: string;
|
qty: string;
|
||||||
@@ -110,15 +106,17 @@
|
|||||||
type DraftStep = { text: string };
|
type DraftStep = { text: string };
|
||||||
|
|
||||||
let ingredients = $state<DraftIng[]>(
|
let ingredients = $state<DraftIng[]>(
|
||||||
recipe.ingredients.map((i) => ({
|
untrack(() =>
|
||||||
qty: i.quantity !== null ? String(i.quantity).replace('.', ',') : '',
|
recipe.ingredients.map((i) => ({
|
||||||
unit: i.unit ?? '',
|
qty: i.quantity !== null ? String(i.quantity).replace('.', ',') : '',
|
||||||
name: i.name,
|
unit: i.unit ?? '',
|
||||||
note: i.note ?? ''
|
name: i.name,
|
||||||
}))
|
note: i.note ?? ''
|
||||||
|
}))
|
||||||
|
)
|
||||||
);
|
);
|
||||||
let steps = $state<DraftStep[]>(
|
let steps = $state<DraftStep[]>(
|
||||||
recipe.steps.map((s) => ({ text: s.text }))
|
untrack(() => recipe.steps.map((s) => ({ text: s.text })))
|
||||||
);
|
);
|
||||||
|
|
||||||
function addIngredient() {
|
function addIngredient() {
|
||||||
|
|||||||
@@ -204,7 +204,7 @@
|
|||||||
.pill {
|
.pill {
|
||||||
padding: 0.15rem 0.55rem;
|
padding: 0.15rem 0.55rem;
|
||||||
background: #eaf4ed;
|
background: #eaf4ed;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: #2b6a3d;
|
color: #2b6a3d;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
padding: 0.3rem 0.65rem;
|
padding: 0.3rem 0.65rem;
|
||||||
background: white;
|
background: white;
|
||||||
border: 1px solid #cfd9d1;
|
border: 1px solid #cfd9d1;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
color: #555;
|
color: #555;
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
padding: 0.6rem 0.85rem 0.6rem 1.1rem;
|
padding: 0.6rem 0.85rem 0.6rem 1.1rem;
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||||
z-index: 500;
|
z-index: 500;
|
||||||
max-width: calc(100% - 2rem);
|
max-width: calc(100% - 2rem);
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
background: #2b6a3d;
|
background: #2b6a3d;
|
||||||
color: white;
|
color: white;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
font-size: 0.88rem;
|
font-size: 0.88rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
padding: 4px;
|
padding: 4px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.dismiss:hover {
|
.dismiss:hover {
|
||||||
|
|||||||
@@ -386,6 +386,9 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
:global(:root) {
|
||||||
|
--pill-radius: 999px;
|
||||||
|
}
|
||||||
:global(html, body) {
|
:global(html, body) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -429,7 +432,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
color: #2b6a3d;
|
color: #2b6a3d;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -621,7 +624,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -636,7 +639,7 @@
|
|||||||
min-width: 18px;
|
min-width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
background: #c53030;
|
background: #c53030;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
|
|||||||
@@ -653,7 +653,7 @@
|
|||||||
padding: 0.4rem 0.85rem;
|
padding: 0.4rem 0.85rem;
|
||||||
background: white;
|
background: white;
|
||||||
border: 1px solid #cfd9d1;
|
border: 1px solid #cfd9d1;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
color: #2b6a3d;
|
color: #2b6a3d;
|
||||||
font-size: 0.88rem;
|
font-size: 0.88rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -760,7 +760,7 @@
|
|||||||
right: 0.4rem;
|
right: 0.4rem;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
border: 0;
|
border: 0;
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
color: #444;
|
color: #444;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
padding: 0.5rem 0.95rem 0.5rem 0.8rem;
|
padding: 0.5rem 0.95rem 0.5rem 0.8rem;
|
||||||
background: white;
|
background: white;
|
||||||
border: 1px solid #e4eae7;
|
border: 1px solid #e4eae7;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #444;
|
color: #444;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
|
|||||||
@@ -185,7 +185,7 @@
|
|||||||
padding: 0.15rem 0.5rem;
|
padding: 0.15rem 0.5rem;
|
||||||
background: #eaf4ed;
|
background: #eaf4ed;
|
||||||
color: #2b6a3d;
|
color: #2b6a3d;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
.actions {
|
.actions {
|
||||||
|
|||||||
@@ -33,7 +33,12 @@
|
|||||||
$effect(() => {
|
$effect(() => {
|
||||||
const u = ($page.url.searchParams.get('url') ?? '').trim();
|
const u = ($page.url.searchParams.get('url') ?? '').trim();
|
||||||
targetUrl = u;
|
targetUrl = u;
|
||||||
if (u) void load(u);
|
if (u) {
|
||||||
|
void load(u);
|
||||||
|
} else {
|
||||||
|
loading = false;
|
||||||
|
errored = 'Kein ?url=-Parameter. Suche zuerst ein Rezept und klicke auf einen Treffer.';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
|
|||||||
@@ -441,7 +441,7 @@
|
|||||||
padding: 0.6rem 0.9rem;
|
padding: 0.6rem 0.9rem;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
border: 1px solid #cfd9d1;
|
border: 1px solid #cfd9d1;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
background: white;
|
background: white;
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, onDestroy, tick } from 'svelte';
|
import { onMount, onDestroy, tick, untrack } from 'svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import {
|
import {
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
let editMode = $state(false);
|
let editMode = $state(false);
|
||||||
let saving = $state(false);
|
let saving = $state(false);
|
||||||
let recipeState = $state(data.recipe);
|
let recipeState = $state(untrack(() => data.recipe));
|
||||||
|
|
||||||
// Einmalige Pulse-Animation beim Aktivieren (nicht beim Wieder-Abwählen).
|
// Einmalige Pulse-Animation beim Aktivieren (nicht beim Wieder-Abwählen).
|
||||||
// Per tick()-Zwischenschritt "aus → an" erzwingen, damit die Animation
|
// Per tick()-Zwischenschritt "aus → an" erzwingen, damit die Animation
|
||||||
@@ -194,6 +194,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteComment(id: number) {
|
||||||
|
const ok = await confirmAction({
|
||||||
|
title: 'Kommentar löschen?',
|
||||||
|
message: 'Der Eintrag verschwindet ohne Umweg.',
|
||||||
|
confirmLabel: 'Löschen',
|
||||||
|
destructive: true
|
||||||
|
});
|
||||||
|
if (!ok) return;
|
||||||
|
if (!requireOnline('Das Löschen')) return;
|
||||||
|
const res = await asyncFetch(
|
||||||
|
`/api/recipes/${data.recipe.id}/comments`,
|
||||||
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify({ comment_id: id })
|
||||||
|
},
|
||||||
|
'Löschen fehlgeschlagen'
|
||||||
|
);
|
||||||
|
if (!res) return;
|
||||||
|
comments = comments.filter((c) => c.id !== id);
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteRecipe() {
|
async function deleteRecipe() {
|
||||||
const ok = await confirmAction({
|
const ok = await confirmAction({
|
||||||
title: 'Rezept löschen?',
|
title: 'Rezept löschen?',
|
||||||
@@ -466,6 +488,16 @@
|
|||||||
<div class="author">{c.author}</div>
|
<div class="author">{c.author}</div>
|
||||||
<div class="text">{c.text}</div>
|
<div class="text">{c.text}</div>
|
||||||
<div class="date">{new Date(c.created_at).toLocaleString('de-DE')}</div>
|
<div class="date">{new Date(c.created_at).toLocaleString('de-DE')}</div>
|
||||||
|
{#if profileStore.active?.id === c.profile_id}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="comment-del"
|
||||||
|
aria-label="Kommentar löschen"
|
||||||
|
onclick={() => void deleteComment(c.id)}
|
||||||
|
>
|
||||||
|
<Trash2 size="14" />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -673,6 +705,26 @@
|
|||||||
border: 1px solid #e4eae7;
|
border: 1px solid #e4eae7;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 0.75rem 0.9rem;
|
padding: 0.75rem 0.9rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.comment-del {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #888;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.comment-del:hover {
|
||||||
|
background: #f3f5f3;
|
||||||
|
color: #b42626;
|
||||||
}
|
}
|
||||||
.comments .author {
|
.comments .author {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { Utensils, Trash2, CookingPot } from 'lucide-svelte';
|
import { Utensils, Trash2, CookingPot } from 'lucide-svelte';
|
||||||
import { profileStore } from '$lib/client/profile.svelte';
|
import { profileStore, requireProfile } from '$lib/client/profile.svelte';
|
||||||
import { wishlistStore } from '$lib/client/wishlist.svelte';
|
import { wishlistStore } from '$lib/client/wishlist.svelte';
|
||||||
import { alertAction, confirmAction } from '$lib/client/confirm.svelte';
|
import { confirmAction } from '$lib/client/confirm.svelte';
|
||||||
import { requireOnline } from '$lib/client/require-online';
|
import { requireOnline } from '$lib/client/require-online';
|
||||||
import type { WishlistEntry, SortKey } from '$lib/server/wishlist/repository';
|
import type { WishlistEntry, SortKey } from '$lib/server/wishlist/repository';
|
||||||
|
|
||||||
@@ -35,15 +35,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function toggleMine(entry: WishlistEntry) {
|
async function toggleMine(entry: WishlistEntry) {
|
||||||
if (!profileStore.active) {
|
const profile = await requireProfile(
|
||||||
await alertAction({
|
'Tippe oben rechts auf „Profil wählen", um mitzuwünschen.'
|
||||||
title: 'Kein Profil gewählt',
|
);
|
||||||
message: 'Tippe oben rechts auf „Profil wählen", um mitzuwünschen.'
|
if (!profile) return;
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!requireOnline('Die Wunschlisten-Aktion')) return;
|
if (!requireOnline('Die Wunschlisten-Aktion')) return;
|
||||||
const profileId = profileStore.active.id;
|
const profileId = profile.id;
|
||||||
if (entry.on_my_wishlist) {
|
if (entry.on_my_wishlist) {
|
||||||
await fetch(`/api/wishlist/${entry.recipe_id}?profile_id=${profileId}`, {
|
await fetch(`/api/wishlist/${entry.recipe_id}?profile_id=${profileId}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
@@ -185,7 +182,7 @@
|
|||||||
padding: 0.4rem 0.85rem;
|
padding: 0.4rem 0.85rem;
|
||||||
background: white;
|
background: white;
|
||||||
border: 1px solid #cfd9d1;
|
border: 1px solid #cfd9d1;
|
||||||
border-radius: 999px;
|
border-radius: var(--pill-radius);
|
||||||
color: #2b6a3d;
|
color: #2b6a3d;
|
||||||
font-size: 0.88rem;
|
font-size: 0.88rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
Reference in New Issue
Block a user