Skip to content

Commit 5ff2fbb

Browse files
feat: Implement full gallery management feature with image upload capabilities across frontend and backend.
1 parent fe74b0d commit 5ff2fbb

14 files changed

Lines changed: 129 additions & 93 deletions

File tree

backend/src/modules/gallery/domain/gallery.entity.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ export class Gallery {
44
public readonly title: string,
55
public readonly imageUrl: string,
66
public readonly category: string, // 'makeup' | 'veil' | 'service'
7-
public readonly tags: string[] = [],
87
public readonly status: string = 'draft',
98
public readonly alt: string = '',
10-
public readonly filename: string = '',
11-
public readonly createdAt: Date = new Date(),
129
) {}
1310
}

backend/src/modules/gallery/infrastructure/schemas/gallery.schema.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,12 @@ export class GallerySchemaEntity {
1414
@Prop({ required: true })
1515
category: string;
1616

17-
@Prop([String])
18-
tags: string[];
19-
2017
@Prop({ default: 'draft' })
2118
status: string;
2219

2320
@Prop()
2421
alt: string;
2522

26-
@Prop()
27-
filename: string;
2823
}
2924

3025
export const GallerySchema = SchemaFactory.createForClass(GallerySchemaEntity);
Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,22 @@
1-
import {
2-
IsString,
3-
IsNotEmpty,
4-
IsUrl,
5-
IsOptional,
6-
IsArray,
7-
} from 'class-validator';
1+
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
82

93
export class CreateGalleryDto {
104
@IsString()
115
@IsNotEmpty()
126
title: string;
137

14-
@IsUrl()
15-
@IsNotEmpty()
8+
@IsOptional()
169
imageUrl: string;
1710

1811
@IsString()
1912
@IsNotEmpty()
2013
category: string;
2114

22-
@IsArray()
23-
@IsString({ each: true })
24-
@IsOptional()
25-
tags?: string[];
26-
2715
@IsString()
2816
@IsOptional()
2917
status?: string;
3018

3119
@IsString()
3220
@IsOptional()
3321
alt?: string;
34-
35-
@IsString()
36-
@IsOptional()
37-
filename?: string;
3822
}

backend/src/modules/gallery/presentation/gallery.controller.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,13 @@ export class GalleryController {
8888
? `/uploads/gallery/${files[0].filename}`
8989
: null;
9090

91-
const galleryData = {
92-
...updateGalleryDto,
93-
};
94-
9591
if (imagePath) {
96-
(galleryData as any).imageUrl = imagePath;
92+
updateGalleryDto.imageUrl = imagePath;
9793
}
9894

9995
return this.galleryService.update(
10096
id,
101-
galleryData as unknown as Partial<Gallery>,
97+
updateGalleryDto as unknown as Partial<Gallery>,
10298
);
10399
}
104100

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./model/gallery.data";
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { required } from "@angular/forms/signals";
2+
import { Gallery } from "@shared/models";
3+
4+
export const galleryFormData: Gallery = {
5+
imageUrl: "",
6+
title: "",
7+
category: "visage",
8+
status: "draft",
9+
alt: "",
10+
};
11+
12+
export const resetGalleryData: Gallery = {
13+
imageUrl: "",
14+
title: "",
15+
category: "visage",
16+
status: "draft",
17+
alt: "",
18+
};
19+
20+
export function galleryValidationSchema(schemaPath: any) {
21+
required(schemaPath.title, {
22+
message: $localize`:@@galleryTitleRequiredMessage: Title is required`,
23+
});
24+
required(schemaPath.category, {
25+
message: $localize`:@@galleryCategoryRequiredMessage: Category is required`,
26+
});
27+
}

frontend/src/pages/gallery/gallery.component.html

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,23 +46,6 @@ <h2 class="font-serif text-4xl text-gray-900 mb-2" i18n="@@galleryTitle">Gallery
4646
}
4747
</div>
4848

