@fayz-ai/plugin-reputation 0.9.3 → 0.10.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.
- package/README.md +30 -5
- package/dist/chunk-BGCEG4NA.js +410 -0
- package/dist/chunk-BGCEG4NA.js.map +1 -0
- package/dist/components/ProductRating.d.ts +15 -0
- package/dist/components/ProductRating.d.ts.map +1 -0
- package/dist/components/ProductReviews.d.ts +12 -0
- package/dist/components/ProductReviews.d.ts.map +1 -0
- package/dist/components/Stars.d.ts +15 -0
- package/dist/components/Stars.d.ts.map +1 -0
- package/dist/components/TopReviews.d.ts +13 -0
- package/dist/components/TopReviews.d.ts.map +1 -0
- package/dist/data/index.d.ts +4 -1
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/mock.d.ts +2 -0
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/registries.d.ts +3 -0
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/data/supabase.d.ts +5 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/tables.d.ts +8 -0
- package/dist/data/tables.d.ts.map +1 -0
- package/dist/data/types.d.ts +24 -3
- package/dist/data/types.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useManagedReviews.d.ts +23 -0
- package/dist/hooks/useManagedReviews.d.ts.map +1 -0
- package/dist/hooks/useProductReviews.d.ts +23 -0
- package/dist/hooks/useProductReviews.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +238 -48
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +6 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/public/index.d.ts +14 -2
- package/dist/public/index.d.ts.map +1 -1
- package/dist/public/index.js +295 -84
- package/dist/public/index.js.map +1 -1
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/ReputationHome.d.ts.map +1 -1
- package/package.json +7 -6
package/dist/data/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export type { ReputationDataProvider } from './types';
|
|
2
|
-
export { createMockReputationProvider } from './mock';
|
|
2
|
+
export { createMockReputationProvider, computeSummary } from './mock';
|
|
3
3
|
export type { ReputationSeed, MockReputationProviderOptions } from './mock';
|
|
4
4
|
export { createSupabaseReputationProvider } from './supabase';
|
|
5
|
+
export type { SupabaseReputationProviderOptions } from './supabase';
|
|
6
|
+
export { T } from './tables';
|
|
7
|
+
export { reputationRegistries } from './registries';
|
|
5
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/data/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AACrD,OAAO,EAAE,4BAA4B,EAAE,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AACrD,OAAO,EAAE,4BAA4B,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AACrE,YAAY,EAAE,cAAc,EAAE,6BAA6B,EAAE,MAAM,QAAQ,CAAA;AAC3E,OAAO,EAAE,gCAAgC,EAAE,MAAM,YAAY,CAAA;AAC7D,YAAY,EAAE,iCAAiC,EAAE,MAAM,YAAY,CAAA;AACnE,OAAO,EAAE,CAAC,EAAE,MAAM,UAAU,CAAA;AAC5B,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/data/mock.d.ts
CHANGED
|
@@ -8,5 +8,7 @@ export interface ReputationSeed {
|
|
|
8
8
|
export interface MockReputationProviderOptions {
|
|
9
9
|
seed?: ReputationSeed;
|
|
10
10
|
}
|
|
11
|
+
export declare const SEED_REVIEWS: Review[];
|
|
12
|
+
export declare function computeSummary(reviews: readonly Review[]): ReviewSummary;
|
|
11
13
|
export declare function createMockReputationProvider(options?: MockReputationProviderOptions): ReputationDataProvider;
|
|
12
14
|
//# sourceMappingURL=mock.d.ts.map
|
package/dist/data/mock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../src/data/mock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AACrD,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../src/data/mock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AACrD,OAAO,KAAK,EACV,MAAM,EACN,aAAa,EAOd,MAAM,UAAU,CAAA;AAEjB,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,qEAAqE;IACrE,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,CAAC,EAAE,cAAc,CAAA;CACtB;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,EAIhC,CAAA;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,CAQxE;AAiBD,wBAAgB,4BAA4B,CAAC,OAAO,CAAC,EAAE,6BAA6B,GAAG,sBAAsB,CAuG5G"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registries.d.ts","sourceRoot":"","sources":["../../src/data/registries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAa,MAAM,eAAe,CAAA;AAoDjE,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,EAqCnD,CAAA"}
|
package/dist/data/supabase.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import type { ReputationDataProvider } from './types';
|
|
2
|
-
export
|
|
2
|
+
export interface SupabaseReputationProviderOptions {
|
|
3
|
+
/** Tenant whose approved reviews to read. Defaults to the active session tenant. */
|
|
4
|
+
tenantId?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function createSupabaseReputationProvider(options?: SupabaseReputationProviderOptions): ReputationDataProvider;
|
|
3
7
|
//# sourceMappingURL=supabase.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AA+BrD,MAAM,WAAW,iCAAiC;IAChD,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AA4FD,wBAAgB,gCAAgC,CAC9C,OAAO,CAAC,EAAE,iCAAiC,GAC1C,sBAAsB,CAyJxB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const T: {
|
|
2
|
+
readonly reviews: "plg_reputation_reviews";
|
|
3
|
+
/** Anon-readable, approved-only feed. Read by the storefront surface. */
|
|
4
|
+
readonly publicReviews: "v_public_product_reviews";
|
|
5
|
+
/** Anon-readable per-product aggregate. */
|
|
6
|
+
readonly publicRatings: "v_public_product_ratings";
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=tables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/data/tables.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,CAAC;;IAEZ,yEAAyE;;IAEzE,2CAA2C;;CAEnC,CAAA"}
|
package/dist/data/types.d.ts
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
|
-
import type { Review, ReviewSummary, ReviewListQuery } from '../types';
|
|
1
|
+
import type { Review, ReviewSummary, ReviewListQuery, ProductReviewQuery, TopReviewsQuery, ManagedReviewQuery, ReviewInput, ReviewStatus } from '../types';
|
|
2
2
|
/**
|
|
3
|
-
* Data seam for reputation. Mock/seed powers
|
|
4
|
-
*
|
|
3
|
+
* Data seam for reputation. Mock/seed powers local work and any host with no
|
|
4
|
+
* Supabase client; the Supabase provider reads the anon-safe views shipped in
|
|
5
|
+
* `migrations/001_product_reviews.sql`. Components never know which is active.
|
|
6
|
+
*
|
|
7
|
+
* The product methods are separate from `listReviews` on purpose: a PDP wants
|
|
8
|
+
* ONE aggregate row, not every review, and `getProductRating` is a single read
|
|
9
|
+
* of `v_public_product_ratings` rather than a client-side reduce over a list.
|
|
5
10
|
*/
|
|
6
11
|
export interface ReputationDataProvider {
|
|
12
|
+
/** Every review (business + product), for the backoffice feed. */
|
|
7
13
|
listReviews(query?: ReviewListQuery): Promise<Review[]>;
|
|
14
|
+
/** Business-wide aggregate — the reputation home's headline number. */
|
|
8
15
|
getSummary(): Promise<ReviewSummary>;
|
|
16
|
+
/** Aggregate for one product. `count: 0` when the product has no reviews. */
|
|
17
|
+
getProductRating(productId: string): Promise<ReviewSummary>;
|
|
18
|
+
/** Ratings for many products in one round trip — a catalogue grid. */
|
|
19
|
+
getProductRatings(productIds: readonly string[]): Promise<Record<string, ReviewSummary>>;
|
|
20
|
+
/** The review list under a PDP. */
|
|
21
|
+
listProductReviews(query: ProductReviewQuery): Promise<Review[]>;
|
|
22
|
+
/** Social proof for a homepage section. */
|
|
23
|
+
listTopReviews(query?: TopReviewsQuery): Promise<Review[]>;
|
|
24
|
+
/** Every review including pending and rejected — what moderation needs. */
|
|
25
|
+
listManagedReviews?(query?: ManagedReviewQuery): Promise<Review[]>;
|
|
26
|
+
createReview?(input: ReviewInput): Promise<Review>;
|
|
27
|
+
updateReview?(id: string, patch: Partial<ReviewInput>): Promise<Review>;
|
|
28
|
+
setReviewStatus?(id: string, status: ReviewStatus): Promise<Review>;
|
|
29
|
+
deleteReview?(id: string): Promise<void>;
|
|
9
30
|
}
|
|
10
31
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/data/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,YAAY,EACb,MAAM,UAAU,CAAA;AAEjB;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,WAAW,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACvD,uEAAuE;IACvE,UAAU,IAAI,OAAO,CAAC,aAAa,CAAC,CAAA;IAEpC,6EAA6E;IAC7E,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAC3D,sEAAsE;IACtE,iBAAiB,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAA;IACxF,mCAAmC;IACnC,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAChE,2CAA2C;IAC3C,cAAc,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAQ1D,2EAA2E;IAC3E,kBAAkB,CAAC,CAAC,KAAK,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAClE,YAAY,CAAC,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAClD,YAAY,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvE,eAAe,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACnE,YAAY,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACzC"}
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -2,4 +2,8 @@ export { useReviews } from './useReviews';
|
|
|
2
2
|
export type { UseReviewsResult } from './useReviews';
|
|
3
3
|
export { useReviewSummary } from './useReviewSummary';
|
|
4
4
|
export type { UseReviewSummaryResult } from './useReviewSummary';
|
|
5
|
+
export { useProductRating, useProductReviews, useTopReviews } from './useProductReviews';
|
|
6
|
+
export type { UseProductRatingResult, UseProductReviewsResult } from './useProductReviews';
|
|
7
|
+
export { useManagedReviews } from './useManagedReviews';
|
|
8
|
+
export type { UseManagedReviewsResult } from './useManagedReviews';
|
|
5
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxF,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AAC1F,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,YAAY,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ManagedReviewQuery, Review, ReviewStatus } from '../types';
|
|
2
|
+
export interface UseManagedReviewsResult {
|
|
3
|
+
reviews: Review[];
|
|
4
|
+
loading: boolean;
|
|
5
|
+
error: Error | null;
|
|
6
|
+
/** False when the active provider is read-only (the anon storefront one). */
|
|
7
|
+
canModerate: boolean;
|
|
8
|
+
refresh: () => void;
|
|
9
|
+
setStatus: (id: string, status: ReviewStatus) => Promise<void>;
|
|
10
|
+
reply: (id: string, text: string) => Promise<void>;
|
|
11
|
+
remove: (id: string) => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The backoffice list: every review, whatever its status.
|
|
15
|
+
*
|
|
16
|
+
* Separate from `useReviews` because that one reads the public, approved-only
|
|
17
|
+
* view — a moderation queue built on it could never show the thing it exists to
|
|
18
|
+
* moderate. Mutations refetch rather than patch local state: status changes also
|
|
19
|
+
* move `published_at`, and guessing that client-side is how a list drifts from
|
|
20
|
+
* the table it claims to show.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useManagedReviews(query?: ManagedReviewQuery): UseManagedReviewsResult;
|
|
23
|
+
//# sourceMappingURL=useManagedReviews.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useManagedReviews.d.ts","sourceRoot":"","sources":["../../src/hooks/useManagedReviews.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAExE,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,6EAA6E;IAC7E,WAAW,EAAE,OAAO,CAAA;IACpB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9D,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAClD,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,EAAE,kBAAkB,GAAG,uBAAuB,CAuErF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Review, ReviewSummary, ProductReviewQuery, TopReviewsQuery } from '../types';
|
|
2
|
+
export interface UseProductRatingResult {
|
|
3
|
+
rating: ReviewSummary;
|
|
4
|
+
loading: boolean;
|
|
5
|
+
error: Error | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The aggregate for one product — one row, not a list.
|
|
9
|
+
*
|
|
10
|
+
* `productId` may be undefined while the PDP is still resolving its product;
|
|
11
|
+
* the hook stays idle rather than firing a query for `undefined`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useProductRating(productId: string | undefined): UseProductRatingResult;
|
|
14
|
+
export interface UseProductReviewsResult {
|
|
15
|
+
reviews: Review[];
|
|
16
|
+
loading: boolean;
|
|
17
|
+
error: Error | null;
|
|
18
|
+
}
|
|
19
|
+
/** The review list under a PDP. */
|
|
20
|
+
export declare function useProductReviews(query: Partial<ProductReviewQuery>): UseProductReviewsResult;
|
|
21
|
+
/** Social proof for a homepage section. */
|
|
22
|
+
export declare function useTopReviews(query?: TopReviewsQuery): UseProductReviewsResult;
|
|
23
|
+
//# sourceMappingURL=useProductReviews.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useProductReviews.d.ts","sourceRoot":"","sources":["../../src/hooks/useProductReviews.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAI1F,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,aAAa,CAAA;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,sBAAsB,CAmCtF;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CACpB;AAED,mCAAmC;AACnC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,uBAAuB,CAqC7F;AAED,2CAA2C;AAC3C,wBAAgB,aAAa,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,uBAAuB,CA0C9E"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import type { PluginManifest, PluginScope, VerticalId } from '@fayz-ai/core';
|
|
2
|
+
import { type ReputationSeed } from './data/mock';
|
|
3
|
+
import type { ReputationDataProvider } from './data/types';
|
|
2
4
|
export interface ReputationPluginOptions {
|
|
3
5
|
navPosition?: number;
|
|
4
6
|
navSection?: 'main' | 'secondary' | 'settings';
|
|
5
7
|
navLabel?: string;
|
|
6
8
|
scope?: PluginScope;
|
|
7
9
|
verticalId?: VerticalId;
|
|
10
|
+
/** Inject a custom provider. Overrides the safe Supabase/mock resolver. */
|
|
11
|
+
dataProvider?: ReputationDataProvider;
|
|
12
|
+
/** Reviews to run on until the tables are populated (or in a demo). */
|
|
13
|
+
seed?: ReputationSeed;
|
|
14
|
+
/** Tenant to read. Defaults to the active session tenant, which is what the
|
|
15
|
+
* backoffice normally wants — pass this only for a fixed single-tenant app. */
|
|
16
|
+
tenantId?: string;
|
|
8
17
|
}
|
|
9
18
|
export declare function createReputationPlugin(options?: ReputationPluginOptions): PluginManifest;
|
|
10
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAI5E,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,aAAa,CAAA;AAE/E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAsB1D,MAAM,WAAW,uBAAuB;IACtC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,sBAAsB,CAAA;IACrC,uEAAuE;IACvE,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB;oFACgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,cAAc,CA+DxF"}
|
package/dist/index.js
CHANGED
|
@@ -1,79 +1,257 @@
|
|
|
1
|
+
import { createSupabaseReputationProvider, createMockReputationProvider, ReputationProvider, useReviewSummary, useManagedReviews } from './chunk-BGCEG4NA.js';
|
|
1
2
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
3
|
+
import { createSafeDataProvider } from '@fayz-ai/core';
|
|
4
|
+
import { Check, X, Trash2, Send, Star } from 'lucide-react';
|
|
5
|
+
import { Button, PageHeaderActions, DataTable } from '@fayz-ai/ui';
|
|
4
6
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{ id: "
|
|
9
|
-
{ id: "
|
|
10
|
-
{ id: "
|
|
11
|
-
{ id: "4", author: "Marco P.", source: "Google", rating: 5, text: "Best in town. Highly recommend to anyone.", date: "Jun 9", replied: true },
|
|
12
|
-
{ id: "5", author: "Lena S.", source: "Facebook", rating: 3, text: "Good but the pricing could be clearer up front.", date: "Jun 6", replied: false }
|
|
13
|
-
];
|
|
14
|
-
var DISTRIBUTION = [
|
|
15
|
-
{ stars: 5, count: 182 },
|
|
16
|
-
{ stars: 4, count: 41 },
|
|
17
|
-
{ stars: 3, count: 12 },
|
|
18
|
-
{ stars: 2, count: 4 },
|
|
19
|
-
{ stars: 1, count: 3 }
|
|
8
|
+
var STATUS_TABS = [
|
|
9
|
+
{ id: "pending", label: "Pendentes" },
|
|
10
|
+
{ id: "approved", label: "Publicadas" },
|
|
11
|
+
{ id: "rejected", label: "Recusadas" },
|
|
12
|
+
{ id: "all", label: "Todas" }
|
|
20
13
|
];
|
|
21
14
|
function Stars({ rating, className }) {
|
|
22
|
-
return /* @__PURE__ */ jsx("div", { className: `flex items-center gap-0.5 ${className ?? ""}`, children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx(
|
|
15
|
+
return /* @__PURE__ */ jsx("div", { className: `flex items-center gap-0.5 ${className ?? ""}`, children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx(
|
|
16
|
+
Star,
|
|
17
|
+
{
|
|
18
|
+
className: `h-4 w-4 ${i <= Math.round(rating) ? "fill-amber-400 text-amber-400" : "text-muted-foreground/30"}`
|
|
19
|
+
},
|
|
20
|
+
i
|
|
21
|
+
)) });
|
|
23
22
|
}
|
|
24
23
|
function ReputationHome() {
|
|
25
|
-
const
|
|
24
|
+
const [tab, setTab] = React.useState("pending");
|
|
25
|
+
const { summary: loadedSummary } = useReviewSummary();
|
|
26
|
+
const summary = loadedSummary ?? { average: 0, count: 0, distribution: [] };
|
|
27
|
+
const { reviews, loading, canModerate, setStatus, remove } = useManagedReviews(
|
|
28
|
+
tab === "all" ? {} : { status: tab }
|
|
29
|
+
);
|
|
30
|
+
const distribution = summary.distribution ?? [];
|
|
31
|
+
const total = summary.count;
|
|
32
|
+
const columns = React.useMemo(
|
|
33
|
+
() => [
|
|
34
|
+
{
|
|
35
|
+
id: "author",
|
|
36
|
+
header: "Cliente",
|
|
37
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "min-w-[9rem]", children: [
|
|
38
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: row.original.author }),
|
|
39
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: row.original.date || "\u2014" })
|
|
40
|
+
] })
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "rating",
|
|
44
|
+
header: "Nota",
|
|
45
|
+
cell: ({ row }) => /* @__PURE__ */ jsx(Stars, { rating: row.original.rating })
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "product",
|
|
49
|
+
header: "Produto",
|
|
50
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: row.original.productName ?? /* @__PURE__ */ jsx("em", { className: "opacity-70", children: "a loja" }) })
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "text",
|
|
54
|
+
header: "Avalia\xE7\xE3o",
|
|
55
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "max-w-[28rem]", children: [
|
|
56
|
+
row.original.title && /* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: row.original.title }),
|
|
57
|
+
/* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-sm text-muted-foreground", children: row.original.text }),
|
|
58
|
+
row.original.replyText && /* @__PURE__ */ jsxs("p", { className: "mt-1 line-clamp-1 border-l-2 border-border pl-2 text-xs text-muted-foreground", children: [
|
|
59
|
+
"Resposta: ",
|
|
60
|
+
row.original.replyText
|
|
61
|
+
] })
|
|
62
|
+
] })
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "actions",
|
|
66
|
+
header: "",
|
|
67
|
+
cell: ({ row }) => {
|
|
68
|
+
const r = row.original;
|
|
69
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1", children: [
|
|
70
|
+
r.status !== "approved" && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", title: "Publicar", onClick: () => void setStatus(r.id, "approved"), children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) }),
|
|
71
|
+
r.status !== "rejected" && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", title: "Recusar", onClick: () => void setStatus(r.id, "rejected"), children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) }),
|
|
72
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", title: "Excluir", onClick: () => void remove(r.id), children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }) })
|
|
73
|
+
] });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
[setStatus, remove]
|
|
78
|
+
);
|
|
26
79
|
return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl p-6", children: [
|
|
27
80
|
/* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsxs(Button, { children: [
|
|
28
81
|
/* @__PURE__ */ jsx(Send, { className: "mr-1.5 h-4 w-4" }),
|
|
29
|
-
"
|
|
82
|
+
" Pedir avalia\xE7\xF5es"
|
|
30
83
|
] }) }),
|
|
31
84
|
/* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-1 gap-4 lg:grid-cols-3", children: [
|
|
32
85
|
/* @__PURE__ */ jsxs("div", { className: "rounded-card border border-border bg-card p-5 text-center", children: [
|
|
33
|
-
/* @__PURE__ */ jsx("p", { className: "text-4xl font-bold text-foreground", children: "
|
|
34
|
-
/* @__PURE__ */ jsx(Stars, { rating:
|
|
86
|
+
/* @__PURE__ */ jsx("p", { className: "text-4xl font-bold text-foreground", children: total === 0 ? "\u2014" : summary.average.toFixed(1).replace(".", ",") }),
|
|
87
|
+
/* @__PURE__ */ jsx(Stars, { rating: summary.average, className: "mt-1 justify-center" }),
|
|
35
88
|
/* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [
|
|
36
89
|
total,
|
|
37
|
-
"
|
|
90
|
+
" ",
|
|
91
|
+
total === 1 ? "avalia\xE7\xE3o" : "avalia\xE7\xF5es"
|
|
38
92
|
] })
|
|
39
93
|
] }),
|
|
40
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-card border border-border bg-card p-5 lg:col-span-2", children: /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children:
|
|
94
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-card border border-border bg-card p-5 lg:col-span-2", children: /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: distribution.map((d) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
|
|
41
95
|
/* @__PURE__ */ jsxs("span", { className: "w-6 text-muted-foreground", children: [
|
|
42
96
|
d.stars,
|
|
43
97
|
"\u2605"
|
|
44
98
|
] }),
|
|
45
|
-
/* @__PURE__ */ jsx("div", { className: "h-2 flex-1 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
|
|
99
|
+
/* @__PURE__ */ jsx("div", { className: "h-2 flex-1 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
|
|
100
|
+
"div",
|
|
101
|
+
{
|
|
102
|
+
className: "h-full rounded-full bg-amber-400",
|
|
103
|
+
style: { width: `${total === 0 ? 0 : d.count / total * 100}%` }
|
|
104
|
+
}
|
|
105
|
+
) }),
|
|
46
106
|
/* @__PURE__ */ jsx("span", { className: "w-8 text-right text-muted-foreground", children: d.count })
|
|
47
107
|
] }, d.stars)) }) })
|
|
48
108
|
] }),
|
|
49
|
-
/* @__PURE__ */ jsx("div", { className: "mt-6
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
] })
|
|
60
|
-
] })
|
|
61
|
-
] }),
|
|
62
|
-
/* @__PURE__ */ jsx(Stars, { rating: r.rating })
|
|
63
|
-
] }),
|
|
64
|
-
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: r.text }),
|
|
65
|
-
/* @__PURE__ */ jsx("div", { className: "mt-2", children: r.replied ? /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-emerald-600", children: "\u2713 Replied" }) : /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", children: [
|
|
66
|
-
/* @__PURE__ */ jsx(MessageSquare, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
67
|
-
" Reply"
|
|
68
|
-
] }) })
|
|
69
|
-
] }, r.id)) }),
|
|
70
|
-
/* @__PURE__ */ jsx("p", { className: "mt-4 text-center text-xs text-muted-foreground", children: "Mock preview \xB7 Google/Facebook review sync + automated requests ship in a later milestone." })
|
|
109
|
+
/* @__PURE__ */ jsx("div", { className: "mt-6 flex flex-wrap gap-1", children: STATUS_TABS.map((t) => /* @__PURE__ */ jsx(Button, { size: "sm", variant: tab === t.id ? "default" : "ghost", onClick: () => setTab(t.id), children: t.label }, t.id)) }),
|
|
110
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
|
|
111
|
+
DataTable,
|
|
112
|
+
{
|
|
113
|
+
columns,
|
|
114
|
+
data: reviews,
|
|
115
|
+
loading,
|
|
116
|
+
emptyMessage: canModerate ? "Nenhuma avalia\xE7\xE3o aqui ainda." : "Este app est\xE1 lendo avalia\xE7\xF5es p\xFAblicas e n\xE3o pode moder\xE1-las."
|
|
117
|
+
}
|
|
118
|
+
) })
|
|
71
119
|
] });
|
|
72
120
|
}
|
|
73
121
|
|
|
122
|
+
// src/migrations/index.ts
|
|
123
|
+
var MIGRATION_001_PRODUCT_REVIEWS = `-- ============================================================================
|
|
124
|
+
-- plugin-reputation 001: product reviews + public (anon) read surface
|
|
125
|
+
--
|
|
126
|
+
-- The plugin shipped with a mock-only reputation home. This is its first real
|
|
127
|
+
-- table, and it carries BOTH shapes of review the plugin talks about:
|
|
128
|
+
-- a business review \u2014 product_id IS NULL (the Google/Facebook feed)
|
|
129
|
+
-- a product review \u2014 product_id IS NOT NULL (the storefront PDP)
|
|
130
|
+
-- One table, because they are the same row to a merchant: an author, a rating,
|
|
131
|
+
-- some words, and a reply. Prefix: plg_reputation_.
|
|
132
|
+
--
|
|
133
|
+
-- \xA71 plg_reputation_reviews \u2014 the reviews themselves
|
|
134
|
+
-- \xA72 RLS: authenticated tenant CRUD (the backoffice)
|
|
135
|
+
-- \xA73 v_public_product_reviews \u2014 anon-readable, APPROVED only, whitelisted
|
|
136
|
+
-- \xA74 v_public_product_ratings \u2014 anon-readable per-product aggregate
|
|
137
|
+
--
|
|
138
|
+
-- Anon-access design mirrors plugin-blog's: the storefront runs with the
|
|
139
|
+
-- publishable key and no session, so canonical tenant RLS yields nothing. Anon
|
|
140
|
+
-- read is granted ONLY through the column-whitelisted, approved-only views; the
|
|
141
|
+
-- front provider filters them by tenant_id. Idempotent + safe to re-run.
|
|
142
|
+
--
|
|
143
|
+
-- product_id deliberately has NO foreign key. plugin-shop owns plg_shop_products
|
|
144
|
+
-- and may not be installed in a given pool \u2014 a FK here would make this migration
|
|
145
|
+
-- fail on every non-commerce tenant. product_name/product_slug are a display
|
|
146
|
+
-- snapshot taken at write time so the homepage card can render and link without
|
|
147
|
+
-- this plugin ever reading another plugin's table.
|
|
148
|
+
-- ============================================================================
|
|
149
|
+
|
|
150
|
+
-- \xA71 \u2014 reviews
|
|
151
|
+
CREATE TABLE IF NOT EXISTS public.plg_reputation_reviews (
|
|
152
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
153
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
154
|
+
product_id uuid,
|
|
155
|
+
product_name text,
|
|
156
|
+
product_slug text,
|
|
157
|
+
order_id uuid,
|
|
158
|
+
author_name text NOT NULL,
|
|
159
|
+
author_avatar_url text,
|
|
160
|
+
rating numeric(2,1) NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
|
161
|
+
title text,
|
|
162
|
+
body text NOT NULL DEFAULT '',
|
|
163
|
+
source text NOT NULL DEFAULT 'Website',
|
|
164
|
+
status text NOT NULL DEFAULT 'pending'
|
|
165
|
+
CHECK (status IN ('pending', 'approved', 'rejected')),
|
|
166
|
+
verified boolean NOT NULL DEFAULT false,
|
|
167
|
+
reply_text text,
|
|
168
|
+
replied_at timestamptz,
|
|
169
|
+
published_at timestamptz,
|
|
170
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
171
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
172
|
+
);
|
|
173
|
+
ALTER TABLE public.plg_reputation_reviews ENABLE ROW LEVEL SECURITY;
|
|
174
|
+
CREATE INDEX IF NOT EXISTS idx_plg_reputation_reviews_tenant ON public.plg_reputation_reviews(tenant_id);
|
|
175
|
+
CREATE INDEX IF NOT EXISTS idx_plg_reputation_reviews_product ON public.plg_reputation_reviews(tenant_id, product_id, status);
|
|
176
|
+
CREATE INDEX IF NOT EXISTS idx_plg_reputation_reviews_recent ON public.plg_reputation_reviews(tenant_id, status, created_at DESC);
|
|
177
|
+
CREATE INDEX IF NOT EXISTS idx_plg_reputation_reviews_order ON public.plg_reputation_reviews(order_id);
|
|
178
|
+
|
|
179
|
+
-- \xA72 \u2014 RLS: authenticated tenant CRUD (the backoffice writes here)
|
|
180
|
+
DROP POLICY IF EXISTS plg_reputation_reviews_select ON public.plg_reputation_reviews;
|
|
181
|
+
DROP POLICY IF EXISTS plg_reputation_reviews_insert ON public.plg_reputation_reviews;
|
|
182
|
+
DROP POLICY IF EXISTS plg_reputation_reviews_update ON public.plg_reputation_reviews;
|
|
183
|
+
DROP POLICY IF EXISTS plg_reputation_reviews_delete ON public.plg_reputation_reviews;
|
|
184
|
+
CREATE POLICY plg_reputation_reviews_select ON public.plg_reputation_reviews FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
185
|
+
CREATE POLICY plg_reputation_reviews_insert ON public.plg_reputation_reviews FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
186
|
+
CREATE POLICY plg_reputation_reviews_update ON public.plg_reputation_reviews FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
187
|
+
CREATE POLICY plg_reputation_reviews_delete ON public.plg_reputation_reviews FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
188
|
+
|
|
189
|
+
-- \xA73 \u2014 anon-readable review feed (owner-rights view = deliberate, whitelisted,
|
|
190
|
+
-- APPROVED only). author_avatar_url and the reply are safe to show; e-mail and
|
|
191
|
+
-- order_id are not, so they are simply absent from the projection.
|
|
192
|
+
CREATE OR REPLACE VIEW public.v_public_product_reviews AS
|
|
193
|
+
SELECT
|
|
194
|
+
r.id,
|
|
195
|
+
r.tenant_id,
|
|
196
|
+
r.product_id,
|
|
197
|
+
r.product_name,
|
|
198
|
+
r.product_slug,
|
|
199
|
+
r.author_name,
|
|
200
|
+
r.author_avatar_url,
|
|
201
|
+
r.rating,
|
|
202
|
+
r.title,
|
|
203
|
+
r.body,
|
|
204
|
+
r.source,
|
|
205
|
+
r.verified,
|
|
206
|
+
r.reply_text,
|
|
207
|
+
r.replied_at,
|
|
208
|
+
COALESCE(r.published_at, r.created_at) AS published_at
|
|
209
|
+
FROM public.plg_reputation_reviews r
|
|
210
|
+
WHERE r.status = 'approved';
|
|
211
|
+
|
|
212
|
+
GRANT SELECT ON public.v_public_product_reviews TO anon, authenticated, service_role;
|
|
213
|
+
|
|
214
|
+
-- \xA74 \u2014 per-product aggregate. Exists so a PDP costs ONE row instead of every
|
|
215
|
+
-- review, and so a catalogue page can fetch ratings for a whole grid with a
|
|
216
|
+
-- single \`product_id=in.(\u2026)\`. Business reviews (product_id IS NULL) are excluded
|
|
217
|
+
-- on purpose: they are not about any product.
|
|
218
|
+
CREATE OR REPLACE VIEW public.v_public_product_ratings AS
|
|
219
|
+
SELECT
|
|
220
|
+
r.tenant_id,
|
|
221
|
+
r.product_id,
|
|
222
|
+
ROUND(AVG(r.rating), 1)::numeric(2,1) AS average,
|
|
223
|
+
COUNT(*)::int AS count,
|
|
224
|
+
COUNT(*) FILTER (WHERE ROUND(r.rating) = 5)::int AS star5,
|
|
225
|
+
COUNT(*) FILTER (WHERE ROUND(r.rating) = 4)::int AS star4,
|
|
226
|
+
COUNT(*) FILTER (WHERE ROUND(r.rating) = 3)::int AS star3,
|
|
227
|
+
COUNT(*) FILTER (WHERE ROUND(r.rating) = 2)::int AS star2,
|
|
228
|
+
COUNT(*) FILTER (WHERE ROUND(r.rating) = 1)::int AS star1
|
|
229
|
+
FROM public.plg_reputation_reviews r
|
|
230
|
+
WHERE r.status = 'approved' AND r.product_id IS NOT NULL
|
|
231
|
+
GROUP BY r.tenant_id, r.product_id;
|
|
232
|
+
|
|
233
|
+
GRANT SELECT ON public.v_public_product_ratings TO anon, authenticated, service_role;
|
|
234
|
+
`;
|
|
235
|
+
var MIGRATIONS = [
|
|
236
|
+
{ id: "001_product_reviews", sql: MIGRATION_001_PRODUCT_REVIEWS }
|
|
237
|
+
];
|
|
238
|
+
|
|
74
239
|
// src/index.ts
|
|
240
|
+
var MIGRATION_META = {
|
|
241
|
+
"001_product_reviews": {
|
|
242
|
+
version: "1.0.0",
|
|
243
|
+
description: "Reviews table (business + product), RLS, and the anon-readable review/rating views"
|
|
244
|
+
}
|
|
245
|
+
};
|
|
75
246
|
function createReputationPlugin(options) {
|
|
76
|
-
const
|
|
247
|
+
const provider = options?.dataProvider ?? createSafeDataProvider(
|
|
248
|
+
() => createSupabaseReputationProvider({ tenantId: options?.tenantId }),
|
|
249
|
+
() => createMockReputationProvider({ seed: options?.seed })
|
|
250
|
+
);
|
|
251
|
+
const PageComponent = () => React.createElement(ReputationProvider, {
|
|
252
|
+
value: { provider },
|
|
253
|
+
children: React.createElement(ReputationHome)
|
|
254
|
+
});
|
|
77
255
|
PageComponent.displayName = "ReputationHome";
|
|
78
256
|
return {
|
|
79
257
|
id: "reputation",
|
|
@@ -105,7 +283,19 @@ function createReputationPlugin(options) {
|
|
|
105
283
|
widgets: [],
|
|
106
284
|
// How the review request reaches the customer — the two the Fayz plugin
|
|
107
285
|
// page lists for Reputação.
|
|
108
|
-
integrations: ["whatsapp", "twilio"]
|
|
286
|
+
integrations: ["whatsapp", "twilio"],
|
|
287
|
+
// Owns the reviews table and the two anon-read views. The backoffice here
|
|
288
|
+
// writes them; the storefront surface in './public' only reads. Declared on
|
|
289
|
+
// this manifest and not on the website one so a host that installs both
|
|
290
|
+
// does not register the same migration twice.
|
|
291
|
+
migrations: [
|
|
292
|
+
...MIGRATIONS.map((m) => ({
|
|
293
|
+
id: `reputation-${m.id}`,
|
|
294
|
+
version: MIGRATION_META[m.id]?.version ?? "1.0.0",
|
|
295
|
+
sql: m.sql,
|
|
296
|
+
description: MIGRATION_META[m.id]?.description ?? m.id
|
|
297
|
+
}))
|
|
298
|
+
]
|
|
109
299
|
};
|
|
110
300
|
}
|
|
111
301
|
|