feat: showing description on image page in frontend

This commit is contained in:
2025-07-24 14:23:20 +01:00
parent 37f966e508
commit 0058cdce40
11 changed files with 18 additions and 649 deletions

View File

@ -262,6 +262,7 @@ func (m UserModel) GetUserImages(ctx context.Context, userId uuid.UUID) ([]UserI
UserImages.AllColumns,
Image.ID,
Image.ImageName,
Image.Description,
).
FROM(UserImages.INNER_JOIN(Image, Image.ID.EQ(UserImages.ImageID))).
WHERE(UserImages.UserID.EQ(UUID(userId)))

View File

@ -3,7 +3,6 @@ import type { UserImage } from "../../network";
import { SearchCardContact } from "./SearchCardContact";
import { SearchCardEvent } from "./SearchCardEvent";
import { SearchCardLocation } from "./SearchCardLocation";
import { SearchCardNote } from "./SearchCardNote";
const UnwrappedSearchCard = (props: { item: UserImage }) => {
const { item } = props;
@ -13,8 +12,6 @@ const UnwrappedSearchCard = (props: { item: UserImage }) => {
return <SearchCardLocation item={item} />;
case "event":
return <SearchCardEvent item={item} />;
case "note":
return <SearchCardNote item={item} />;
case "contact":
return <SearchCardContact item={item} />;
default:

View File

@ -1,27 +0,0 @@
import SolidjsMarkdown from "solidjs-markdown";
import { IconNote } from "@tabler/icons-solidjs";
import type { UserImage } from "../../network";
type Props = {
item: Extract<UserImage, { type: "note" }>;
};
export const SearchCardNote = ({ item }: Props) => {
const { data } = item;
return (
<div class="h-full inset-0 p-3 bg-green-50">
<div class="flex mb-1 items-center gap-1">
<IconNote size={14} class="text-neutral-500" />
<p class="text-xs text-neutral-500">Note</p>
</div>
<p class="text-sm text-neutral-900 font-bold mb-1">
{data.Name.length > 0 ? data.Name : "Unknown 🐞"}
</p>
<p class="text-xs text-neutral-700">
<SolidjsMarkdown>{data.Content}</SolidjsMarkdown>
</p>
</div>
);
};

View File

@ -1,30 +0,0 @@
import { Separator } from "@kobalte/core/separator";
import { IconReceipt } from "@tabler/icons-solidjs";
import type { Receipt } from "../../network/types";
type Props = {
item: Receipt;
};
export const SearchCardReceipt = ({ item }: Props) => {
const { data } = item;
return (
<div class="h-full inset-0 p-3 bg-yellow-50">
<div class="grid grid-cols-[auto_20px] gap-1 mb-1">
<p class="text-sm text-neutral-900 font-bold">
{data.orderNumber} - {data.vendor}
</p>
<IconReceipt size={20} class="text-neutral-500 mt-1" />
</div>
<p class="text-xs text-neutral-500">
{data.shippingAddress.address}
</p>
<Separator class="my-2" />
<p class="text-xs text-neutral-500 line-clamp-2 overflow-hidden">
{data.amount} {data.currency}
</p>
</div>
);
};

View File

@ -1,26 +0,0 @@
import { Separator } from "@kobalte/core/separator";
import { IconLink } from "@tabler/icons-solidjs";
import type { Website } from "../../network/types";
type Props = {
item: Website;
};
export const SearchCardWebsite = ({ item }: Props) => {
const { data } = item;
return (
<div class="h-full inset-0 p-3 bg-blue-50">
<div class="grid grid-cols-[auto_20px] gap-1 mb-1">
<p class="text-sm text-neutral-900 font-bold">{data.title}</p>
<IconLink size={20} class="text-neutral-500 mt-1" />
</div>
<p class="text-xs text-neutral-500">{data.url}</p>
<Separator class="my-2" />
<p class="text-xs text-neutral-500 line-clamp-2 overflow-hidden">
{data.description}
</p>
</div>
);
};

View File

@ -104,7 +104,6 @@ export const SearchImageContextProvider: Component<ParentProps> = (props) => {
contact: [],
event: [],
location: [],
note: [],
};
for (const category of data()?.ImageProperties ?? []) {

View File

@ -22,7 +22,7 @@ type BaseRequestParams = Partial<{
}>;
// export const base = "https://haystack.johncosta.tech";
export const base = "http://192.168.1.199:3040";
export const base = "http://localhost:3040";
const getBaseRequest = ({ path, body, method }: BaseRequestParams): Request => {
return new Request(`${base}/${path}`, {
@ -118,15 +118,6 @@ const eventValidator = strictObject({
Images: array(pipe(string(), uuid())),
});
const noteValidator = strictObject({
ID: pipe(string(), uuid()),
CreatedAt: pipe(string()),
Name: string(),
Description: nullable(string()),
Content: string(),
Images: array(pipe(string(), uuid())),
});
const locationDataType = strictObject({
type: literal("location"),
data: locationValidator,
@ -137,11 +128,6 @@ const eventDataType = strictObject({
data: eventValidator,
});
const noteDataType = strictObject({
type: literal("note"),
data: noteValidator,
});
const contactDataType = strictObject({
type: literal("contact"),
data: contactValidator,
@ -150,7 +136,6 @@ const contactDataType = strictObject({
const dataTypeValidator = variant("type", [
locationDataType,
eventDataType,
noteDataType,
contactDataType,
]);
@ -164,6 +149,7 @@ const userImageValidator = strictObject({
Image: strictObject({
ID: pipe(string(), uuid()),
ImageName: string(),
Description: string(),
Image: null_(),
}),
});
@ -175,6 +161,7 @@ const userProcessingImageValidator = strictObject({
Image: strictObject({
ID: pipe(string(), uuid()),
ImageName: string(),
Description: string(),
Image: null_(),
}),
Status: union([

View File

@ -1,436 +0,0 @@
import type { DataArray, DataItem } from "./types";
const getRawData = (item: DataItem) => {
return Object.values(item.data).join(" ");
};
export const sampleData: DataArray = [
{
id: "1",
type: "Event",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.title;
},
data: {
title: "Startup Mixer",
dateTime: {
start: "2025-06-01T19:00:00+01:00",
end: "2025-06-01T23:00:00+01:00",
},
location: "The Kings Arms, 27 Ropemaker St, London EC2Y 9LY",
description:
"Casual networking event for tech entrepreneurs and investors in London.",
organizer: {
name: "London Startup Network",
email: "events@londonstartupnetwork.co.uk",
},
attendees: ["Alex Smith", "Sofia Rodriguez"],
category: "Networking",
},
},
{
id: "2",
type: "Contact",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.name;
},
data: {
name: "João Silva",
phoneNumber: "+351 912 345 678",
emailAddress: "joao.silva@example.pt",
address: "Rua do Carmo 12, 1200-161 Lisboa, Portugal",
organization: "PortoTech Solutions",
title: "Marketing Manager",
notes: "Met at Web Summit Lisbon 2024",
},
},
{
id: "3",
type: "Location",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.name;
},
data: {
name: "Barman Dictat",
address: "14 Khreshchatyk St., Kyiv, Ukraine",
category: "Bar",
description:
"Stylish cocktail bar in the heart of Kyiv with an extensive menu of craft cocktails and a sophisticated atmosphere",
},
},
{
id: "4",
type: "Note",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.title;
},
data: {
title: "Q2 2025 Marketing Strategy",
keywords: ["strategy", "digital marketing", "Q2", "2025"],
content:
"## Executive Summary\n\nOur Q2 2025 marketing strategy focuses on expanding our digital presence and increasing customer engagement across all platforms. We will leverage AI-driven personalization to enhance user experience and implement a multi-channel approach to reach our target demographics more effectively.\n\n### Key Objectives\n\n1. Increase website traffic by 30% through SEO optimization and content marketing.\n2. Boost social media engagement rates by 25% using interactive campaigns and influencer partnerships.\n3. Implement a new customer loyalty program to improve retention rates by 15%.\n\nBy aligning our marketing efforts with emerging trends and customer preferences, we aim to solidify our market position and drive sustainable growth throughout Q2 and beyond.",
},
},
{
id: "5",
type: "Website",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.title;
},
data: {
url: "https://www.attio.com",
title: "Attio",
description:
"Attio is the AI-native CRM that builds, scales and grows your company to the next level.",
category: "SaaS",
},
},
{
id: "6",
type: "Receipt",
get rawData() {
return getRawData(this);
},
get title() {
return `${this.data.orderNumber} - ${this.data.vendor}`;
},
data: {
receiptDate: "2025-03-12T20:15:30+01:00",
orderNumber: "ORD12345",
amount: 49.99,
currency: "GBP",
vendor: "Zara Online Store",
items: [
{
name: "Slim Fit Dress Shirt",
quantity: 1,
price: 49.99,
currency: "GBP",
},
],
paymentMethod: "Visa",
shippingAddress: {
name: "Alex Smith",
address: "123 High St, London, EC2A 3AZ",
},
category: "Online Shopping",
},
},
{
id: "7",
type: "Event",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.title;
},
data: {
title: "AI in Healthcare Summit",
dateTime: {
start: "2025-07-15T09:00:00+01:00",
end: "2025-07-15T17:00:00+01:00",
},
location:
"Royal College of Physicians, 11 St Andrews Pl, London NW1 4LE",
description:
"Annual conference exploring the latest developments in AI applications for healthcare",
organizer: {
name: "HealthTech Alliance",
email: "events@healthtechalliance.org",
},
attendees: [
"Dr. Sarah Chen",
"Prof. James Wilson",
"Dr. Maria Santos",
],
category: "Conference",
},
},
{
id: "8",
type: "Contact",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.name;
},
data: {
name: "Emma Schmidt",
phoneNumber: "+49 30 12345678",
emailAddress: "e.schmidt@techberlin.de",
address: "Friedrichstraße 123, 10117 Berlin, Germany",
organization: "TechBerlin GmbH",
title: "Chief Technology Officer",
notes: "Key contact for Berlin tech scene, met at TechFest 2024",
},
},
{
id: "9",
type: "Location",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.name;
},
data: {
name: "Digital Nomad Hub",
address: "Calle Princesa 25, 08001 Barcelona, Spain",
category: "Coworking Space",
description:
"Modern coworking space with high-speed internet, meeting rooms, and a vibrant community of international remote workers",
},
},
{
id: "10",
type: "Website",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.title;
},
data: {
url: "https://www.techcrunch.com",
title: "TechCrunch",
description:
"Leading technology media platform covering startups, tech news, and venture capital",
category: "Tech News",
},
},
{
id: "11",
type: "Note",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.title;
},
data: {
title: "Product Roadmap 2025",
keywords: ["product development", "strategy", "features", "2025"],
content:
"## Overview\n\nPriority features for 2025:\n\n1. AI-powered customer insights dashboard\n2. Integration with major CRM platforms\n3. Mobile app redesign\n4. Enhanced analytics suite\n\n### Timeline\n- Q1: Research and planning\n- Q2: Development phase 1\n- Q3: Beta testing\n- Q4: Full release",
},
},
{
id: "12",
type: "Receipt",
get rawData() {
return getRawData(this);
},
get title() {
return `${this.data.orderNumber} - ${this.data.vendor}`;
},
data: {
receiptDate: "2025-03-15T13:45:00+01:00",
orderNumber: "INV789012",
amount: 1299.99,
currency: "EUR",
vendor: "Apple Store",
items: [
{
name: "MacBook Air M3",
quantity: 1,
price: 1299.99,
currency: "EUR",
},
],
paymentMethod: "MasterCard",
shippingAddress: {
name: "Emma Schmidt",
address: "Friedrichstraße 123, 10117 Berlin, Germany",
},
category: "Electronics",
},
},
{
id: "13",
type: "Event",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.title;
},
data: {
title: "Sustainable Tech Workshop",
dateTime: {
start: "2025-08-20T14:00:00+02:00",
end: "2025-08-20T17:00:00+02:00",
},
location: "GreenTech Hub, Prinsengracht 150, Amsterdam",
description:
"Workshop on implementing sustainable practices in tech companies",
organizer: {
name: "Green Digital Alliance",
email: "workshops@greendigital.org",
},
attendees: ["Lisa van der Berg", "Mark Johnson"],
category: "Workshop",
},
},
{
id: "14",
type: "Contact",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.name;
},
data: {
name: "Akiko Tanaka",
phoneNumber: "+81 3 1234 5678",
emailAddress: "a.tanaka@tokyotech.jp",
address: "2-1-1 Marunouchi, Chiyoda-ku, Tokyo 100-0005",
organization: "Tokyo Tech Ventures",
title: "Investment Director",
notes: "Specialist in Asia-Pacific tech investments",
},
},
{
id: "15",
type: "Location",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.name;
},
data: {
name: "Innovation Center Stockholm",
address: "Regeringsgatan 65, 111 56 Stockholm, Sweden",
category: "Tech Hub",
description:
"Leading innovation center in Scandinavia, hosting startups and tech events",
},
},
{
id: "16",
type: "Website",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.title;
},
data: {
url: "https://www.github.com",
title: "GitHub",
description: "World's leading software development platform",
category: "Development Tools",
},
},
{
id: "17",
type: "Note",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.title;
},
data: {
title: "Team Structure Reorganization",
keywords: ["organization", "teams", "structure", "management"],
content:
"## Proposed Changes\n\n1. Create dedicated AI/ML team\n2. Merge frontend and mobile teams\n3. Establish DevOps center of excellence\n\n### Timeline\nGradual implementation over Q3 2025\n\n### Expected Outcomes\n- Improved efficiency\n- Better resource allocation\n- Faster delivery cycles",
},
},
{
id: "18",
type: "Receipt",
get rawData() {
return getRawData(this);
},
get title() {
return `${this.data.orderNumber} - ${this.data.vendor}`;
},
data: {
receiptDate: "2025-03-18T10:30:00+01:00",
orderNumber: "BOK456789",
amount: 850.0,
currency: "USD",
vendor: "Hilton Hotels",
items: [
{
name: "Executive Suite - 2 nights",
quantity: 1,
price: 850.0,
currency: "USD",
},
],
paymentMethod: "American Express",
shippingAddress: {
name: "Akiko Tanaka",
address:
"Hilton San Francisco, 333 O'Farrell St, San Francisco, CA 94102",
},
category: "Travel",
},
},
{
id: "19",
type: "Event",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.title;
},
data: {
title: "Web3 Developer Conference",
dateTime: {
start: "2025-09-10T09:00:00-07:00",
end: "2025-09-12T17:00:00-07:00",
},
location: "Moscone Center, 747 Howard St, San Francisco, CA 94103",
description:
"Three-day conference focusing on blockchain, DeFi, and Web3 development",
organizer: {
name: "Web3 Alliance",
email: "conference@web3alliance.org",
},
attendees: ["Vitalik B.", "Charles H.", "Gavin W."],
category: "Conference",
},
},
{
id: "20",
type: "Contact",
get rawData() {
return getRawData(this);
},
get title() {
return this.data.name;
},
data: {
name: "Rachel Chen",
phoneNumber: "+1 415 555 0123",
emailAddress: "rachel.chen@siliconvc.com",
address: "525 Market St, San Francisco, CA 94105",
organization: "Silicon Valley Capital",
title: "Partner",
notes: "Specializes in Series A/B investments in AI and ML startups",
},
},
];