49-
<!-- Drag & Drop Area -->
50-
<div
51-
(dragover)="onDragOver($event)"
52-
(dragleave)="onDragLeave($event)"
53-
(drop)="onDrop($event)"
54-
class="mb-10 p-8 border-2 border-dashed border-gray-200 rounded-2xl bg-white flex flex-col items-center justify-center text-center cursor-pointer transition-all group"
55-
[class]="isDragging()
56-
? 'border-primary bg-primary/10 scale-105'
57-
: 'hover:border-primary hover:bg-primary/5'"
58-
>
59-
<div class="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center shadow-sm mb-4 group-hover:scale-110 transition-transform">
60-
<span class="material-symbols-outlined text-3xl text-primary">cloud_upload</span>
61-
</div>
62-
<h3 class="font-serif text-lg text-gray-800 mb-1" i18n="@@galleryDragDropTitle">Drag and drop files here</h3>
63-
<p class="text-sm text-gray-500" i18n="@@galleryDragDropSubtitle">Supported formats: JPG, PNG, WEBP (Max 5MB)</p>
64-
</div>
65-
6649
@if (viewMode() === 'grid') {
6750
<!-- Image Grid -->
6851
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8 animate-page-enter">
@@ -101,7 +84,6 @@ <h3 class="font-serif text-lg text-gray-800 mb-1" i18n="@@galleryDragDropTitle">
10184
</td>
10285
<td class="px-6 py-4 whitespace-nowrap">
10386
<div class="text-sm font-medium text-gray-900">{{ image.title }}</div>
104-
<div class="text-xs text-gray-500 font-mono">{{ image.filename }}</div>
10587
</td>
10688
<td class="px-6 py-4 whitespace-nowrap">
10789
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">

frontend/src/pages/gallery/gallery.component.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,9 @@ export class GalleryComponent implements OnInit {
5555
id: "",
5656
imageUrl: "",
5757
title: "",
58-
filename: "",
5958
category: GalleryCategories.VISAGE, // Default
60-
createdAt: "",
6159
status: "draft",
6260
alt: "",
63-
tags: [],
6461
};
6562
}
6663

@@ -78,8 +75,8 @@ export class GalleryComponent implements OnInit {
7875
this.isModalOpen.set(false);
7976
}
8077

81-
saveImage(event: { image: Gallery; file: File | null }) {
82-
const { image, file } = event;
78+
saveImage(event: { data: any; file: File | null }) {
79+
const { data: image, file } = event;
8380
const args: any[] = [{ ...image }];
8481
if (file) args.push(file);
8582
const formData = convertFormData(...args);

frontend/src/pages/gallery/ui/gallery-card/gallery-card.component.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div class="group bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden flex flex-col transition-all duration-300 hover:shadow-lg hover:-translate-y-1 reveal-item" [style.animation-delay.ms]="index() * 75">
22
<div (click)="onViewImage()" class="relative aspect-[4/5] overflow-hidden cursor-pointer">
3-
<img [src]="image().imageUrl" [alt]="image().alt" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"/>
3+
<img [src]="imageUrl()" [alt]="image().alt" class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"/>
44
<div class="absolute top-4 left-4 bg-gray-900/80 backdrop-blur-sm px-3 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-wider text-white shadow-sm">
55
{{ image().category }}
66
</div>
@@ -11,7 +11,6 @@
1111
<div class="p-5 flex-1 flex flex-col justify-between">
1212
<div>
1313
<h3 class="font-serif text-lg text-gray-900 mb-1">{{ image().title }}</h3>
14-
<p class="text-xs text-gray-400 font-mono truncate">{{ image().filename }}</p>
1514
</div>
1615
<div class="mt-4 flex items-center justify-between border-t border-gray-100 pt-3">
1716
<div class="flex items-center gap-2">

frontend/src/pages/gallery/ui/gallery-card/gallery-card.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import { CommonModule } from "@angular/common";
22
import {
33
ChangeDetectionStrategy,
44
Component,
5+
computed,
56
input,
67
output,
78
OnInit,
89
} from "@angular/core";
910
import { Gallery } from "@shared/models";
11+
import { linkServerConvert } from "@shared/lib";
1012

1113
@Component({
1214
selector: "app-gallery-card",
@@ -23,6 +25,8 @@ export class GalleryCardComponent implements OnInit {
2325
viewImage = output<string>();
2426
deleteCard = output<string>();
2527

28+
imageUrl = computed(() => this.image().imageUrl ? linkServerConvert(this.image().imageUrl) : "public/images/treatments-no-img.png");
29+
2630
ngOnInit(): void {}
2731

2832
onEdit() {

0 commit comments

Comments
 (0)