reviews-plugin.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import { OnApplicationBootstrap } from '@nestjs/common';
  2. import {
  3. LanguageCode,
  4. PluginCommonModule,
  5. Product,
  6. ProductVariant,
  7. RequestContextService,
  8. TransactionalConnection,
  9. TranslatableSaver,
  10. VendurePlugin,
  11. } from '@vendure/core';
  12. import { AdminUiExtension } from '@vendure/ui-devkit/compiler';
  13. import path from 'path';
  14. import { adminApiExtensions, shopApiExtensions } from './api/api-extensions';
  15. import { ProductEntityResolver } from './api/product-entity.resolver';
  16. import { ProductReviewAdminResolver } from './api/product-review-admin.resolver';
  17. import { ProductReviewEntityResolver } from './api/product-review-entity.resolver';
  18. import { ProductReviewShopResolver } from './api/product-review-shop.resolver';
  19. import { ProductReviewTranslation } from './entities/product-review-translation.entity';
  20. import { ProductReview } from './entities/product-review.entity';
  21. @VendurePlugin({
  22. imports: [PluginCommonModule],
  23. entities: [ProductReview, ProductReviewTranslation],
  24. adminApiExtensions: {
  25. schema: adminApiExtensions,
  26. resolvers: [ProductEntityResolver, ProductReviewAdminResolver, ProductReviewEntityResolver],
  27. },
  28. shopApiExtensions: {
  29. schema: shopApiExtensions,
  30. resolvers: [ProductEntityResolver, ProductReviewShopResolver, ProductReviewEntityResolver],
  31. },
  32. configuration: config => {
  33. config.customFields.Product.push({
  34. name: 'reviewRating',
  35. label: [{ languageCode: LanguageCode.en, value: 'Review rating' }],
  36. public: true,
  37. nullable: true,
  38. type: 'float',
  39. ui: { tab: 'Reviews', component: 'star-rating-form-input' },
  40. });
  41. config.customFields.Product.push({
  42. name: 'reviewCount',
  43. label: [{ languageCode: LanguageCode.en, value: 'Review count' }],
  44. public: true,
  45. defaultValue: 0,
  46. type: 'float',
  47. ui: { tab: 'Reviews', component: 'review-count-link' },
  48. });
  49. config.customFields.Product.push({
  50. name: 'featuredReview',
  51. label: [{ languageCode: LanguageCode.en, value: 'Featured review' }],
  52. public: true,
  53. type: 'relation',
  54. entity: ProductReview,
  55. ui: { tab: 'Reviews', fullWidth: true },
  56. inverseSide: undefined,
  57. });
  58. config.customFields.Product.push({
  59. name: 'translatableText',
  60. label: [{ languageCode: LanguageCode.en, value: 'Translatable text' }],
  61. public: true,
  62. type: 'localeText',
  63. ui: { tab: 'Reviews' },
  64. });
  65. config.customFields.ProductReview = [
  66. {
  67. type: 'localeText',
  68. name: 'reviewerName',
  69. label: [{ languageCode: LanguageCode.en, value: 'Reviewer name' }],
  70. public: true,
  71. nullable: true,
  72. ui: { component: 'textarea' },
  73. },
  74. ];
  75. config.customFields.ProductReview.push({
  76. name: 'verifiedReviewerName',
  77. label: [{ languageCode: LanguageCode.en, value: 'Verified reviewer name' }],
  78. public: true,
  79. nullable: true,
  80. type: 'string',
  81. readonly: true,
  82. });
  83. return config;
  84. },
  85. dashboard: './dashboard/index.tsx',
  86. })
  87. export class ReviewsPlugin implements OnApplicationBootstrap {
  88. constructor(
  89. private readonly connection: TransactionalConnection,
  90. private readonly requestContextService: RequestContextService,
  91. private readonly translatableSaver: TranslatableSaver,
  92. ) {}
  93. static uiExtensions: AdminUiExtension = {
  94. extensionPath: path.join(__dirname, 'ui'),
  95. routes: [{ route: 'product-reviews', filePath: 'routes.ts' }],
  96. providers: ['providers.ts'],
  97. };
  98. async onApplicationBootstrap() {
  99. const ctx = await this.requestContextService.create({
  100. apiType: 'admin',
  101. languageCode: LanguageCode.en,
  102. });
  103. const reviewCount = await this.connection.getRepository(ctx, ProductReview).count();
  104. if (reviewCount === 0) {
  105. const products = await this.connection.getRepository(ctx, Product).find();
  106. if (products.length === 0) {
  107. return;
  108. }
  109. const demoReviews = [
  110. {
  111. summary: 'Great product, highly recommend!',
  112. body: 'I was really impressed with the quality and performance. Would definitely buy again.',
  113. rating: 5,
  114. authorName: 'John Smith',
  115. authorLocation: 'New York, USA',
  116. state: 'approved',
  117. translations: [
  118. {
  119. languageCode: LanguageCode.en,
  120. text: 'Great product, highly recommend!',
  121. customFields: {
  122. reviewerName: 'JSmith123',
  123. },
  124. },
  125. ],
  126. customFields: {
  127. verifiedReviewerName: 'JSmith123 verified',
  128. },
  129. },
  130. {
  131. summary: 'Good value for money',
  132. body: 'Does exactly what it says. No complaints.',
  133. rating: 4,
  134. authorName: 'Sarah Wilson',
  135. authorLocation: 'London, UK',
  136. state: 'approved',
  137. translations: [
  138. {
  139. languageCode: LanguageCode.en,
  140. text: 'Good value for money',
  141. customFields: {
  142. reviewerName: 'SarahW',
  143. },
  144. },
  145. ],
  146. customFields: {
  147. verifiedReviewerName: 'SarahW verified',
  148. },
  149. },
  150. {
  151. summary: 'Decent but could be better',
  152. body: 'The product is okay but there is room for improvement in terms of durability.',
  153. rating: 3,
  154. authorName: 'Mike Johnson',
  155. authorLocation: 'Toronto, Canada',
  156. state: 'approved',
  157. translations: [
  158. {
  159. languageCode: LanguageCode.en,
  160. text: 'Decent but could be better',
  161. customFields: {
  162. reviewerName: 'MikeJ',
  163. },
  164. },
  165. ],
  166. customFields: {
  167. verifiedReviewerName: 'MikeJ verified',
  168. },
  169. },
  170. {
  171. summary: 'Exceeded expectations',
  172. body: 'Really happy with this purchase. The quality is outstanding.',
  173. rating: 5,
  174. authorName: 'Emma Brown',
  175. authorLocation: 'Sydney, Australia',
  176. state: 'approved',
  177. translations: [
  178. {
  179. languageCode: LanguageCode.en,
  180. text: 'Exceeded expectations',
  181. customFields: {
  182. reviewerName: 'EmmaB',
  183. },
  184. },
  185. ],
  186. customFields: {
  187. verifiedReviewerName: 'EmmaB verified',
  188. },
  189. },
  190. {
  191. summary: 'Good product, fast delivery',
  192. body: 'Product arrived quickly and works as described. Happy with the purchase.',
  193. rating: 4,
  194. authorName: 'David Lee',
  195. authorLocation: 'Singapore',
  196. state: 'approved',
  197. translations: [
  198. {
  199. languageCode: LanguageCode.en,
  200. text: 'Good product, fast delivery',
  201. customFields: {
  202. reviewerName: 'DavidL',
  203. },
  204. },
  205. ],
  206. customFields: {
  207. verifiedReviewerName: 'DavidL verified',
  208. },
  209. },
  210. ];
  211. for (const review of demoReviews) {
  212. const randomProduct = products[Math.floor(Math.random() * products.length)];
  213. const productVariants = await this.connection.getRepository(ctx, ProductVariant).find({
  214. where: { productId: randomProduct.id },
  215. });
  216. const randomVariant = productVariants[Math.floor(Math.random() * productVariants.length)];
  217. const input = {
  218. ...review,
  219. product: randomProduct,
  220. productVariant: randomVariant,
  221. };
  222. const createdReview = await this.translatableSaver.create({
  223. ctx,
  224. input,
  225. entityType: ProductReview,
  226. translationType: ProductReviewTranslation,
  227. });
  228. }
  229. }
  230. }
  231. }