View File

@ -1,106 +0,0 @@
interface DateTime {
start: string;
end: string;
}
interface Address {
name: string;
address: string;
}
interface Organizer {
name: string;
email: string;
}
interface ReceiptItem {
name: string;
quantity: number;
price: number;
currency: string;
}
interface DataItemType<T extends string, D> {
id: string;
title: string;
type: T;
rawData: string;
data: D;
}
interface EventData {
title: string;
dateTime: DateTime;
location: string;
description: string;
organizer: Organizer;
attendees: string[];
category: string;
}
interface ContactData {
name: string;
phoneNumber: string;
emailAddress: string;
address: string;
organization: string;
title: string;
notes: string;
}
interface LocationData {
name: string;
address: string;
category: string;
description: string;
}
interface NoteData {
title: string;
keywords: string[];
content: string;
}
interface WebsiteData {
url: string;
title: string;
description: string;
category: string;
}
interface ReceiptData {
receiptDate: string;
orderNumber: string;
amount: number;
currency: string;
vendor: string;
items: ReceiptItem[];
paymentMethod: string;
shippingAddress: Address;
category: string;
}
type Event = DataItemType<"Event", EventData>;
type Contact = DataItemType<"Contact", ContactData>;
type Location = DataItemType<"Location", LocationData>;
type Note = DataItemType<"Note", NoteData>;
type Website = DataItemType<"Website", WebsiteData>;
type Receipt = DataItemType<"Receipt", ReceiptData>;
type DataItem = Event | Contact | Location | Note | Website | Receipt;
type DataArray = DataItem[];
export type {
DateTime,
Address,
Organizer,
ReceiptItem,
Event,
Contact,
Location,
Note,
Website,
Receipt,
DataItem,
DataArray,
};

