@mohasinac/feat-homepage 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,387 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { IRepository, SieveQuery, PagedResult, FeatureManifest } from '@mohasinac/contracts';
3
+ import { NextResponse } from 'next/server';
4
+
5
+ type HomepageSectionType = "hero" | "featured_categories" | "featured_products" | "banner" | "testimonials" | "promotions" | "blog_posts" | "sellers" | "custom";
6
+ interface HomepageSectionContent {
7
+ title?: string;
8
+ subtitle?: string;
9
+ ctaLabel?: string;
10
+ ctaUrl?: string;
11
+ imageUrl?: string;
12
+ videoUrl?: string;
13
+ itemIds?: string[];
14
+ html?: string;
15
+ }
16
+ interface HomepageSection {
17
+ id: string;
18
+ type: HomepageSectionType;
19
+ title?: string;
20
+ /** Primary enable/disable flag stored in Firestore as `enabled` */
21
+ enabled?: boolean;
22
+ /** Alias kept for older consumers */
23
+ isVisible?: boolean;
24
+ order?: number;
25
+ content?: HomepageSectionContent;
26
+ mobile?: Partial<HomepageSectionContent>;
27
+ createdAt?: string;
28
+ updatedAt?: string;
29
+ }
30
+ interface HomepageData {
31
+ sections: HomepageSection[];
32
+ }
33
+ interface HotspotPin {
34
+ id: string;
35
+ name: string;
36
+ universe: string;
37
+ description: string;
38
+ href: string;
39
+ /** Percentage from left edge, 0–100 */
40
+ xPct: number;
41
+ /** Percentage from top edge, 0–100 */
42
+ yPct: number;
43
+ /** Hex accent colour for popup header/button */
44
+ accent: string;
45
+ /** Small label on pin (optional) */
46
+ badge?: string;
47
+ /** CTA button label */
48
+ buyText: string;
49
+ }
50
+ interface CharacterHotspotConfig {
51
+ imageUrl: string;
52
+ imageAlt: string;
53
+ active: boolean;
54
+ pins: HotspotPin[];
55
+ updatedAt?: string;
56
+ }
57
+ interface Banner {
58
+ id: string;
59
+ title: string;
60
+ subtitle?: string;
61
+ ctaLabel?: string;
62
+ ctaUrl?: string;
63
+ backgroundImage?: string;
64
+ backgroundColor?: string;
65
+ textColor?: string;
66
+ sortOrder: number;
67
+ active: boolean;
68
+ }
69
+ interface PromoBanner {
70
+ id: string;
71
+ title: string;
72
+ ctaLabel: string;
73
+ ctaUrl: string;
74
+ image: string;
75
+ sortOrder: number;
76
+ active: boolean;
77
+ }
78
+ type TrustBadgeIconKey = "shipping" | "support" | "rewards" | "secure";
79
+ interface TrustBadge {
80
+ id: string;
81
+ title: string;
82
+ sub: string;
83
+ iconKey: TrustBadgeIconKey;
84
+ sortOrder: number;
85
+ active: boolean;
86
+ }
87
+ interface Testimonial {
88
+ id: string;
89
+ name: string;
90
+ rating: 1 | 2 | 3 | 4 | 5;
91
+ text: string;
92
+ avatarUrl?: string;
93
+ featured: boolean;
94
+ sortOrder: number;
95
+ active: boolean;
96
+ }
97
+ interface BeforeAfterItem {
98
+ id: string;
99
+ beforeImage: string;
100
+ afterImage: string;
101
+ productId?: string;
102
+ caption: string;
103
+ sortOrder: number;
104
+ }
105
+ interface CarouselSlideCard {
106
+ id: string;
107
+ gridRow: 1 | 2;
108
+ gridCol: 1 | 2 | 3;
109
+ background: {
110
+ type: "color" | "gradient" | "image" | "transparent";
111
+ value: string;
112
+ };
113
+ content?: {
114
+ title?: string;
115
+ subtitle?: string;
116
+ description?: string;
117
+ };
118
+ buttons?: Array<{
119
+ id: string;
120
+ text: string;
121
+ link: string;
122
+ variant: "primary" | "secondary" | "outline";
123
+ openInNewTab: boolean;
124
+ }>;
125
+ isButtonOnly: boolean;
126
+ sizing?: {
127
+ widthPct?: 25 | 50 | 75 | 100;
128
+ heightPct?: 25 | 50 | 75 | 100;
129
+ padding?: "none" | "sm" | "md" | "lg";
130
+ };
131
+ }
132
+ interface CarouselSlide {
133
+ id: string;
134
+ title: string;
135
+ order: number;
136
+ active: boolean;
137
+ media: {
138
+ type: "image" | "video";
139
+ url: string;
140
+ alt: string;
141
+ thumbnail?: string;
142
+ };
143
+ link?: {
144
+ url: string;
145
+ openInNewTab: boolean;
146
+ };
147
+ mobileMedia?: {
148
+ type: "image" | "video";
149
+ url: string;
150
+ alt: string;
151
+ };
152
+ cards: CarouselSlideCard[];
153
+ overlay?: {
154
+ title?: string;
155
+ subtitle?: string;
156
+ description?: string;
157
+ button?: {
158
+ id: string;
159
+ text: string;
160
+ link: string;
161
+ variant: "primary" | "secondary" | "outline";
162
+ openInNewTab: boolean;
163
+ };
164
+ };
165
+ createdAt?: string;
166
+ updatedAt?: string;
167
+ }
168
+
169
+ interface UseHomepageOptions {
170
+ initialData?: HomepageData;
171
+ enabled?: boolean;
172
+ }
173
+ declare function useHomepage(opts?: UseHomepageOptions): {
174
+ sections: HomepageSection[];
175
+ isLoading: boolean;
176
+ error: Error | null;
177
+ };
178
+
179
+ interface HeroSectionProps {
180
+ section: HomepageSection;
181
+ onCtaClick?: () => void;
182
+ }
183
+ declare function HeroSection({ section, onCtaClick }: HeroSectionProps): react_jsx_runtime.JSX.Element;
184
+
185
+ interface CharacterHotspotProps {
186
+ config?: CharacterHotspotConfig | null;
187
+ /**
188
+ * Override the default fallback hotspot pins shown when no config is supplied.
189
+ * Useful for non-collectibles storefronts.
190
+ */
191
+ defaultHotspots?: HotspotPin[];
192
+ /** Universe quick-browse links shown below the panorama. */
193
+ universeLinks?: {
194
+ label: string;
195
+ href: string;
196
+ color: string;
197
+ icon: string;
198
+ }[];
199
+ /** "Shop all" button href */
200
+ shopAllHref?: string;
201
+ /** Section heading */
202
+ heading?: string;
203
+ /** Sub-heading / helper text */
204
+ subheading?: string;
205
+ }
206
+ declare function CharacterHotspot({ config, defaultHotspots, universeLinks, shopAllHref, heading, subheading, }: CharacterHotspotProps): react_jsx_runtime.JSX.Element | null;
207
+
208
+ interface CharacterHotspotFormProps {
209
+ initial?: CharacterHotspotConfig | null;
210
+ /**
211
+ * Upload an image and return its public URL.
212
+ * Implement however suits your storage backend:
213
+ * ```ts
214
+ * onUploadImage={async (file) => {
215
+ * const storage = getStorage(getFirebaseApp());
216
+ * const storageRef = ref(storage, `character-hotspot/${Date.now()}_${file.name}`);
217
+ * await uploadBytes(storageRef, file);
218
+ * return getDownloadURL(storageRef);
219
+ * }}
220
+ * ```
221
+ */
222
+ onUploadImage: (file: File) => Promise<string>;
223
+ /**
224
+ * Persist the final config (e.g. write to Firestore).
225
+ */
226
+ onSave: (config: CharacterHotspotConfig) => Promise<void>;
227
+ /**
228
+ * Optional callback after a successful save (e.g. revalidate cache).
229
+ */
230
+ onAfterSave?: () => void | Promise<void>;
231
+ }
232
+ declare function CharacterHotspotForm({ initial, onUploadImage, onSave, onAfterSave, }: CharacterHotspotFormProps): react_jsx_runtime.JSX.Element;
233
+
234
+ interface HeroBannerProps {
235
+ banners: Banner[];
236
+ /** Milliseconds between automatic slide advances. Default: 5000. */
237
+ autoplayMs?: number;
238
+ }
239
+ declare function HeroBanner({ banners, autoplayMs }: HeroBannerProps): react_jsx_runtime.JSX.Element | null;
240
+
241
+ interface PromoGridProps {
242
+ banners: PromoBanner[];
243
+ /** Eyebrow label above the section heading. Default: "LIMITED TIME". */
244
+ eyebrow?: string;
245
+ /** Section heading. Default: "HOT DEALS & PROMOS". */
246
+ heading?: string;
247
+ }
248
+ declare function PromoGrid({ banners, eyebrow, heading, }: PromoGridProps): react_jsx_runtime.JSX.Element | null;
249
+
250
+ interface TrustBadgesProps {
251
+ badges?: TrustBadge[];
252
+ }
253
+ declare function TrustBadges({ badges }: TrustBadgesProps): react_jsx_runtime.JSX.Element;
254
+
255
+ interface TestimonialsCarouselProps {
256
+ testimonials: Testimonial[];
257
+ /** Eyebrow label. Default: "COLLECTOR REVIEWS". */
258
+ eyebrow?: string;
259
+ /** Section heading. Default: "WHAT OUR COLLECTORS SAY". */
260
+ heading?: string;
261
+ }
262
+ declare function TestimonialsCarousel({ testimonials, eyebrow, heading, }: TestimonialsCarouselProps): react_jsx_runtime.JSX.Element | null;
263
+
264
+ interface BeforeAfterCardProps {
265
+ item: BeforeAfterItem;
266
+ /** Label shown on the left (before) side. Default: "Before". */
267
+ beforeLabel?: string;
268
+ /** Label shown on the right (after) side. Default: "After". */
269
+ afterLabel?: string;
270
+ }
271
+ declare function BeforeAfterCard({ item, beforeLabel, afterLabel, }: BeforeAfterCardProps): react_jsx_runtime.JSX.Element;
272
+
273
+ interface NewsletterBannerProps {
274
+ /**
275
+ * Called when the user submits the form. Throw on failure — the component
276
+ * will show a generic error message.
277
+ */
278
+ onSubscribe: (email: string) => Promise<void>;
279
+ /** Eyebrow label. Default: "STAY CONNECTED". */
280
+ eyebrow?: string;
281
+ /** Section heading. Default: "Join Our Newsletter". */
282
+ heading?: string;
283
+ /** Sub-heading. Default: "Get exclusive offers, new arrivals and more." */
284
+ subheading?: string;
285
+ /** Email placeholder text. Default: "Enter your email". */
286
+ placeholder?: string;
287
+ /** CTA button label. Default: "Subscribe". */
288
+ ctaLabel?: string;
289
+ /** Success message shown after subscribe. Default: "You're in! Check your inbox." */
290
+ successMessage?: string;
291
+ /** Error message on failure. Default: "Something went wrong. Please try again." */
292
+ errorMessage?: string;
293
+ }
294
+ declare function NewsletterBanner({ onSubscribe, eyebrow, heading, subheading, placeholder, ctaLabel, successMessage, errorMessage, }: NewsletterBannerProps): react_jsx_runtime.JSX.Element;
295
+
296
+ declare class HomepageSectionsRepository {
297
+ private readonly repo;
298
+ constructor(repo: IRepository<HomepageSection>);
299
+ findAll(query?: SieveQuery): Promise<PagedResult<HomepageSection>>;
300
+ findById(id: string): Promise<HomepageSection | null>;
301
+ create(data: Omit<HomepageSection, "id">): Promise<HomepageSection>;
302
+ update(id: string, data: Partial<HomepageSection>): Promise<HomepageSection>;
303
+ delete(id: string): Promise<void>;
304
+ }
305
+
306
+ declare const manifest: FeatureManifest;
307
+
308
+ /**
309
+ * feat-homepage — Next.js App Router API handlers (GET/POST /api/homepage-sections)
310
+ *
311
+ * Pure stub:
312
+ * ```ts
313
+ * // app/api/homepage-sections/route.ts
314
+ * export { GET, POST } from "@mohasinac/feat-homepage";
315
+ * ```
316
+ */
317
+
318
+ declare function GET$3(request: Request): Promise<NextResponse>;
319
+ declare const POST: (request: Request, context: {
320
+ params: Promise<Record<string, string>>;
321
+ }) => Promise<NextResponse>;
322
+
323
+ /**
324
+ * feat-homepage — GET / PATCH / DELETE /api/homepage-sections/[id]
325
+ *
326
+ * Pure stub:
327
+ * ```ts
328
+ * // app/api/homepage-sections/[id]/route.ts
329
+ * export { homepageSectionItemGET as GET, homepageSectionItemPATCH as PATCH, homepageSectionItemDELETE as DELETE }
330
+ * from "@mohasinac/feat-homepage";
331
+ * ```
332
+ */
333
+
334
+ declare function GET$2(_request: Request, context: {
335
+ params: Promise<{
336
+ id: string;
337
+ }>;
338
+ }): Promise<NextResponse>;
339
+
340
+ declare const homepageSectionItemPATCH: (request: Request, context: {
341
+ params: Promise<Record<string, string>>;
342
+ }) => Promise<NextResponse>;
343
+ declare const homepageSectionItemDELETE: (request: Request, context: {
344
+ params: Promise<Record<string, string>>;
345
+ }) => Promise<NextResponse>;
346
+
347
+ /**
348
+ * feat-homepage — Next.js App Router API handler (GET/POST /api/carousel)
349
+ *
350
+ * Pure stub:
351
+ * ```ts
352
+ * // app/api/carousel/route.ts
353
+ * export { carouselGET as GET, carouselPOST as POST } from "@mohasinac/feat-homepage";
354
+ * ```
355
+ */
356
+
357
+ declare function GET$1(request: Request): Promise<NextResponse>;
358
+
359
+ declare const carouselPOST: (request: Request, context: {
360
+ params: Promise<Record<string, string>>;
361
+ }) => Promise<NextResponse>;
362
+
363
+ /**
364
+ * feat-homepage — GET / PATCH / DELETE /api/carousel/[id]
365
+ *
366
+ * Pure stub:
367
+ * ```ts
368
+ * // app/api/carousel/[id]/route.ts
369
+ * export { carouselItemGET as GET, carouselItemPATCH as PATCH, carouselItemDELETE as DELETE }
370
+ * from "@mohasinac/feat-homepage";
371
+ * ```
372
+ */
373
+
374
+ declare function GET(_request: Request, context: {
375
+ params: Promise<{
376
+ id: string;
377
+ }>;
378
+ }): Promise<NextResponse>;
379
+
380
+ declare const carouselItemPATCH: (request: Request, context: {
381
+ params: Promise<Record<string, string>>;
382
+ }) => Promise<NextResponse>;
383
+ declare const carouselItemDELETE: (request: Request, context: {
384
+ params: Promise<Record<string, string>>;
385
+ }) => Promise<NextResponse>;
386
+
387
+ export { type Banner, BeforeAfterCard, type BeforeAfterCardProps, type BeforeAfterItem, type CarouselSlide, type CarouselSlideCard, CharacterHotspot, type CharacterHotspotConfig, CharacterHotspotForm, type CharacterHotspotFormProps, type CharacterHotspotProps, GET$3 as GET, HeroBanner, type HeroBannerProps, HeroSection, type HomepageData, type HomepageSection, type HomepageSectionContent, type HomepageSectionType, HomepageSectionsRepository, type HotspotPin, NewsletterBanner, type NewsletterBannerProps, POST, type PromoBanner, PromoGrid, type PromoGridProps, type Testimonial, TestimonialsCarousel, type TestimonialsCarouselProps, type TrustBadge, type TrustBadgeIconKey, TrustBadges, type TrustBadgesProps, GET$1 as carouselGET, carouselItemDELETE, GET as carouselItemGET, carouselItemPATCH, carouselPOST, GET$3 as homepageGET, homepageSectionItemDELETE, GET$2 as homepageSectionItemGET, homepageSectionItemPATCH, manifest, useHomepage };