inquiry ui
This commit is contained in:
parent
505434a2d8
commit
6c4ce12b45
@ -40,7 +40,7 @@
|
|||||||
--warning: oklch(0.84 0.16 84);
|
--warning: oklch(0.84 0.16 84);
|
||||||
--error: oklch(0.577 0.245 27.325);
|
--error: oklch(0.577 0.245 27.325);
|
||||||
--positive: oklch(0.5 0.2067 147.18);
|
--positive: oklch(0.5 0.2067 147.18);
|
||||||
--edit: oklch(0.5852 0.2263 260.47);
|
--action: oklch(0.5852 0.2263 260.47);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@ -78,7 +78,7 @@
|
|||||||
--warning: oklch(0.84 0.16 84);
|
--warning: oklch(0.84 0.16 84);
|
||||||
--error: oklch(0.704 0.191 22.216);
|
--error: oklch(0.704 0.191 22.216);
|
||||||
--positive: oklch(0.7522 0.2067 147.18);
|
--positive: oklch(0.7522 0.2067 147.18);
|
||||||
--edit: oklch(0.6098 0.1872 260.47);
|
--action: oklch(0.6098 0.1872 260.47);
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
@ -120,7 +120,7 @@
|
|||||||
--color-warning: var(--warning);
|
--color-warning: var(--warning);
|
||||||
--color-error: var(--error);
|
--color-error: var(--error);
|
||||||
--color-positive: var(--positive);
|
--color-positive: var(--positive);
|
||||||
--color-edit: var(--edit);
|
--color-action: var(--action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</Dialog.Header>
|
</Dialog.Header>
|
||||||
|
|
||||||
<form method="post" action="?/inquire">
|
<form method="post" action="?/claim">
|
||||||
<Field.Group>
|
<Field.Group>
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
import { Label } from '$lib/components/ui/label';
|
import { Label } from '$lib/components/ui/label';
|
||||||
import * as Field from '$lib/components/ui/field';
|
import * as Field from '$lib/components/ui/field';
|
||||||
import { Separator } from '$lib/components/ui/separator';
|
import { Separator } from '$lib/components/ui/separator';
|
||||||
|
import TrashIcon from '@lucide/svelte/icons/trash';
|
||||||
import ImageUpload from '$lib/components/custom/image-upload/image-upload.svelte';
|
import ImageUpload from '$lib/components/custom/image-upload/image-upload.svelte';
|
||||||
import { genDescription } from '$lib/db/items.remote';
|
import { genDescription } from '$lib/db/items.remote';
|
||||||
import { EMAIL_REGEX_STRING } from '$lib/consts';
|
import { EMAIL_REGEX_STRING } from '$lib/consts';
|
||||||
@ -14,6 +14,9 @@
|
|||||||
import { Textarea } from '$lib/components/ui/textarea';
|
import { Textarea } from '$lib/components/ui/textarea';
|
||||||
import * as Card from '$lib/components/ui/card';
|
import * as Card from '$lib/components/ui/card';
|
||||||
import { dateFormatOptions } from '$lib/shared';
|
import { dateFormatOptions } from '$lib/shared';
|
||||||
|
import { Badge } from '$lib/components/ui/badge';
|
||||||
|
import { deleteInquiry } from '$lib/db/inquiries.remote';
|
||||||
|
import { invalidateAll } from '$app/navigation';
|
||||||
|
|
||||||
let { open = $bindable(), item = $bindable() }: { open: boolean, item: Item | undefined } = $props();
|
let { open = $bindable(), item = $bindable() }: { open: boolean, item: Item | undefined } = $props();
|
||||||
|
|
||||||
@ -32,7 +35,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Dialog.Root bind:open>
|
<Dialog.Root bind:open>
|
||||||
<Dialog.Content class="max-w-[calc(100%-2rem)] md:max-w-3xl">
|
<Dialog.Content class={item?.threads ? 'max-w-[calc(100%-2rem)] md:max-w-3xl' : ''}>
|
||||||
<Dialog.Header>
|
<Dialog.Header>
|
||||||
<Dialog.Title>Manage Item</Dialog.Title>
|
<Dialog.Title>Manage Item</Dialog.Title>
|
||||||
<Dialog.Description>
|
<Dialog.Description>
|
||||||
@ -122,16 +125,30 @@
|
|||||||
|
|
||||||
<h2 class="text-lg leading-none font-semibold">Item inquiries:</h2>
|
<h2 class="text-lg leading-none font-semibold">Item inquiries:</h2>
|
||||||
{#each item.threads as thread (thread)}
|
{#each item.threads as thread (thread)}
|
||||||
<a href="/items/{item.id}/inquiries/{thread.id}" class="mt-4">
|
|
||||||
|
|
||||||
<Card.Root>
|
<Card.Root class="mt-4">
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
<Card.Title>Date</Card.Title>
|
<Card.Title>{thread.createdAt.toLocaleDateString('en-US', dateFormatOptions)}
|
||||||
<Card.Content>Inquirer: {thread.messages[0].body}
|
<!--{#if thread.messages[thread.messages.length - 1].sender === 'inquirer'}-->
|
||||||
</Card.Content>
|
<!-- t-->
|
||||||
</Card.Header>
|
<!--{/if}-->
|
||||||
</Card.Root>
|
</Card.Title>
|
||||||
</a>
|
<Card.Description
|
||||||
|
>{thread.messages[0].body}
|
||||||
|
</Card.Description
|
||||||
|
>
|
||||||
|
|
||||||
|
<Card.Action class="items-center flex gap-2">
|
||||||
|
<a href="/items/{item.id}/inquiries/{thread.id}"
|
||||||
|
class="{buttonVariants({variant: 'ghost'})} text-action">Reply?</a>
|
||||||
|
<Button variant="ghost" class="text-destructive"
|
||||||
|
onclick={() => {deleteInquiry(thread.id); invalidateAll();}}>
|
||||||
|
<TrashIcon />
|
||||||
|
</Button>
|
||||||
|
</Card.Action>
|
||||||
|
|
||||||
|
</Card.Header>
|
||||||
|
</Card.Root>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
|
|
||||||
let { open = $bindable(), item }: { open: boolean, item: Item | undefined } = $props();
|
let { open = $bindable(), item }: { open: boolean, item: Item | undefined } = $props();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Dialog.Root bind:open>
|
<Dialog.Root bind:open>
|
||||||
@ -23,7 +24,7 @@
|
|||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</Dialog.Header>
|
</Dialog.Header>
|
||||||
|
|
||||||
<form method="post" action="?/inquire">
|
<form method="post" action={'?/inquire&id=' + item?.id}>
|
||||||
<Field.Group>
|
<Field.Group>
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
|
|
||||||
@ -53,7 +54,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Field.Field>
|
<Field.Field>
|
||||||
<Field.Label for="description">
|
<Field.Label for="inquiry">
|
||||||
Please describe your inquiry <span class="text-error">*</span>
|
Please describe your inquiry <span class="text-error">*</span>
|
||||||
</Field.Label>
|
</Field.Label>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@ -98,7 +98,7 @@
|
|||||||
Deny
|
Deny
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
<Button variant="ghost" class="text-edit"
|
<Button variant="ghost" class="text-action"
|
||||||
onclick={() => {editCallback(item)}}>
|
onclick={() => {editCallback(item)}}>
|
||||||
<PencilIcon />
|
<PencilIcon />
|
||||||
Manage
|
Manage
|
||||||
@ -107,7 +107,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="mt-2 justify-between flex">
|
<div class="mt-2 justify-between flex">
|
||||||
<Button variant="ghost" class="text-edit"
|
<Button variant="ghost" class="text-action"
|
||||||
onclick={() => {inquireCallback(item)}}>
|
onclick={() => {inquireCallback(item)}}>
|
||||||
<NotebookPenIcon />
|
<NotebookPenIcon />
|
||||||
Inquire
|
Inquire
|
||||||
|
|||||||
@ -34,7 +34,7 @@
|
|||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</Dialog.Header>
|
</Dialog.Header>
|
||||||
|
|
||||||
<form method="post" action="?/create" enctype="multipart/form-data">
|
<form method="post" action="/items?/create" enctype="multipart/form-data">
|
||||||
<Field.Group>
|
<Field.Group>
|
||||||
<ImageUpload onSelect={onSelect} required />
|
<ImageUpload onSelect={onSelect} required />
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export const EXPIRE_REMINDER_DAYS = 30;
|
|||||||
// /(?:[a-z0-9!#$%&'*+\/=?^`\{-\}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`\{-\}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
|
// /(?:[a-z0-9!#$%&'*+\/=?^`\{-\}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`\{-\}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
|
||||||
|
|
||||||
const EMAIL_REGEX =
|
const EMAIL_REGEX =
|
||||||
/^(?!\.)(?!.*\.\.)([a-z0-9_'+\-.]*)[a-z0-9_'+-]@([a-z0-9][a-z0-9-]*\.)+[a-z]{2,}$/i;
|
/^(?!\.)(?!.*\.\.)([a-z0-9_'+\-\.]*)[a-z0-9_'+\-]@([a-z0-9][a-z0-9\-]*\.)+[a-z]{2,}$/i;
|
||||||
|
|
||||||
// Replace single quote with HTML entity or remove it from the character class
|
// Replace single quote with HTML entity or remove it from the character class
|
||||||
export const EMAIL_REGEX_STRING = EMAIL_REGEX.source.replace(/'/g, ''');
|
export const EMAIL_REGEX_STRING = EMAIL_REGEX.source.replace(/'/g, ''');
|
||||||
|
|||||||
11
src/lib/db/inquiries.remote.ts
Normal file
11
src/lib/db/inquiries.remote.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { getRequestEvent, query } from '$app/server';
|
||||||
|
import * as v from 'valibot';
|
||||||
|
import sql from '$lib/db/db.server';
|
||||||
|
import { verifyJWT } from '$lib/auth/index.server';
|
||||||
|
|
||||||
|
export const deleteInquiry = query(v.number(), async (id) => {
|
||||||
|
const { cookies } = getRequestEvent();
|
||||||
|
verifyJWT(cookies);
|
||||||
|
|
||||||
|
await sql`DELETE FROM inquiry_threads WHERE id = ${id};`;
|
||||||
|
});
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { getRequestEvent, query } from '$app/server';
|
import { command, getRequestEvent, query } from '$app/server';
|
||||||
import * as v from 'valibot';
|
import * as v from 'valibot';
|
||||||
import sql from '$lib/db/db.server';
|
import sql from '$lib/db/db.server';
|
||||||
import { verifyJWT } from '$lib/auth/index.server';
|
import { verifyJWT } from '$lib/auth/index.server';
|
||||||
@ -9,7 +9,7 @@ export const genDescription = query(async () => {
|
|||||||
return 'A matte black water bottle with a black lid and a "BKLYN BENTO" logo on the side, resting on a tree trunk in a forest.';
|
return 'A matte black water bottle with a black lid and a "BKLYN BENTO" logo on the side, resting on a tree trunk in a forest.';
|
||||||
});
|
});
|
||||||
|
|
||||||
export const approveDenyItem = query(
|
export const approveDenyItem = command(
|
||||||
v.object({ id: v.number(), approved: v.boolean() }),
|
v.object({ id: v.number(), approved: v.boolean() }),
|
||||||
async ({ id, approved }) => {
|
async ({ id, approved }) => {
|
||||||
const { cookies } = getRequestEvent();
|
const { cookies } = getRequestEvent();
|
||||||
|
|||||||
@ -5,8 +5,6 @@ import sql from '$lib/db/db.server';
|
|||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import { writeFileSync } from 'node:fs';
|
import { writeFileSync } from 'node:fs';
|
||||||
import type { Item } from '$lib/types/item.server';
|
import type { Item } from '$lib/types/item.server';
|
||||||
import type { Message } from '$lib/types/inquiries.server';
|
|
||||||
import { Sender } from '$lib/types/inquiries.server';
|
|
||||||
import { getFormString, getRequiredFormString } from '$lib/shared';
|
import { getFormString, getRequiredFormString } from '$lib/shared';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ url, locals }) => {
|
export const load: PageServerLoad = async ({ url, locals }) => {
|
||||||
@ -15,59 +13,92 @@ export const load: PageServerLoad = async ({ url, locals }) => {
|
|||||||
// If the user is logged in, fetch items together with their threads (each thread contains its first message)
|
// If the user is logged in, fetch items together with their threads (each thread contains its first message)
|
||||||
if (locals && locals.user) {
|
if (locals && locals.user) {
|
||||||
try {
|
try {
|
||||||
type DBMessage = {
|
const items: Item[] = await sql`
|
||||||
id: number;
|
SELECT
|
||||||
sender: keyof typeof Sender | string;
|
i.*,
|
||||||
body: string;
|
json_agg(
|
||||||
createdAt: string;
|
jsonb_build_object(
|
||||||
};
|
'id', t.id,
|
||||||
type DBThread = { id: number; messages: DBMessage[] };
|
'item_id', t.item_id,
|
||||||
type RowsItem = { threads: DBThread[]; [key: string]: unknown };
|
'created_at', t.created_at,
|
||||||
|
'messages', m.messages
|
||||||
const rows = (await sql`
|
)
|
||||||
SELECT i.*, COALESCE(
|
) FILTER (WHERE t.id IS NOT NULL) AS threads
|
||||||
(
|
FROM items i
|
||||||
SELECT json_agg(json_build_object('id', t.id, 'messages', json_build_array(json_build_object('id', m.id, 'sender', m.sender, 'body', m.body, 'createdAt', m.created_at))) ORDER BY t.id)
|
LEFT JOIN inquiry_threads t
|
||||||
FROM inquiry_threads t
|
ON t.item_id = i.id
|
||||||
LEFT JOIN LATERAL (
|
LEFT JOIN LATERAL (
|
||||||
SELECT id, sender, body, created_at
|
SELECT
|
||||||
FROM inquiry_messages
|
json_agg(im.* ORDER BY im.created_at) AS messages
|
||||||
WHERE thread_id = t.id
|
FROM inquiry_messages im
|
||||||
ORDER BY created_at ASC
|
WHERE im.thread_id = t.id
|
||||||
LIMIT 1
|
) m ON TRUE
|
||||||
) m ON true
|
GROUP BY i.id;
|
||||||
WHERE t.item_id = i.id
|
`;
|
||||||
), '[]'::json
|
items.forEach((item) =>
|
||||||
) AS threads
|
item.threads?.forEach((thread) => (thread.createdAt = new Date(thread.createdAt)))
|
||||||
FROM items i
|
);
|
||||||
${
|
|
||||||
searchQuery
|
|
||||||
? sql`WHERE word_similarity(${searchQuery}, i.description) > 0.3
|
|
||||||
ORDER BY word_similarity(${searchQuery}, i.description)`
|
|
||||||
: sql``
|
|
||||||
};
|
|
||||||
`) as RowsItem[];
|
|
||||||
|
|
||||||
// `rows` contains items with a `threads` JSON column. Attach parsed threads to each item.
|
|
||||||
const items: Item[] = rows.map((r) => {
|
|
||||||
const item: Record<string, unknown> = { ...r };
|
|
||||||
const rawThreads = (r.threads || []) as DBThread[];
|
|
||||||
item.threads = rawThreads.map((t) => ({
|
|
||||||
id: t.id,
|
|
||||||
messages: (t.messages || []).map((m) => ({
|
|
||||||
id: m.id,
|
|
||||||
// coerce sender to our Sender enum type if possible
|
|
||||||
sender: (Object.values(Sender).includes(m.sender as Sender)
|
|
||||||
? (m.sender as Sender)
|
|
||||||
: (m.sender as unknown)) as Message['sender'],
|
|
||||||
body: m.body,
|
|
||||||
createdAt: new Date(m.createdAt)
|
|
||||||
}))
|
|
||||||
}));
|
|
||||||
return item as unknown as Item;
|
|
||||||
});
|
|
||||||
|
|
||||||
return { items };
|
return { items };
|
||||||
|
|
||||||
|
// type DBMessage = {
|
||||||
|
// id: number;
|
||||||
|
// sender: keyof typeof Sender | string;
|
||||||
|
// body: string;
|
||||||
|
// createdAt: string;
|
||||||
|
// };
|
||||||
|
// type DBThread = { id: number; messages: DBMessage[] };
|
||||||
|
// type RowsItem = { threads: DBThread[]; [key: string]: unknown };
|
||||||
|
//
|
||||||
|
// const rows = (await sql`
|
||||||
|
// SELECT i.*, COALESCE(
|
||||||
|
// (
|
||||||
|
// SELECT json_agg(json_build_object('id', t.id, 'messages', json_build_array(json_build_object('id', m.id, 'sender', m.sender, 'body', m.body, 'createdAt', m.created_at))), 'createdAt' , t.created_at ORDER BY t.id)
|
||||||
|
// FROM inquiry_threads t
|
||||||
|
// LEFT JOIN LATERAL (
|
||||||
|
// SELECT id, sender, body, created_at
|
||||||
|
// FROM inquiry_messages
|
||||||
|
// WHERE thread_id = t.id
|
||||||
|
// ORDER BY created_at ASC
|
||||||
|
// LIMIT 1
|
||||||
|
// ) m ON true
|
||||||
|
// WHERE t.item_id = i.id
|
||||||
|
// ), '[]'::json
|
||||||
|
// ) AS threads
|
||||||
|
// FROM items i
|
||||||
|
// ${
|
||||||
|
// searchQuery
|
||||||
|
// ? sql`WHERE word_similarity(${searchQuery}, i.description) > 0.3
|
||||||
|
// ORDER BY word_similarity(${searchQuery}, i.description)`
|
||||||
|
// : sql``
|
||||||
|
// };
|
||||||
|
// `) as RowsItem[];
|
||||||
|
//
|
||||||
|
// // `rows` contains items with a `threads` JSON column. Attach parsed threads to each item.
|
||||||
|
// const items: Item[] = rows.map((r) => {
|
||||||
|
// const item: Record<string, unknown> = { ...r };
|
||||||
|
// const rawThreads = (r.threads || []) as DBThread[];
|
||||||
|
// console.log(rawThreads);
|
||||||
|
// if (rawThreads.length === 0) {
|
||||||
|
// item.threads = null;
|
||||||
|
// } else {
|
||||||
|
// item.threads = rawThreads.map((t) => ({
|
||||||
|
// id: t.id,
|
||||||
|
// messages: (t.messages || []).map((m) => ({
|
||||||
|
// id: m.id,
|
||||||
|
// // coerce sender to our Sender enum type if possible
|
||||||
|
// sender: (Object.values(Sender).includes(m.sender as Sender)
|
||||||
|
// ? (m.sender as Sender)
|
||||||
|
// : (m.sender as unknown)) as Message['sender'],
|
||||||
|
// body: m.body,
|
||||||
|
// createdAt: new Date(m.createdAt)
|
||||||
|
// }))
|
||||||
|
// }));
|
||||||
|
// }
|
||||||
|
// return item as unknown as Item;
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// return { items };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load items with threads:', err);
|
console.error('Failed to load items with threads:', err);
|
||||||
// Fallback to non-joined fetch so the page still loads
|
// Fallback to non-joined fetch so the page still loads
|
||||||
@ -128,7 +159,10 @@ export const actions: Actions = {
|
|||||||
const inputBuffer = Buffer.from(await file.arrayBuffer());
|
const inputBuffer = Buffer.from(await file.arrayBuffer());
|
||||||
|
|
||||||
// Detect format (Sharp does this internally)
|
// Detect format (Sharp does this internally)
|
||||||
const image = sharp(inputBuffer);
|
const image = sharp(inputBuffer).rotate();
|
||||||
|
|
||||||
|
// image = image.rotate();
|
||||||
|
|
||||||
const metadata = await image.metadata();
|
const metadata = await image.metadata();
|
||||||
|
|
||||||
if (metadata.format === 'jpeg') {
|
if (metadata.format === 'jpeg') {
|
||||||
@ -137,6 +171,7 @@ export const actions: Actions = {
|
|||||||
} else {
|
} else {
|
||||||
// Convert to JPG
|
// Convert to JPG
|
||||||
outputBuffer = await image
|
outputBuffer = await image
|
||||||
|
|
||||||
.jpeg({ quality: 90 }) // adjust if needed
|
.jpeg({ quality: 90 }) // adjust if needed
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
}
|
}
|
||||||
@ -238,7 +273,7 @@ export const actions: Actions = {
|
|||||||
// Convert File → Buffer
|
// Convert File → Buffer
|
||||||
const inputBuffer = Buffer.from(await file.arrayBuffer());
|
const inputBuffer = Buffer.from(await file.arrayBuffer());
|
||||||
|
|
||||||
const image = sharp(inputBuffer);
|
const image = sharp(inputBuffer).rotate();
|
||||||
const metadata = await image.metadata();
|
const metadata = await image.metadata();
|
||||||
|
|
||||||
if (metadata.format === 'jpeg') {
|
if (metadata.format === 'jpeg') {
|
||||||
@ -280,6 +315,7 @@ export const actions: Actions = {
|
|||||||
inquire: async ({ request, url }) => {
|
inquire: async ({ request, url }) => {
|
||||||
const id = url.searchParams.get('id');
|
const id = url.searchParams.get('id');
|
||||||
if (!id) {
|
if (!id) {
|
||||||
|
console.log('No id provided!');
|
||||||
return fail(400, { message: 'Missing id!', success: false });
|
return fail(400, { message: 'Missing id!', success: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,13 +328,21 @@ export const actions: Actions = {
|
|||||||
inquiry = getRequiredFormString(data, 'inquiry');
|
inquiry = getRequiredFormString(data, 'inquiry');
|
||||||
email = getRequiredFormString(data, 'email');
|
email = getRequiredFormString(data, 'email');
|
||||||
|
|
||||||
const response = sql`
|
const response = await sql`
|
||||||
WITH new_thread AS (
|
WITH new_thread AS (
|
||||||
INSERT INTO inquiry_threads (item_id) VALUES (${id})
|
INSERT INTO inquiry_threads (item_id, inquirer_email)
|
||||||
RETURNING id
|
VALUES (${id}, ${email})
|
||||||
) INSERT INTO inquiry_messages (thread_id, sender, body)
|
RETURNING id
|
||||||
VALUES (new_thread.id, 'inquirer', ${inquiry})
|
)
|
||||||
RETURNING new_thread.id, id;
|
INSERT INTO inquiry_messages (thread_id, sender, body)
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
'inquirer',
|
||||||
|
${inquiry}
|
||||||
|
FROM new_thread
|
||||||
|
RETURNING
|
||||||
|
thread_id,
|
||||||
|
id AS message_id;
|
||||||
`;
|
`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return fail(400, {
|
return fail(400, {
|
||||||
|
|||||||
@ -15,6 +15,6 @@ export const actions = {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
return fail(400, { message: e instanceof Error ? e.message : 'Unknown error occurred' });
|
return fail(400, { message: e instanceof Error ? e.message : 'Unknown error occurred' });
|
||||||
}
|
}
|
||||||
throw redirect(303, '/');
|
throw redirect(303, '/items');
|
||||||
}
|
}
|
||||||
} satisfies Actions;
|
} satisfies Actions;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user