View File

@ -14,7 +14,6 @@ const CategoryColor: Record<
contact: "bg-orange-50",
location: "bg-red-50",
event: "bg-purple-50",
note: "bg-green-50",
};
const colors = [

View File

@ -1,14 +1,21 @@
import { ImageComponent } from "@components/image";
import { SearchCard } from "@components/search-card/SearchCard";
import { useSearchImageContext } from "@contexts/SearchImageContext";
import { base, UserImage } from "@network/index";
import { UserImage } from "@network/index";
import { useParams } from "@solidjs/router";
import { For, Show, type Component } from "solid-js";
import { createEffect, For, Show, type Component } from "solid-js";
import SolidjsMarkdown from "solidjs-markdown";
export const ImagePage: Component = () => {
const { imageId } = useParams<{ imageId: string }>();
const { imagesWithProperties } = useSearchImageContext();
const { imagesWithProperties, userImages } = useSearchImageContext();
const image = () => userImages().find((i) => i.ImageID === imageId);
createEffect(() => {
console.log(userImages());
});
const imageProperties = (): UserImage[] | undefined =>
Object.entries(imagesWithProperties()).find(([id]) => id === imageId)?.[1];
@ -18,6 +25,10 @@ export const ImagePage: Component = () => {
<div class="w-full bg-white rounded-xl p-4">
<ImageComponent ID={imageId} />
</div>
<div>
<h2 class="font-bold text-xl">Description</h2>
<SolidjsMarkdown>{image()?.Image.Description}</SolidjsMarkdown>
</div>
<div class="w-full grid grid-cols-3 gap-2 grid-flow-row-dense p-4 bg-white rounded-xl">
<Show when={imageProperties()}>
{(image) => (