negodesign 0.0.30 → 0.0.32
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.
- package/dist/components/NegoDesign.svelte +2 -10
- package/dist/components/NegoDesign.svelte.d.ts +2 -7
- package/dist/components/core/banner/CtaCardBanner.svelte +1 -1
- package/dist/components/core/banner/types.d.ts +16 -6
- package/dist/components/core/card/types.d.ts +94 -0
- package/dist/components/core/carousel/grid/media/types.d.ts +11 -1
- package/dist/components/core/carousel/grid/media/ui/CarouselGridMedia.svelte +2 -2
- package/dist/components/core/carousel/grid/media/ui/CarouselGridMedia.svelte.d.ts +2 -2
- package/dist/components/core/carousel/grid/profile/types.d.ts +11 -1
- package/dist/components/core/carousel/grid/profile/ui/CarouselGridProfile.svelte +2 -2
- package/dist/components/core/carousel/grid/profile/ui/CarouselGridProfile.svelte.d.ts +2 -2
- package/dist/components/core/carousel/highlights/types.d.ts +11 -0
- package/dist/components/core/carousel/promotion/types.d.ts +10 -0
- package/dist/components/core/carousel/types.d.ts +56 -0
- package/dist/components/core/datatable/data/data-table.d.ts +25 -0
- package/dist/components/core/footer/types.d.ts +28 -0
- package/dist/components/core/nav/data/nav-menu-sidebar.d.ts +29 -0
- package/dist/components/core/nav/data/nav-menu.d.ts +43 -0
- package/dist/components/core/nav/data/nav-user-sidebar.d.ts +14 -0
- package/dist/components/core/nav/ui/menu-links.svelte +1 -1
- package/dist/components/core/panel/CarouselSlot.svelte +1 -1
- package/dist/components/core/panel/type.d.ts +11 -0
- package/dist/components/core/search/types.d.ts +17 -5
- package/dist/components/core/sidebar/types.d.ts +19 -0
- package/dist/components/core/sidebar/types.js +1 -0
- package/dist/components/core/tabs/data/TabModel.d.ts +11 -0
- package/dist/components/core/tabs/types.d.ts +11 -0
- package/dist/components/core/tabs/types.js +1 -0
- package/dist/components/pages/admin/01/ui/types.d.ts +21 -0
- package/dist/components/pages/admin/01/ui/types.js +1 -0
- package/dist/components/pages/product-details/01/ProductDetails01.svelte +71 -0
- package/dist/components/pages/product-details/01/ProductDetails01.svelte.d.ts +11 -0
- package/dist/components/pages/product-details/02/ProductDetails02.svelte +88 -0
- package/dist/components/pages/product-details/02/ProductDetails02.svelte.d.ts +11 -0
- package/dist/components/pages/product-details/03/ProductDetails03.svelte +77 -0
- package/dist/components/pages/product-details/03/ProductDetails03.svelte.d.ts +11 -0
- package/dist/components/pages/product-details/04/ProductDetails04.svelte +65 -0
- package/dist/components/pages/product-details/04/ProductDetails04.svelte.d.ts +11 -0
- package/dist/components/pages/product-details/ProductDetails.svelte +38 -0
- package/dist/components/pages/product-details/ProductDetails.svelte.d.ts +4 -0
- package/dist/components/pages/product-details/shared/ProductDetailActions.svelte +82 -0
- package/dist/components/pages/product-details/shared/ProductDetailActions.svelte.d.ts +13 -0
- package/dist/components/pages/product-details/shared/ProductDetailBreadcrumb.svelte +46 -0
- package/dist/components/pages/product-details/shared/ProductDetailBreadcrumb.svelte.d.ts +8 -0
- package/dist/components/pages/product-details/shared/ProductDetailDescription.svelte +38 -0
- package/dist/components/pages/product-details/shared/ProductDetailDescription.svelte.d.ts +8 -0
- package/dist/components/pages/product-details/shared/ProductDetailFeatures.svelte +40 -0
- package/dist/components/pages/product-details/shared/ProductDetailFeatures.svelte.d.ts +7 -0
- package/dist/components/pages/product-details/shared/ProductDetailFilesVertical.svelte +72 -0
- package/dist/components/pages/product-details/shared/ProductDetailFilesVertical.svelte.d.ts +9 -0
- package/dist/components/pages/product-details/shared/ProductDetailHeader.svelte +41 -0
- package/dist/components/pages/product-details/shared/ProductDetailHeader.svelte.d.ts +9 -0
- package/dist/components/pages/product-details/shared/ProductDetailImage.svelte +70 -0
- package/dist/components/pages/product-details/shared/ProductDetailImage.svelte.d.ts +9 -0
- package/dist/components/pages/product-details/shared/ProductDetailMap.svelte +57 -0
- package/dist/components/pages/product-details/shared/ProductDetailMap.svelte.d.ts +9 -0
- package/dist/components/pages/product-details/shared/ProductDetailPrice.svelte +44 -0
- package/dist/components/pages/product-details/shared/ProductDetailPrice.svelte.d.ts +9 -0
- package/dist/components/pages/product-details/shared/ProductDetailRating.svelte +42 -0
- package/dist/components/pages/product-details/shared/ProductDetailRating.svelte.d.ts +8 -0
- package/dist/components/pages/product-details/shared/ProductDetailReviews.svelte +70 -0
- package/dist/components/pages/product-details/shared/ProductDetailReviews.svelte.d.ts +8 -0
- package/dist/components/pages/product-details/shared/ProductDetailTabs.svelte +96 -0
- package/dist/components/pages/product-details/shared/ProductDetailTabs.svelte.d.ts +12 -0
- package/dist/components/pages/product-details/shared/ProductDetailTags.svelte +35 -0
- package/dist/components/pages/product-details/shared/ProductDetailTags.svelte.d.ts +8 -0
- package/dist/components/pages/product-details/shared/ProductPurchasePanel.svelte +58 -0
- package/dist/components/pages/product-details/shared/ProductPurchasePanel.svelte.d.ts +11 -0
- package/dist/components/pages/product-details/types.d.ts +170 -0
- package/dist/components/pages/product-details/types.js +1 -0
- package/dist/components/pages/profile-user/01/ProfileUser01.svelte +113 -0
- package/dist/components/pages/profile-user/01/ProfileUser01.svelte.d.ts +27 -0
- package/dist/components/pages/profile-user/02/ProfileUser02.svelte +83 -0
- package/dist/components/pages/profile-user/02/ProfileUser02.svelte.d.ts +20 -0
- package/dist/components/pages/profile-user/03/ProfileUser03.svelte +85 -0
- package/dist/components/pages/profile-user/03/ProfileUser03.svelte.d.ts +20 -0
- package/dist/components/pages/profile-user/ProfileUser.svelte +53 -0
- package/dist/components/pages/profile-user/ProfileUser.svelte.d.ts +4 -0
- package/dist/components/pages/profile-user/shared/ProfileUserAddress.svelte +84 -0
- package/dist/components/pages/profile-user/shared/ProfileUserAddress.svelte.d.ts +8 -0
- package/dist/components/pages/profile-user/shared/ProfileUserHeader.svelte +104 -0
- package/dist/components/pages/profile-user/shared/ProfileUserHeader.svelte.d.ts +9 -0
- package/dist/components/pages/profile-user/shared/ProfileUserResetPassword.svelte +116 -0
- package/dist/components/pages/profile-user/shared/ProfileUserResetPassword.svelte.d.ts +7 -0
- package/dist/components/pages/profile-user/shared/ProfileUserSetting.svelte +176 -0
- package/dist/components/pages/profile-user/shared/ProfileUserSetting.svelte.d.ts +14 -0
- package/dist/components/pages/profile-user/shared/ProfileUserTabs.svelte +78 -0
- package/dist/components/pages/profile-user/shared/ProfileUserTabs.svelte.d.ts +22 -0
- package/dist/components/pages/profile-user/types.d.ts +144 -0
- package/dist/components/pages/profile-user/types.js +1 -0
- package/dist/components/types.d.ts +6 -0
- package/dist/components/types.js +1 -0
- package/dist/globals.css +210 -3
- package/dist/i18n/langs/en.d.ts +83 -0
- package/dist/i18n/langs/en.js +83 -0
- package/dist/i18n/langs/pt.d.ts +83 -0
- package/dist/i18n/langs/pt.js +83 -0
- package/dist/i18n/translations.d.ts +166 -0
- package/dist/index.d.ts +190 -25
- package/dist/index.js +190 -25
- package/dist/types/index.d.ts +159 -5
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
/**
|
|
3
|
+
* Avaliação (estrelas + contagem) do produto.
|
|
4
|
+
* Quando `isLoading` é true, exibe um Skeleton.
|
|
5
|
+
* @component
|
|
6
|
+
* @example
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <ProductDetailRating rating={4.5} reviewsCount={120} />
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import CardStarRating from "../../../core/card/shared/CardStarRating.svelte";
|
|
15
|
+
import Skeleton from "../../../ui/skeleton/skeleton.svelte";
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
rating,
|
|
19
|
+
reviewsCount,
|
|
20
|
+
isLoading = false,
|
|
21
|
+
}: {
|
|
22
|
+
rating?: number;
|
|
23
|
+
reviewsCount?: number;
|
|
24
|
+
isLoading?: boolean;
|
|
25
|
+
} = $props();
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
{#if isLoading}
|
|
29
|
+
<div class="flex items-center gap-2">
|
|
30
|
+
<Skeleton class="h-6 w-24 rounded-md" />
|
|
31
|
+
<Skeleton class="h-4 w-16 rounded-md" />
|
|
32
|
+
</div>
|
|
33
|
+
{:else if rating}
|
|
34
|
+
<div class="flex items-center gap-2">
|
|
35
|
+
<CardStarRating startNumber={rating} startMax={5} />
|
|
36
|
+
{#if reviewsCount}
|
|
37
|
+
<span class="text-sm text-muted-foreground">
|
|
38
|
+
({reviewsCount} avaliações)
|
|
39
|
+
</span>
|
|
40
|
+
{/if}
|
|
41
|
+
</div>
|
|
42
|
+
{/if}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
rating?: number;
|
|
3
|
+
reviewsCount?: number;
|
|
4
|
+
isLoading?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare const ProductDetailRating: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type ProductDetailRating = ReturnType<typeof ProductDetailRating>;
|
|
8
|
+
export default ProductDetailRating;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
/**
|
|
3
|
+
* Lista de avaliações de clientes do produto.
|
|
4
|
+
* Quando `isLoading` é true, exibe Skeletons.
|
|
5
|
+
* @component
|
|
6
|
+
* @example
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <ProductDetailReviews reviews={data.reviews} />
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import CardStarRating from "../../../core/card/shared/CardStarRating.svelte";
|
|
15
|
+
import Skeleton from "../../../ui/skeleton/skeleton.svelte";
|
|
16
|
+
import type { ProductDetailsReview } from "../types";
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
reviews,
|
|
20
|
+
isLoading = false,
|
|
21
|
+
}: {
|
|
22
|
+
reviews?: ProductDetailsReview[];
|
|
23
|
+
isLoading?: boolean;
|
|
24
|
+
} = $props();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
{#if isLoading}
|
|
28
|
+
<div class="flex flex-col gap-6">
|
|
29
|
+
{#each Array.from({ length: 2 }) as _, i (i)}
|
|
30
|
+
<div class="flex flex-col gap-2 border-b pb-6 last:border-b-0">
|
|
31
|
+
<div class="flex items-center justify-between">
|
|
32
|
+
<div class="flex items-center gap-3">
|
|
33
|
+
<Skeleton class="size-9 rounded-full" />
|
|
34
|
+
<div class="flex flex-col gap-1">
|
|
35
|
+
<Skeleton class="h-4 w-28 rounded-md" />
|
|
36
|
+
<Skeleton class="h-3 w-20 rounded-md" />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<Skeleton class="h-5 w-24 rounded-md" />
|
|
40
|
+
</div>
|
|
41
|
+
<Skeleton class="h-4 w-full rounded-md" />
|
|
42
|
+
<Skeleton class="h-4 w-3/4 rounded-md" />
|
|
43
|
+
</div>
|
|
44
|
+
{/each}
|
|
45
|
+
</div>
|
|
46
|
+
{:else if reviews?.length}
|
|
47
|
+
<div class="flex flex-col gap-6">
|
|
48
|
+
{#each reviews as review (review.id)}
|
|
49
|
+
<article class="flex flex-col gap-2 border-b pb-6 last:border-b-0">
|
|
50
|
+
<div class="flex items-center justify-between">
|
|
51
|
+
<div class="flex items-center gap-3">
|
|
52
|
+
<div class="flex size-9 items-center justify-center rounded-full bg-muted text-sm font-semibold">
|
|
53
|
+
{review.author.charAt(0)}
|
|
54
|
+
</div>
|
|
55
|
+
<div>
|
|
56
|
+
<p class="text-sm font-medium">{review.author}</p>
|
|
57
|
+
{#if review.date}
|
|
58
|
+
<p class="text-xs text-muted-foreground">{review.date}</p>
|
|
59
|
+
{/if}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<CardStarRating startNumber={review.rating} startMax={5} />
|
|
63
|
+
</div>
|
|
64
|
+
<p class="text-sm leading-6 text-foreground/85">{review.comment}</p>
|
|
65
|
+
</article>
|
|
66
|
+
{/each}
|
|
67
|
+
</div>
|
|
68
|
+
{:else}
|
|
69
|
+
<p class="text-muted-foreground">Sem avaliações disponíveis.</p>
|
|
70
|
+
{/if}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ProductDetailsReview } from "../types";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
reviews?: ProductDetailsReview[];
|
|
4
|
+
isLoading?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare const ProductDetailReviews: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type ProductDetailReviews = ReturnType<typeof ProductDetailReviews>;
|
|
8
|
+
export default ProductDetailReviews;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
/**
|
|
3
|
+
* Abas de conteúdo do produto (descrição, avaliações, etc).
|
|
4
|
+
* Quando `isLoading` é true, exibe Skeletons.
|
|
5
|
+
* @component
|
|
6
|
+
* @example
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <ProductDetailTabs tabs={data.tabs} {data}>
|
|
9
|
+
* {#snippet tabContent(tab)}
|
|
10
|
+
* ...
|
|
11
|
+
* {/snippet}
|
|
12
|
+
* </ProductDetailTabs>
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<script lang="ts">
|
|
18
|
+
import type { Snippet } from "svelte";
|
|
19
|
+
import Skeleton from "../../../ui/skeleton/skeleton.svelte";
|
|
20
|
+
import type { ProductDetailsData, ProductDetailsTab } from "../types";
|
|
21
|
+
import ProductDetailDescription from "./ProductDetailDescription.svelte";
|
|
22
|
+
import ProductDetailFeatures from "./ProductDetailFeatures.svelte";
|
|
23
|
+
import ProductDetailReviews from "./ProductDetailReviews.svelte";
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
data,
|
|
27
|
+
tabs,
|
|
28
|
+
isLoading = false,
|
|
29
|
+
children,
|
|
30
|
+
}: {
|
|
31
|
+
data: ProductDetailsData;
|
|
32
|
+
tabs?: ProductDetailsTab[];
|
|
33
|
+
isLoading?: boolean;
|
|
34
|
+
/** Snippet opcional para renderizar o conteúdo de cada aba. */
|
|
35
|
+
children?: Snippet<[ProductDetailsTab]>;
|
|
36
|
+
} = $props();
|
|
37
|
+
|
|
38
|
+
const defaultTabs = $derived(
|
|
39
|
+
tabs?.length
|
|
40
|
+
? tabs
|
|
41
|
+
: [
|
|
42
|
+
{ id: "description", label: "Descrição" },
|
|
43
|
+
{ id: "reviews", label: "Avaliações" },
|
|
44
|
+
],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
let activeTab = $state<string | undefined>(undefined);
|
|
48
|
+
const currentTab = $derived(activeTab ?? defaultTabs[0]?.id ?? "description");
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<div>
|
|
52
|
+
<div class="mb-6 flex gap-1 border-b">
|
|
53
|
+
{#if isLoading}
|
|
54
|
+
{#each defaultTabs as tab (tab.id)}
|
|
55
|
+
<Skeleton class="h-9 w-24 rounded-t-md" />
|
|
56
|
+
{/each}
|
|
57
|
+
{:else}
|
|
58
|
+
{#each defaultTabs as tab (tab.id)}
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
onclick={() => (activeTab = tab.id)}
|
|
62
|
+
class="px-4 py-2 text-sm font-medium transition-colors {currentTab ===
|
|
63
|
+
tab.id
|
|
64
|
+
? 'border-b-2 border-foreground text-foreground'
|
|
65
|
+
: 'border-b-2 border-transparent text-muted-foreground hover:text-foreground'}"
|
|
66
|
+
>
|
|
67
|
+
{tab.label}
|
|
68
|
+
</button>
|
|
69
|
+
{/each}
|
|
70
|
+
{/if}
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{#if isLoading}
|
|
74
|
+
<div class="flex max-w-3xl flex-col gap-2">
|
|
75
|
+
{#each Array.from({ length: 4 }) as _, i (i)}
|
|
76
|
+
<Skeleton class="h-4 w-full rounded-md" />
|
|
77
|
+
{/each}
|
|
78
|
+
</div>
|
|
79
|
+
{:else if children}
|
|
80
|
+
{@render children(defaultTabs.find((tab) => tab.id === currentTab) ?? defaultTabs[0])}
|
|
81
|
+
{:else if currentTab === "description"}
|
|
82
|
+
<div class="flex max-w-3xl flex-col gap-5">
|
|
83
|
+
<ProductDetailDescription
|
|
84
|
+
description={data.description}
|
|
85
|
+
longDescription={data.longDescription}
|
|
86
|
+
/>
|
|
87
|
+
{#if data.features}
|
|
88
|
+
<ProductDetailFeatures features={data.features} />
|
|
89
|
+
{/if}
|
|
90
|
+
</div>
|
|
91
|
+
{:else if currentTab === "reviews"}
|
|
92
|
+
<div class="max-w-3xl">
|
|
93
|
+
<ProductDetailReviews reviews={data.reviews} />
|
|
94
|
+
</div>
|
|
95
|
+
{/if}
|
|
96
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { ProductDetailsData, ProductDetailsTab } from "../types";
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
data: ProductDetailsData;
|
|
5
|
+
tabs?: ProductDetailsTab[];
|
|
6
|
+
isLoading?: boolean;
|
|
7
|
+
/** Snippet opcional para renderizar o conteúdo de cada aba. */
|
|
8
|
+
children?: Snippet<[ProductDetailsTab]>;
|
|
9
|
+
};
|
|
10
|
+
declare const ProductDetailTabs: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
11
|
+
type ProductDetailTabs = ReturnType<typeof ProductDetailTabs>;
|
|
12
|
+
export default ProductDetailTabs;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
/**
|
|
3
|
+
* Lista de tags do produto (ex: localização, categoria).
|
|
4
|
+
* Quando `isLoading` é true, exibe Skeletons.
|
|
5
|
+
* @component
|
|
6
|
+
* @example
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <ProductDetailTags tags={[{ text: "Luanda" }, { text: "Kilamba" }]} />
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import CardTags from "../../../core/card/shared/CardTags.svelte";
|
|
15
|
+
import Skeleton from "../../../ui/skeleton/skeleton.svelte";
|
|
16
|
+
import type { ProductDetailsTag } from "../types";
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
tags,
|
|
20
|
+
isLoading = false,
|
|
21
|
+
}: {
|
|
22
|
+
tags?: ProductDetailsTag[];
|
|
23
|
+
isLoading?: boolean;
|
|
24
|
+
} = $props();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
{#if isLoading}
|
|
28
|
+
<div class="flex flex-wrap gap-2">
|
|
29
|
+
{#each Array.from({ length: 3 }) as _, i (i)}
|
|
30
|
+
<Skeleton class="h-5 w-20 rounded-full" />
|
|
31
|
+
{/each}
|
|
32
|
+
</div>
|
|
33
|
+
{:else if tags?.length}
|
|
34
|
+
<CardTags tags={tags} isWrap />
|
|
35
|
+
{/if}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ProductDetailsTag } from "../types";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
tags?: ProductDetailsTag[];
|
|
4
|
+
isLoading?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare const ProductDetailTags: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type ProductDetailTags = ReturnType<typeof ProductDetailTags>;
|
|
8
|
+
export default ProductDetailTags;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
/**
|
|
3
|
+
* Painel de compra do produto: avaliação, preço e ações.
|
|
4
|
+
* Compõe ProductDetailRating, ProductDetailPrice e ProductDetailActions.
|
|
5
|
+
* @component
|
|
6
|
+
* @example
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <ProductPurchasePanel {data} isLoading={true} />
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import ProductDetailActions from "./ProductDetailActions.svelte";
|
|
15
|
+
import ProductDetailPrice from "./ProductDetailPrice.svelte";
|
|
16
|
+
import ProductDetailRating from "./ProductDetailRating.svelte";
|
|
17
|
+
import type { ProductDetailsData } from "../types";
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
data,
|
|
21
|
+
isLoading = false,
|
|
22
|
+
onBuy,
|
|
23
|
+
onFavorite,
|
|
24
|
+
onCart,
|
|
25
|
+
}: {
|
|
26
|
+
data: ProductDetailsData;
|
|
27
|
+
isLoading?: boolean;
|
|
28
|
+
onBuy?: (id: string | number) => void;
|
|
29
|
+
onFavorite?: (id: string | number) => void;
|
|
30
|
+
onCart?: (id: string | number) => void;
|
|
31
|
+
} = $props();
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<div class="flex flex-col gap-4">
|
|
35
|
+
<ProductDetailRating
|
|
36
|
+
rating={data.rating}
|
|
37
|
+
reviewsCount={data.reviewsCount}
|
|
38
|
+
{isLoading}
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
<ProductDetailPrice
|
|
42
|
+
newPrice={data.newPrice}
|
|
43
|
+
oldPrice={data.oldPrice}
|
|
44
|
+
currency={data.currency}
|
|
45
|
+
{isLoading}
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
<ProductDetailActions
|
|
49
|
+
id={data.id}
|
|
50
|
+
buttonBuyText={data.buttonBuyText}
|
|
51
|
+
isFavorite={data.isFavorite}
|
|
52
|
+
isCart={data.isCart}
|
|
53
|
+
{isLoading}
|
|
54
|
+
{onBuy}
|
|
55
|
+
{onFavorite}
|
|
56
|
+
{onCart}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ProductDetailsData } from "../types";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
data: ProductDetailsData;
|
|
4
|
+
isLoading?: boolean;
|
|
5
|
+
onBuy?: (id: string | number) => void;
|
|
6
|
+
onFavorite?: (id: string | number) => void;
|
|
7
|
+
onCart?: (id: string | number) => void;
|
|
8
|
+
};
|
|
9
|
+
declare const ProductPurchasePanel: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type ProductPurchasePanel = ReturnType<typeof ProductPurchasePanel>;
|
|
11
|
+
export default ProductPurchasePanel;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { IconSvgElement } from "@hugeicons/svelte";
|
|
2
|
+
/**
|
|
3
|
+
* Item individual do breadcrumb de navegação.
|
|
4
|
+
* Representa um nível hierárquico na navegação da página
|
|
5
|
+
* (ex: Home / Turismo / Hotel Baía Azul).
|
|
6
|
+
*/
|
|
7
|
+
export interface ProductDetailsBreadcrumbItem {
|
|
8
|
+
/** Texto visível do item no breadcrumb. */
|
|
9
|
+
label: string;
|
|
10
|
+
/** URL de destino quando o item é clicável (último item geralmente não tem href). */
|
|
11
|
+
href?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Tag/badge associada ao produto.
|
|
15
|
+
* Usada para destacar categorias, localização ou características rápidas.
|
|
16
|
+
*/
|
|
17
|
+
export interface ProductDetailsTag {
|
|
18
|
+
/** Ícone opcional exibido antes do texto da tag. */
|
|
19
|
+
icon?: IconSvgElement;
|
|
20
|
+
/** Texto principal da tag (ex: "Luanda", "5 Estrelas"). */
|
|
21
|
+
text: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Imagem da galeria do produto.
|
|
25
|
+
* Cada imagem pode ter um texto alternativo para acessibilidade.
|
|
26
|
+
*/
|
|
27
|
+
export interface ProductDetailsImage {
|
|
28
|
+
/** Caminho ou URL completa da imagem (obrigatório). */
|
|
29
|
+
src: string;
|
|
30
|
+
/** Texto alternativo para acessibilidade (recomendado). */
|
|
31
|
+
alt?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Informações de localização do produto.
|
|
35
|
+
* Usada para exibir o mapa embed e detalhes do endereço.
|
|
36
|
+
*/
|
|
37
|
+
export interface ProductDetailsLocation {
|
|
38
|
+
/** Nome/título da localização (ex: "Morro da Luz"). */
|
|
39
|
+
label?: string;
|
|
40
|
+
/** Endereço completo ou parcial para exibição. */
|
|
41
|
+
address?: string;
|
|
42
|
+
/**
|
|
43
|
+
* URL do embed do mapa.
|
|
44
|
+
* Suporta OpenStreetMap, Google Maps e outros serviços de embed.
|
|
45
|
+
* Se não for fornecido, usa um mapa padrão.
|
|
46
|
+
*/
|
|
47
|
+
mapUrl?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Avaliação de um cliente sobre o produto.
|
|
51
|
+
* Contém autor, classificação, data e comentário.
|
|
52
|
+
*/
|
|
53
|
+
export interface ProductDetailsReview {
|
|
54
|
+
/** Identificador único da avaliação (usado como key nos {#each}). */
|
|
55
|
+
id: string | number;
|
|
56
|
+
/** Nome do autor da avaliação. */
|
|
57
|
+
author: string;
|
|
58
|
+
/**
|
|
59
|
+
* Classificação numérica (ex: 4.5 de 5).
|
|
60
|
+
* Geralmente um valor entre 0 e 5.
|
|
61
|
+
*/
|
|
62
|
+
rating: number;
|
|
63
|
+
/** Data da avaliação (formato string, ex: "12/08/2024"). */
|
|
64
|
+
date?: string;
|
|
65
|
+
/** Comentário/texto completo da avaliação. */
|
|
66
|
+
comment: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Aba de conteúdo adicional no produto.
|
|
70
|
+
* Usada para organizar descrição, avaliações, detalhes técnicos, etc.
|
|
71
|
+
*/
|
|
72
|
+
export interface ProductDetailsTab {
|
|
73
|
+
/** Identificador único da aba (ex: "description", "reviews"). */
|
|
74
|
+
id: string;
|
|
75
|
+
/** Rótulo visível no botão da aba. */
|
|
76
|
+
label: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Dados completos apresentados na página de detalhes do produto.
|
|
80
|
+
* Interface principal que agrega todas as informações necessárias
|
|
81
|
+
* para renderizar qualquer variante do componente.
|
|
82
|
+
*/
|
|
83
|
+
export interface ProductDetailsData {
|
|
84
|
+
/** Identificador único do produto. */
|
|
85
|
+
id: string | number;
|
|
86
|
+
/** Itens do breadcrumb de navegação. */
|
|
87
|
+
breadcrumb: ProductDetailsBreadcrumbItem[];
|
|
88
|
+
/** Título principal do produto (nome). */
|
|
89
|
+
title: string;
|
|
90
|
+
/** Subtítulo opcional (slogan ou descrição curta). */
|
|
91
|
+
subtitle?: string;
|
|
92
|
+
/** Descrição curta do produto. */
|
|
93
|
+
description: string;
|
|
94
|
+
/** Descrição longa/detalhada (opcional). */
|
|
95
|
+
longDescription?: string;
|
|
96
|
+
/** Lista de tags/categorias associadas ao produto. */
|
|
97
|
+
tags?: ProductDetailsTag[];
|
|
98
|
+
/** Galeria de imagens do produto. */
|
|
99
|
+
gallery: ProductDetailsImage[];
|
|
100
|
+
/** Informações de localização para exibir no mapa. */
|
|
101
|
+
location?: ProductDetailsLocation;
|
|
102
|
+
/**
|
|
103
|
+
* Classificação média do produto.
|
|
104
|
+
* Valor entre 0 e 5 (ex: 4.2).
|
|
105
|
+
*/
|
|
106
|
+
rating?: number;
|
|
107
|
+
/** Número total de avaliações recebidas. */
|
|
108
|
+
reviewsCount?: number;
|
|
109
|
+
/** Lista de avaliações individuais dos clientes. */
|
|
110
|
+
reviews?: ProductDetailsReview[];
|
|
111
|
+
/** Preço atual/promocional do produto. */
|
|
112
|
+
newPrice?: string | number;
|
|
113
|
+
/** Preço original (antes do desconto). */
|
|
114
|
+
oldPrice?: string | number;
|
|
115
|
+
/** Símbolo da moeda (ex: "Kz", "€", "$"). */
|
|
116
|
+
currency?: string;
|
|
117
|
+
/** Quantidade disponível em stock. */
|
|
118
|
+
quantityAvailable?: number;
|
|
119
|
+
/** Lista de características/features do produto (strings). */
|
|
120
|
+
features?: string[];
|
|
121
|
+
/**
|
|
122
|
+
* Abas de conteúdo personalizadas.
|
|
123
|
+
* Se não for fornecido, usa padrão: Descrição + Avaliações.
|
|
124
|
+
*/
|
|
125
|
+
tabs?: ProductDetailsTab[];
|
|
126
|
+
/** Texto customizado para o botão de compra (padrão: "Comprar"). */
|
|
127
|
+
buttonBuyText?: string;
|
|
128
|
+
/** Estado inicial do produto no carrinho. */
|
|
129
|
+
isCart?: boolean;
|
|
130
|
+
/** Estado inicial do produto nos favoritos. */
|
|
131
|
+
isFavorite?: boolean;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Propriedades do componente principal ProductDetails.
|
|
135
|
+
* Suporta quatro variantes de layout e callbacks de interação.
|
|
136
|
+
*/
|
|
137
|
+
export interface ProductDetailsProps {
|
|
138
|
+
/**
|
|
139
|
+
* Variante do layout da página de detalhes.
|
|
140
|
+
* - 1: Duas colunas (galeria à esquerda, info à direita)
|
|
141
|
+
* - 2: Galeria em destaque com conteúdo centralizado
|
|
142
|
+
* - 3: Painel de compra sticky no lado direito
|
|
143
|
+
* - 4: Abas de conteúdo (descrição e avaliações) abaixo da info
|
|
144
|
+
* @default 1
|
|
145
|
+
*/
|
|
146
|
+
varient?: 1 | 2 | 3 | 4;
|
|
147
|
+
/** Dados completos do produto a serem exibidos. */
|
|
148
|
+
data: ProductDetailsData;
|
|
149
|
+
/**
|
|
150
|
+
* Estado de carregamento.
|
|
151
|
+
* Quando true, todos os blocos exibem Skeletons no lugar do conteúdo.
|
|
152
|
+
* @default false
|
|
153
|
+
*/
|
|
154
|
+
isLoading?: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* Callback executado quando o utilizador clica no botão de compra.
|
|
157
|
+
* Recebe o id do produto como argumento.
|
|
158
|
+
*/
|
|
159
|
+
onBuy?: (id: string | number) => void;
|
|
160
|
+
/**
|
|
161
|
+
* Callback executado quando o utilizador clica no botão de favorito.
|
|
162
|
+
* Recebe o id do produto como argumento.
|
|
163
|
+
*/
|
|
164
|
+
onFavorite?: (id: string | number) => void;
|
|
165
|
+
/**
|
|
166
|
+
* Callback executado quando o utilizador clica no botão de carrinho.
|
|
167
|
+
* Recebe o id do produto como argumento.
|
|
168
|
+
*/
|
|
169
|
+
onCart?: (id: string | number) => void;
|
|
170
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
/**
|
|
3
|
+
* Variante 01 da página de perfil. Reutiliza headers partilhados,
|
|
4
|
+
* tabs, moradas, segurança e definições.
|
|
5
|
+
* @component
|
|
6
|
+
*/
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
import { t } from "../../../../i18n";
|
|
11
|
+
import type { ProfileUserData } from "../types";
|
|
12
|
+
import ProfileUserHeader from "../shared/ProfileUserHeader.svelte";
|
|
13
|
+
import ProfileUserTabs from "../shared/ProfileUserTabs.svelte";
|
|
14
|
+
import ProfileUserAddress from "../shared/ProfileUserAddress.svelte";
|
|
15
|
+
import ProfileUserResetPassword from "../shared/ProfileUserResetPassword.svelte";
|
|
16
|
+
import ProfileUserSetting from "../shared/ProfileUserSetting.svelte";
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
data,
|
|
20
|
+
activeTab = $bindable(),
|
|
21
|
+
isLoading = false,
|
|
22
|
+
onLoginAsUser,
|
|
23
|
+
onAddAddress,
|
|
24
|
+
onChangePassword,
|
|
25
|
+
tabs: customTabs,
|
|
26
|
+
// Header V1
|
|
27
|
+
statusDefault = $t("profile-user.variant1.statusActive"),
|
|
28
|
+
roleDefault = $t("profile-user.variant1.roleAdministrator"),
|
|
29
|
+
loginAsUserLabel = $t("profile-user.variant1.loginAsUser"),
|
|
30
|
+
// Address
|
|
31
|
+
addressesTitle = $t("profile-user.variant1.addresses.title"),
|
|
32
|
+
addressesAddLabel = $t("profile-user.variant1.addresses.add"),
|
|
33
|
+
addressesDefaultBadge = $t("profile-user.variant1.addresses.defaultBadge"),
|
|
34
|
+
addressesEmptyText = $t("profile-user.variant1.addresses.empty"),
|
|
35
|
+
}: {
|
|
36
|
+
data: ProfileUserData;
|
|
37
|
+
activeTab?: string;
|
|
38
|
+
isLoading?: boolean;
|
|
39
|
+
onLoginAsUser?: (id: string | number) => void;
|
|
40
|
+
onAddAddress?: () => void;
|
|
41
|
+
onChangePassword?: (payload: {
|
|
42
|
+
currentPassword: string;
|
|
43
|
+
newPassword: string;
|
|
44
|
+
confirmPassword: string;
|
|
45
|
+
}) => void;
|
|
46
|
+
tabs?: { id: string; label: string }[];
|
|
47
|
+
// Header V1
|
|
48
|
+
statusDefault?: string;
|
|
49
|
+
roleDefault?: string;
|
|
50
|
+
loginAsUserLabel?: string;
|
|
51
|
+
// Address
|
|
52
|
+
addressesTitle?: string;
|
|
53
|
+
addressesAddLabel?: string;
|
|
54
|
+
addressesDefaultBadge?: string;
|
|
55
|
+
addressesEmptyText?: string;
|
|
56
|
+
} = $props();
|
|
57
|
+
|
|
58
|
+
const defaultTabs = $derived(
|
|
59
|
+
customTabs?.length
|
|
60
|
+
? customTabs
|
|
61
|
+
: [
|
|
62
|
+
{ id: "addresses", label: $t("profile-user.variant1.tabs.addresses") || "Moradas" },
|
|
63
|
+
{ id: "security", label: $t("profile-user.variant2.tabs.security") || "Segurança" },
|
|
64
|
+
{ id: "settings", label: $t("profile-user.variant1.tabs.settings") || "Definições" },
|
|
65
|
+
]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const currentTab = $derived(activeTab ?? defaultTabs[0]?.id ?? "addresses");
|
|
69
|
+
const fullName = $derived(data.fullName ?? `${data.firstName} ${data.lastName}`);
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<div class="bg-background">
|
|
73
|
+
<ProfileUserHeader
|
|
74
|
+
{data}
|
|
75
|
+
{isLoading}
|
|
76
|
+
{fullName}
|
|
77
|
+
{onLoginAsUser}
|
|
78
|
+
{statusDefault}
|
|
79
|
+
{roleDefault}
|
|
80
|
+
{loginAsUserLabel}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
<div class="mx-auto max-w-7xl px-6 py-8">
|
|
84
|
+
<ProfileUserTabs
|
|
85
|
+
tabs={defaultTabs}
|
|
86
|
+
bind:activeTab
|
|
87
|
+
{isLoading}
|
|
88
|
+
/>
|
|
89
|
+
|
|
90
|
+
<div class="flex flex-col gap-8">
|
|
91
|
+
{#if currentTab === "addresses"}
|
|
92
|
+
<ProfileUserAddress
|
|
93
|
+
addresses={data.addresses}
|
|
94
|
+
{isLoading}
|
|
95
|
+
{onAddAddress}
|
|
96
|
+
title={addressesTitle}
|
|
97
|
+
addLabel={addressesAddLabel}
|
|
98
|
+
defaultBadge={addressesDefaultBadge}
|
|
99
|
+
emptyText={addressesEmptyText}
|
|
100
|
+
/>
|
|
101
|
+
{:else if currentTab === "security"}
|
|
102
|
+
<ProfileUserResetPassword {isLoading} {onChangePassword} />
|
|
103
|
+
{:else if currentTab === "settings"}
|
|
104
|
+
<ProfileUserSetting
|
|
105
|
+
notificationPref={data.notificationPref}
|
|
106
|
+
enableNotification={data.enableNotification}
|
|
107
|
+
marketingPrefs={data.marketingPrefs}
|
|
108
|
+
{isLoading}
|
|
109
|
+
/>
|
|
110
|
+
{/if}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ProfileUserData } from "../types";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
data: ProfileUserData;
|
|
4
|
+
activeTab?: string;
|
|
5
|
+
isLoading?: boolean;
|
|
6
|
+
onLoginAsUser?: (id: string | number) => void;
|
|
7
|
+
onAddAddress?: () => void;
|
|
8
|
+
onChangePassword?: (payload: {
|
|
9
|
+
currentPassword: string;
|
|
10
|
+
newPassword: string;
|
|
11
|
+
confirmPassword: string;
|
|
12
|
+
}) => void;
|
|
13
|
+
tabs?: {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
}[];
|
|
17
|
+
statusDefault?: string;
|
|
18
|
+
roleDefault?: string;
|
|
19
|
+
loginAsUserLabel?: string;
|
|
20
|
+
addressesTitle?: string;
|
|
21
|
+
addressesAddLabel?: string;
|
|
22
|
+
addressesDefaultBadge?: string;
|
|
23
|
+
addressesEmptyText?: string;
|
|
24
|
+
};
|
|
25
|
+
declare const ProfileUser01: import("svelte").Component<$$ComponentProps, {}, "activeTab">;
|
|
26
|
+
type ProfileUser01 = ReturnType<typeof ProfileUser01>;
|
|
27
|
+
export default ProfileUser01;
|