@hed-hog/core 0.0.400 → 0.0.402

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.
Files changed (274) hide show
  1. package/dist/access-log/access-log.interceptor.d.ts +10 -0
  2. package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
  3. package/dist/access-log/access-log.interceptor.js +15 -1
  4. package/dist/access-log/access-log.interceptor.js.map +1 -1
  5. package/dist/ai/ai.controller.d.ts +2 -0
  6. package/dist/ai/ai.controller.d.ts.map +1 -1
  7. package/dist/ai/ai.service.d.ts +86 -2
  8. package/dist/ai/ai.service.d.ts.map +1 -1
  9. package/dist/ai/ai.service.js +304 -23
  10. package/dist/ai/ai.service.js.map +1 -1
  11. package/dist/ai-instruction/ai-prompt-resolver.service.d.ts +12 -0
  12. package/dist/ai-instruction/ai-prompt-resolver.service.d.ts.map +1 -1
  13. package/dist/ai-instruction/ai-prompt-resolver.service.js +26 -0
  14. package/dist/ai-instruction/ai-prompt-resolver.service.js.map +1 -1
  15. package/dist/core.module.d.ts.map +1 -1
  16. package/dist/core.module.js +3 -0
  17. package/dist/core.module.js.map +1 -1
  18. package/dist/document-kit/document-kit.module.d.ts +3 -0
  19. package/dist/document-kit/document-kit.module.d.ts.map +1 -0
  20. package/dist/document-kit/document-kit.module.js +24 -0
  21. package/dist/document-kit/document-kit.module.js.map +1 -0
  22. package/dist/document-kit/document-kit.registry.d.ts +55 -0
  23. package/dist/document-kit/document-kit.registry.d.ts.map +1 -0
  24. package/dist/document-kit/document-kit.registry.js +91 -0
  25. package/dist/document-kit/document-kit.registry.js.map +1 -0
  26. package/dist/document-kit/document-kit.service.d.ts +49 -0
  27. package/dist/document-kit/document-kit.service.d.ts.map +1 -0
  28. package/dist/document-kit/document-kit.service.js +135 -0
  29. package/dist/document-kit/document-kit.service.js.map +1 -0
  30. package/dist/file/file-upload-ticket.service.d.ts +1 -1
  31. package/dist/file/file.controller.d.ts +3 -3
  32. package/dist/file/file.service.d.ts +136 -13
  33. package/dist/file/file.service.d.ts.map +1 -1
  34. package/dist/file/file.service.js +297 -27
  35. package/dist/file/file.service.js.map +1 -1
  36. package/dist/file/mcp-file-source.schema.d.ts.map +1 -1
  37. package/dist/file/mcp-file-source.schema.js +8 -5
  38. package/dist/file/mcp-file-source.schema.js.map +1 -1
  39. package/dist/file/provider/abstract.provider.d.ts +33 -0
  40. package/dist/file/provider/abstract.provider.d.ts.map +1 -1
  41. package/dist/file/provider/abstract.provider.js +9 -0
  42. package/dist/file/provider/abstract.provider.js.map +1 -1
  43. package/dist/file/provider/s3-multipart.util.d.ts +17 -0
  44. package/dist/file/provider/s3-multipart.util.d.ts.map +1 -0
  45. package/dist/file/provider/s3-multipart.util.js +88 -0
  46. package/dist/file/provider/s3-multipart.util.js.map +1 -0
  47. package/dist/file/provider/s3.provider.d.ts +9 -1
  48. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  49. package/dist/file/provider/s3.provider.js +16 -0
  50. package/dist/file/provider/s3.provider.js.map +1 -1
  51. package/dist/file/provider/spaces.provider.d.ts +10 -1
  52. package/dist/file/provider/spaces.provider.d.ts.map +1 -1
  53. package/dist/file/provider/spaces.provider.js +17 -0
  54. package/dist/file/provider/spaces.provider.js.map +1 -1
  55. package/dist/index.d.ts +5 -0
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +5 -0
  58. package/dist/index.js.map +1 -1
  59. package/dist/integration-profile/integration-profile.controller.d.ts +1 -0
  60. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
  61. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
  62. package/dist/integration-profile/integration-profile.secrets.js +26 -0
  63. package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
  64. package/dist/integration-profile/integration-profile.service.d.ts +1 -0
  65. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
  66. package/dist/integration-profile/integration-profile.service.js +3 -3
  67. package/dist/integration-profile/integration-profile.service.js.map +1 -1
  68. package/dist/integration-profile/testers/ai.testers.d.ts.map +1 -1
  69. package/dist/integration-profile/testers/ai.testers.js +46 -8
  70. package/dist/integration-profile/testers/ai.testers.js.map +1 -1
  71. package/dist/integration-profile/testers/http-probe.d.ts.map +1 -1
  72. package/dist/integration-profile/testers/http-probe.js +5 -3
  73. package/dist/integration-profile/testers/http-probe.js.map +1 -1
  74. package/dist/integration-profile/testers/integration-tester.types.d.ts +5 -0
  75. package/dist/integration-profile/testers/integration-tester.types.d.ts.map +1 -1
  76. package/dist/mail/mail.service.d.ts.map +1 -1
  77. package/dist/mail/mail.service.js +16 -1
  78. package/dist/mail/mail.service.js.map +1 -1
  79. package/dist/mcp/mcp-server.service.d.ts.map +1 -1
  80. package/dist/mcp/mcp-server.service.js +10 -1
  81. package/dist/mcp/mcp-server.service.js.map +1 -1
  82. package/dist/mcp/mcp.module.d.ts.map +1 -1
  83. package/dist/mcp/mcp.module.js +2 -0
  84. package/dist/mcp/mcp.module.js.map +1 -1
  85. package/dist/mcp/tools/core-access-log.mcp-tools.d.ts +17 -0
  86. package/dist/mcp/tools/core-access-log.mcp-tools.d.ts.map +1 -0
  87. package/dist/mcp/tools/core-access-log.mcp-tools.js +74 -0
  88. package/dist/mcp/tools/core-access-log.mcp-tools.js.map +1 -0
  89. package/dist/mcp/tools/core-files.mcp-tools.js +4 -4
  90. package/dist/mcp/tools/core-files.mcp-tools.js.map +1 -1
  91. package/dist/meeting/meeting-provider.registry.d.ts +72 -0
  92. package/dist/meeting/meeting-provider.registry.d.ts.map +1 -1
  93. package/dist/meeting/meeting-provider.registry.js.map +1 -1
  94. package/dist/oauth/oauth-token-broker.service.d.ts +50 -1
  95. package/dist/oauth/oauth-token-broker.service.d.ts.map +1 -1
  96. package/dist/oauth/oauth-token-broker.service.js +105 -3
  97. package/dist/oauth/oauth-token-broker.service.js.map +1 -1
  98. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +6 -0
  99. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
  100. package/dist/oauth/providers/microsoft-entra-id.provider.js +30 -4
  101. package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
  102. package/dist/pdf/document/charts/chart-bar.d.ts +24 -0
  103. package/dist/pdf/document/charts/chart-bar.d.ts.map +1 -0
  104. package/dist/pdf/document/charts/chart-bar.js +117 -0
  105. package/dist/pdf/document/charts/chart-bar.js.map +1 -0
  106. package/dist/pdf/document/charts/chart-donut.d.ts +16 -0
  107. package/dist/pdf/document/charts/chart-donut.d.ts.map +1 -0
  108. package/dist/pdf/document/charts/chart-donut.js +89 -0
  109. package/dist/pdf/document/charts/chart-donut.js.map +1 -0
  110. package/dist/pdf/document/charts/chart-gauge.d.ts +15 -0
  111. package/dist/pdf/document/charts/chart-gauge.d.ts.map +1 -0
  112. package/dist/pdf/document/charts/chart-gauge.js +60 -0
  113. package/dist/pdf/document/charts/chart-gauge.js.map +1 -0
  114. package/dist/pdf/document/charts/chart-line.d.ts +29 -0
  115. package/dist/pdf/document/charts/chart-line.d.ts.map +1 -0
  116. package/dist/pdf/document/charts/chart-line.js +89 -0
  117. package/dist/pdf/document/charts/chart-line.js.map +1 -0
  118. package/dist/pdf/document/charts/chart-primitives.d.ts +64 -0
  119. package/dist/pdf/document/charts/chart-primitives.d.ts.map +1 -0
  120. package/dist/pdf/document/charts/chart-primitives.js +110 -0
  121. package/dist/pdf/document/charts/chart-primitives.js.map +1 -0
  122. package/dist/pdf/document/charts/chart-radar.d.ts +13 -0
  123. package/dist/pdf/document/charts/chart-radar.d.ts.map +1 -0
  124. package/dist/pdf/document/charts/chart-radar.js +83 -0
  125. package/dist/pdf/document/charts/chart-radar.js.map +1 -0
  126. package/dist/pdf/document/charts/chart-scale.d.ts +39 -0
  127. package/dist/pdf/document/charts/chart-scale.d.ts.map +1 -0
  128. package/dist/pdf/document/charts/chart-scale.js +118 -0
  129. package/dist/pdf/document/charts/chart-scale.js.map +1 -0
  130. package/dist/pdf/document/charts/index.d.ts +8 -0
  131. package/dist/pdf/document/charts/index.d.ts.map +1 -0
  132. package/dist/pdf/document/charts/index.js +24 -0
  133. package/dist/pdf/document/charts/index.js.map +1 -0
  134. package/dist/pdf/document/index.d.ts +8 -0
  135. package/dist/pdf/document/index.d.ts.map +1 -0
  136. package/dist/pdf/document/index.js +24 -0
  137. package/dist/pdf/document/index.js.map +1 -0
  138. package/dist/pdf/document/pdf-document.chrome.d.ts +20 -0
  139. package/dist/pdf/document/pdf-document.chrome.d.ts.map +1 -0
  140. package/dist/pdf/document/pdf-document.chrome.js +36 -0
  141. package/dist/pdf/document/pdf-document.chrome.js.map +1 -0
  142. package/dist/pdf/document/pdf-document.css.d.ts +20 -0
  143. package/dist/pdf/document/pdf-document.css.d.ts.map +1 -0
  144. package/dist/pdf/document/pdf-document.css.js +327 -0
  145. package/dist/pdf/document/pdf-document.css.js.map +1 -0
  146. package/dist/pdf/document/pdf-document.renderer.d.ts +10 -0
  147. package/dist/pdf/document/pdf-document.renderer.d.ts.map +1 -0
  148. package/dist/pdf/document/pdf-document.renderer.js +226 -0
  149. package/dist/pdf/document/pdf-document.renderer.js.map +1 -0
  150. package/dist/pdf/document/pdf-document.service.d.ts +27 -0
  151. package/dist/pdf/document/pdf-document.service.d.ts.map +1 -0
  152. package/dist/pdf/document/pdf-document.service.js +81 -0
  153. package/dist/pdf/document/pdf-document.service.js.map +1 -0
  154. package/dist/pdf/document/pdf-document.theme.d.ts +41 -0
  155. package/dist/pdf/document/pdf-document.theme.d.ts.map +1 -0
  156. package/dist/pdf/document/pdf-document.theme.js +105 -0
  157. package/dist/pdf/document/pdf-document.theme.js.map +1 -0
  158. package/dist/pdf/document/pdf-document.types.d.ts +164 -0
  159. package/dist/pdf/document/pdf-document.types.d.ts.map +1 -0
  160. package/dist/pdf/document/pdf-document.types.js +18 -0
  161. package/dist/pdf/document/pdf-document.types.js.map +1 -0
  162. package/dist/pdf/pdf.module.d.ts.map +1 -1
  163. package/dist/pdf/pdf.module.js +3 -2
  164. package/dist/pdf/pdf.module.js.map +1 -1
  165. package/dist/pdf/pdf.service.d.ts +75 -1
  166. package/dist/pdf/pdf.service.d.ts.map +1 -1
  167. package/dist/pdf/pdf.service.js +167 -63
  168. package/dist/pdf/pdf.service.js.map +1 -1
  169. package/dist/pdf/templates/commercial-proposal.template.d.ts +96 -0
  170. package/dist/pdf/templates/commercial-proposal.template.d.ts.map +1 -0
  171. package/dist/pdf/templates/commercial-proposal.template.js +276 -0
  172. package/dist/pdf/templates/commercial-proposal.template.js.map +1 -0
  173. package/dist/setting/favicon.controller.d.ts +26 -0
  174. package/dist/setting/favicon.controller.d.ts.map +1 -0
  175. package/dist/setting/favicon.controller.js +130 -0
  176. package/dist/setting/favicon.controller.js.map +1 -0
  177. package/dist/setting/setting.module.d.ts.map +1 -1
  178. package/dist/setting/setting.module.js +2 -1
  179. package/dist/setting/setting.module.js.map +1 -1
  180. package/dist/setting/setting.service.d.ts.map +1 -1
  181. package/dist/setting/setting.service.js +3 -0
  182. package/dist/setting/setting.service.js.map +1 -1
  183. package/dist/task/task.service.d.ts +6 -0
  184. package/dist/task/task.service.d.ts.map +1 -1
  185. package/dist/task/task.service.js +44 -13
  186. package/dist/task/task.service.js.map +1 -1
  187. package/dist/whatsapp-sender/whatsapp-sender.service.d.ts +35 -2
  188. package/dist/whatsapp-sender/whatsapp-sender.service.d.ts.map +1 -1
  189. package/dist/whatsapp-sender/whatsapp-sender.service.js +107 -22
  190. package/dist/whatsapp-sender/whatsapp-sender.service.js.map +1 -1
  191. package/hedhog/data/integration_type.yaml +6 -0
  192. package/hedhog/data/route.yaml +8 -0
  193. package/hedhog/data/setting_group.yaml +52 -0
  194. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.test.tsx.ejs +85 -2
  195. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +576 -491
  196. package/hedhog/frontend/app/users/simulate-access-dialog.test.tsx.ejs +29 -50
  197. package/hedhog/frontend/app/users/simulate-access-dialog.tsx.ejs +146 -50
  198. package/hedhog/frontend/messages/en.json +81 -2
  199. package/hedhog/frontend/messages/pt.json +81 -2
  200. package/hedhog/table/file.yaml +3 -1
  201. package/package.json +5 -5
  202. package/src/access-log/access-log.interceptor.spec.ts +50 -0
  203. package/src/access-log/access-log.interceptor.ts +18 -0
  204. package/src/ai/ai-attachment.fragments.spec.ts +56 -3
  205. package/src/ai/ai.service.responses.spec.ts +429 -0
  206. package/src/ai/ai.service.ts +409 -27
  207. package/src/ai-instruction/ai-prompt-resolver.service.spec.ts +44 -0
  208. package/src/ai-instruction/ai-prompt-resolver.service.ts +29 -0
  209. package/src/core.module.ts +3 -0
  210. package/src/document-kit/document-kit.module.ts +12 -0
  211. package/src/document-kit/document-kit.registry.ts +125 -0
  212. package/src/document-kit/document-kit.service.spec.ts +156 -0
  213. package/src/document-kit/document-kit.service.ts +173 -0
  214. package/src/file/file-direct-upload.spec.ts +344 -0
  215. package/src/file/file.service.spec.ts +168 -1
  216. package/src/file/file.service.ts +435 -36
  217. package/src/file/mcp-file-source.schema.ts +8 -5
  218. package/src/file/provider/abstract.provider.ts +57 -0
  219. package/src/file/provider/s3-multipart.spec.ts +123 -0
  220. package/src/file/provider/s3-multipart.util.ts +126 -0
  221. package/src/file/provider/s3.provider.ts +65 -1
  222. package/src/file/provider/spaces.provider.ts +67 -1
  223. package/src/index.ts +5 -0
  224. package/src/integration-profile/integration-profile.secrets.ts +26 -0
  225. package/src/integration-profile/integration-profile.service.ts +1 -0
  226. package/src/integration-profile/testers/ai.testers.ts +70 -5
  227. package/src/integration-profile/testers/http-probe.ts +5 -3
  228. package/src/integration-profile/testers/integration-tester.types.ts +5 -0
  229. package/src/integration-profile/testers/oauth.testers.spec.ts +13 -0
  230. package/src/integration-profile/testers/remaining.testers.spec.ts +65 -1
  231. package/src/integration-profile/testers/tester-coverage.spec.ts +12 -0
  232. package/src/mail/mail.service.spec.ts +25 -0
  233. package/src/mail/mail.service.ts +18 -1
  234. package/src/mcp/mcp-server.service.spec.ts +16 -0
  235. package/src/mcp/mcp-server.service.ts +10 -1
  236. package/src/mcp/mcp.module.ts +2 -0
  237. package/src/mcp/tools/core-access-log.mcp-tools.ts +66 -0
  238. package/src/mcp/tools/core-files.mcp-tools.ts +4 -4
  239. package/src/meeting/meeting-provider.registry.ts +80 -0
  240. package/src/oauth/oauth-token-broker.service.spec.ts +108 -0
  241. package/src/oauth/oauth-token-broker.service.ts +129 -2
  242. package/src/oauth/providers/microsoft-entra-id.provider.spec.ts +133 -0
  243. package/src/oauth/providers/microsoft-entra-id.provider.ts +29 -2
  244. package/src/pdf/document/charts/chart-bar.ts +217 -0
  245. package/src/pdf/document/charts/chart-donut.ts +163 -0
  246. package/src/pdf/document/charts/chart-gauge.ts +94 -0
  247. package/src/pdf/document/charts/chart-line.ts +159 -0
  248. package/src/pdf/document/charts/chart-primitives.ts +188 -0
  249. package/src/pdf/document/charts/chart-radar.ts +136 -0
  250. package/src/pdf/document/charts/chart-scale.spec.ts +93 -0
  251. package/src/pdf/document/charts/chart-scale.ts +141 -0
  252. package/src/pdf/document/charts/charts.spec.ts +157 -0
  253. package/src/pdf/document/charts/index.ts +7 -0
  254. package/src/pdf/document/index.ts +7 -0
  255. package/src/pdf/document/pdf-document.chrome.ts +38 -0
  256. package/src/pdf/document/pdf-document.css.ts +326 -0
  257. package/src/pdf/document/pdf-document.renderer.spec.ts +172 -0
  258. package/src/pdf/document/pdf-document.renderer.ts +317 -0
  259. package/src/pdf/document/pdf-document.service.ts +77 -0
  260. package/src/pdf/document/pdf-document.theme.ts +146 -0
  261. package/src/pdf/document/pdf-document.types.ts +149 -0
  262. package/src/pdf/pdf.module.ts +3 -2
  263. package/src/pdf/pdf.service.spec.ts +131 -0
  264. package/src/pdf/pdf.service.ts +256 -82
  265. package/src/pdf/templates/commercial-proposal.template.spec.ts +177 -0
  266. package/src/pdf/templates/commercial-proposal.template.ts +400 -0
  267. package/src/setting/favicon.controller.spec.ts +92 -0
  268. package/src/setting/favicon.controller.ts +135 -0
  269. package/src/setting/setting.module.ts +2 -1
  270. package/src/setting/setting.service.ts +3 -0
  271. package/src/task/task.service.spec.ts +77 -1
  272. package/src/task/task.service.ts +58 -14
  273. package/src/whatsapp-sender/whatsapp-sender.service.spec.ts +263 -0
  274. package/src/whatsapp-sender/whatsapp-sender.service.ts +154 -16
@@ -24,48 +24,22 @@ vi.mock('next-intl', () => ({
24
24
  useTranslations: () => (key: string) => key,
25
25
  }));
26
26
 
27
- // O Select real é Radix e precisa de APIs de ponteiro que o jsdom não tem. O
28
- // stub vira um <select> nativo, mesmo padrão de commerce-form-sheet.test.tsx.
29
- vi.mock('@/components/ui/select', () => ({
30
- Select: ({
31
- children,
32
- value,
33
- onValueChange,
34
- }: {
35
- children: ReactNode;
36
- value?: string;
37
- onValueChange?: (value: string) => void;
38
- }) => (
39
- <select
40
- aria-label="select-app"
41
- value={value}
42
- onChange={(event) => onValueChange?.(event.target.value)}
43
- >
44
- {children}
45
- </select>
46
- ),
47
- SelectContent: ({ children }: { children: ReactNode }) => <>{children}</>,
48
- SelectItem: ({ children, value }: { children: ReactNode; value: string }) => (
49
- <option value={value}>{children}</option>
50
- ),
51
- SelectTrigger: ({ children }: { children: ReactNode }) => <>{children}</>,
52
- SelectValue: ({ placeholder }: { placeholder?: string }) => (
53
- <option value="" disabled>
54
- {placeholder ?? 'Selecione'}
55
- </option>
56
- ),
57
- }));
58
-
59
- // O Dialog real também é Radix (portal + focus trap); aqui só interessa o
27
+ // O Sheet real é Radix (portal + focus trap) e o ResizableSheetContent depende
28
+ // de matchMedia/ResizeObserver, que o jsdom não tem; aqui só interessa o
60
29
  // conteúdo renderizado.
61
- vi.mock('@/components/ui/dialog', () => ({
62
- Dialog: ({ children, open }: { children: ReactNode; open?: boolean }) =>
30
+ vi.mock('@/components/ui/sheet', () => ({
31
+ Sheet: ({ children, open }: { children: ReactNode; open?: boolean }) =>
63
32
  open ? <div>{children}</div> : null,
64
- DialogContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
65
- DialogDescription: ({ children }: { children: ReactNode }) => <p>{children}</p>,
66
- DialogFooter: ({ children }: { children: ReactNode }) => <div>{children}</div>,
67
- DialogHeader: ({ children }: { children: ReactNode }) => <div>{children}</div>,
68
- DialogTitle: ({ children }: { children: ReactNode }) => <h2>{children}</h2>,
33
+ SheetDescription: ({ children }: { children: ReactNode }) => <p>{children}</p>,
34
+ SheetFooter: ({ children }: { children: ReactNode }) => <div>{children}</div>,
35
+ SheetHeader: ({ children }: { children: ReactNode }) => <div>{children}</div>,
36
+ SheetTitle: ({ children }: { children: ReactNode }) => <h2>{children}</h2>,
37
+ }));
38
+
39
+ vi.mock('@/components/ui/resizable-sheet-content', () => ({
40
+ ResizableSheetContent: ({ children }: { children: ReactNode }) => (
41
+ <div>{children}</div>
42
+ ),
69
43
  }));
70
44
 
71
45
  import { SimulateAccessDialog } from './simulate-access-dialog';
@@ -87,10 +61,9 @@ const renderDialog = () => {
87
61
  };
88
62
 
89
63
  const chooseApp = async (value: string) => {
90
- // Espera a lista de apps chegar: mudar o valor antes de a <option> existir é
91
- // um no-op silencioso e o teste passaria a testar nada.
92
- await screen.findByText(value);
93
- fireEvent.change(screen.getByLabelText('select-app'), { target: { value } });
64
+ // Espera a lista de apps chegar: clicar antes de o card existir falharia, e o
65
+ // teste passaria a testar nada.
66
+ fireEvent.click(await screen.findByRole('radio', { name: new RegExp(`apps.${value}`) }));
94
67
  };
95
68
 
96
69
  describe('SimulateAccessDialog', () => {
@@ -114,10 +87,9 @@ describe('SimulateAccessDialog', () => {
114
87
  it('lista apenas os apps que o backend devolve', async () => {
115
88
  renderDialog();
116
89
 
117
- expect(await screen.findByText('admin')).toBeInTheDocument();
118
- expect(screen.getByText('class')).toBeInTheDocument();
119
- // `training` tem autenticação própria e ficou fora desta fase.
120
- expect(screen.queryByText('training')).not.toBeInTheDocument();
90
+ expect(await screen.findByText('apps.admin')).toBeInTheDocument();
91
+ expect(screen.getByText('apps.class')).toBeInTheDocument();
92
+ expect(screen.queryByText('apps.training')).not.toBeInTheDocument();
121
93
  });
122
94
 
123
95
  it('exige escolher o app antes de confirmar', async () => {
@@ -130,7 +102,13 @@ describe('SimulateAccessDialog', () => {
130
102
 
131
103
  it('abre a aba no gesto do clique, antes do await, e só depois aponta a URL', async () => {
132
104
  // Abrir a aba depois do await cai no bloqueador de pop-up de todo navegador.
133
- const tab = { location: { href: '' }, close: vi.fn() };
105
+ // Sem `noopener`: com ele o navegador devolve `null` e a URL iria para a
106
+ // aba do admin, deixando a nova em branco.
107
+ const tab = {
108
+ location: { href: '' },
109
+ opener: window as Window | null,
110
+ close: vi.fn(),
111
+ };
134
112
  const openSpy = vi
135
113
  .spyOn(window, 'open')
136
114
  .mockReturnValue(tab as unknown as Window);
@@ -139,12 +117,13 @@ describe('SimulateAccessDialog', () => {
139
117
  await chooseApp('class');
140
118
  fireEvent.click(screen.getByRole('button', { name: 'confirm' }));
141
119
 
142
- expect(openSpy).toHaveBeenCalledWith('', '_blank', 'noopener');
120
+ expect(openSpy).toHaveBeenCalledWith('', '_blank');
143
121
  await waitFor(() =>
144
122
  expect(tab.location.href).toBe(
145
123
  'https://class.example.com/simulacao#code=abc'
146
124
  )
147
125
  );
126
+ expect(tab.opener).toBeNull();
148
127
  });
149
128
 
150
129
  it('envia o alvo, o app e o motivo para o backend', async () => {
@@ -1,32 +1,78 @@
1
1
  'use client';
2
2
 
3
3
  import { Button } from '@/components/ui/button';
4
- import {
5
- Dialog,
6
- DialogContent,
7
- DialogDescription,
8
- DialogFooter,
9
- DialogHeader,
10
- DialogTitle,
11
- } from '@/components/ui/dialog';
12
4
  import { Label } from '@/components/ui/label';
5
+ import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
13
6
  import {
14
- Select,
15
- SelectContent,
16
- SelectItem,
17
- SelectTrigger,
18
- SelectValue,
19
- } from '@/components/ui/select';
7
+ Sheet,
8
+ SheetDescription,
9
+ SheetFooter,
10
+ SheetHeader,
11
+ SheetTitle,
12
+ } from '@/components/ui/sheet';
13
+ import { Skeleton } from '@/components/ui/skeleton';
20
14
  import { Textarea } from '@/components/ui/textarea';
15
+ import { cn } from '@/lib/utils';
21
16
  import { User } from '@hed-hog/api-types';
22
17
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
23
- import { VenetianMask } from 'lucide-react';
18
+ import {
19
+ AppWindow,
20
+ CheckCircle2,
21
+ GraduationCap,
22
+ Handshake,
23
+ LayoutDashboard,
24
+ LucideIcon,
25
+ Presentation,
26
+ VenetianMask,
27
+ } from 'lucide-react';
24
28
  import { useTranslations } from 'next-intl';
25
29
  import { useEffect, useState } from 'react';
26
30
  import { toast } from 'sonner';
27
31
 
28
32
  type AppOption = { key: string; url: string };
29
33
 
34
+ // O ícone é o do próprio app, servido pela rota de ícone do Next de cada um
35
+ // (`app/icon.*`). O Lucide só aparece se a imagem não carregar.
36
+ const APP_META: Record<string, { iconPath: string; fallback: LucideIcon }> = {
37
+ admin: { iconPath: '/icon', fallback: LayoutDashboard },
38
+ class: { iconPath: '/icon.png', fallback: GraduationCap },
39
+ partners: { iconPath: '/icon.svg', fallback: Handshake },
40
+ training: { iconPath: '/icon.png', fallback: Presentation },
41
+ };
42
+
43
+ function resolveUrl(path: string, base: string) {
44
+ try {
45
+ return new URL(path, base);
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ function AppIcon({ option }: { option: AppOption }) {
52
+ const meta = APP_META[option.key];
53
+ const Fallback = meta?.fallback ?? AppWindow;
54
+ const src = meta ? resolveUrl(meta.iconPath, option.url)?.href : undefined;
55
+ const [failed, setFailed] = useState(false);
56
+
57
+ if (!src || failed) {
58
+ return (
59
+ <span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-muted">
60
+ <Fallback className="h-5 w-5 text-muted-foreground" />
61
+ </span>
62
+ );
63
+ }
64
+
65
+ return (
66
+ // eslint-disable-next-line @next/next/no-img-element
67
+ <img
68
+ src={src}
69
+ alt=""
70
+ className="h-10 w-10 shrink-0 rounded-lg object-contain"
71
+ onError={() => setFailed(true)}
72
+ />
73
+ );
74
+ }
75
+
30
76
  export function SimulateAccessDialog({
31
77
  open,
32
78
  onOpenChange,
@@ -59,6 +105,9 @@ export function SimulateAccessDialog({
59
105
 
60
106
  const options = apps?.apps ?? [];
61
107
 
108
+ const appLabel = (key: string) =>
109
+ APP_META[key] ? t(`apps.${key}` as 'apps.admin') : key;
110
+
62
111
  useEffect(() => {
63
112
  if (!open) {
64
113
  setApp('');
@@ -71,8 +120,10 @@ export function SimulateAccessDialog({
71
120
  if (!user || !app) return;
72
121
 
73
122
  // A aba precisa ser aberta AGORA, no gesto do clique. Abrir depois do await
74
- // cai no bloqueador de pop-up de todo navegador.
75
- const tab = window.open('', '_blank', 'noopener');
123
+ // cai no bloqueador de pop-up de todo navegador. Sem `noopener`: com ele o
124
+ // `window.open` devolve `null` mesmo abrindo a aba, e a URL acabaria na aba
125
+ // do admin. O vínculo com o admin é cortado à mão antes de navegar.
126
+ const tab = window.open('', '_blank');
76
127
 
77
128
  setSubmitting(true);
78
129
  try {
@@ -84,6 +135,7 @@ export function SimulateAccessDialog({
84
135
  });
85
136
 
86
137
  if (tab) {
138
+ tab.opener = null;
87
139
  tab.location.href = data.url;
88
140
  } else {
89
141
  // Pop-up bloqueado apesar do gesto: o código vale 60s, então mandar o
@@ -92,14 +144,15 @@ export function SimulateAccessDialog({
92
144
  }
93
145
 
94
146
  onOpenChange(false);
95
- } catch (error: any) {
147
+ } catch (error) {
96
148
  tab?.close();
97
149
  // A mensagem do backend é específica (alvo administrador, conta
98
150
  // desativada, app sem URL...) e vale mais que um texto genérico.
151
+ const backendMessage = (
152
+ error as { response?: { data?: { message?: unknown } } }
153
+ )?.response?.data?.message;
99
154
  const message =
100
- typeof error?.response?.data?.message === 'string'
101
- ? error.response.data.message
102
- : t('startFailure');
155
+ typeof backendMessage === 'string' ? backendMessage : t('startFailure');
103
156
  toast.error(message);
104
157
  } finally {
105
158
  setSubmitting(false);
@@ -107,35 +160,73 @@ export function SimulateAccessDialog({
107
160
  };
108
161
 
109
162
  return (
110
- <Dialog open={open} onOpenChange={onOpenChange}>
111
- <DialogContent>
112
- <DialogHeader>
113
- <DialogTitle className="flex items-center gap-2">
114
- <VenetianMask className="h-4 w-4 text-amber-600" />
163
+ <Sheet open={open} onOpenChange={onOpenChange}>
164
+ <ResizableSheetContent
165
+ sheetId="core-users-simulate-access-sheet"
166
+ defaultWidth={520}
167
+ minWidth={420}
168
+ maxWidth={800}
169
+ className="w-full sm:max-w-xl gap-0"
170
+ >
171
+ <SheetHeader className="px-6 pt-6 pb-4">
172
+ <SheetTitle className="flex items-center gap-2">
173
+ <VenetianMask className="h-5 w-5 text-amber-600" />
115
174
  {t('dialogTitle')}
116
- </DialogTitle>
117
- <DialogDescription>
175
+ </SheetTitle>
176
+ <SheetDescription>
118
177
  {t('dialogDescription', { name: user?.name ?? '' })}
119
- </DialogDescription>
120
- </DialogHeader>
178
+ </SheetDescription>
179
+ </SheetHeader>
180
+
181
+ <div className="w-full border-t" />
121
182
 
122
- <div className="space-y-4">
183
+ <div className="flex-1 space-y-6 overflow-y-auto px-6 py-5">
123
184
  <div className="space-y-2">
124
- <Label htmlFor="impersonation-app">{t('targetApp')}</Label>
125
- <Select value={app} onValueChange={setApp} disabled={isLoadingApps}>
126
- <SelectTrigger id="impersonation-app" className="w-full">
127
- <SelectValue placeholder={t('targetAppPlaceholder')} />
128
- </SelectTrigger>
129
- <SelectContent>
130
- {options.map((option) => (
131
- <SelectItem key={option.key} value={option.key}>
132
- {option.key}
133
- </SelectItem>
185
+ <Label id="impersonation-app-label">{t('targetApp')}</Label>
186
+ {isLoadingApps ? (
187
+ <div className="space-y-2">
188
+ {[0, 1, 2].map((index) => (
189
+ <Skeleton key={index} className="h-[62px] w-full rounded-lg" />
134
190
  ))}
135
- </SelectContent>
136
- </Select>
137
- {!isLoadingApps && options.length === 0 && (
191
+ </div>
192
+ ) : options.length === 0 ? (
138
193
  <p className="text-xs text-destructive">{t('noAppsConfigured')}</p>
194
+ ) : (
195
+ <div
196
+ role="radiogroup"
197
+ aria-labelledby="impersonation-app-label"
198
+ className="space-y-2"
199
+ >
200
+ {options.map((option) => {
201
+ const selected = option.key === app;
202
+ return (
203
+ <button
204
+ key={option.key}
205
+ type="button"
206
+ role="radio"
207
+ aria-checked={selected}
208
+ onClick={() => setApp(option.key)}
209
+ className={cn(
210
+ 'flex w-full cursor-pointer items-center gap-3 rounded-lg border bg-card px-3 py-2.5 text-left transition-colors hover:border-primary/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
211
+ selected && 'border-primary bg-primary/5 hover:border-primary'
212
+ )}
213
+ >
214
+ <AppIcon option={option} />
215
+ <div className="min-w-0 flex-1">
216
+ <p className="truncate text-sm font-medium">
217
+ {appLabel(option.key)}
218
+ </p>
219
+ <p className="truncate text-xs text-muted-foreground">
220
+ {resolveUrl('/', option.url)?.host ?? option.url}
221
+ </p>
222
+ </div>
223
+ {selected && (
224
+ <CheckCircle2 className="h-5 w-5 shrink-0 text-primary" />
225
+ )}
226
+ </button>
227
+ );
228
+ })}
229
+ </div>
139
230
  )}
140
231
  </div>
141
232
 
@@ -156,15 +247,20 @@ export function SimulateAccessDialog({
156
247
  </div>
157
248
  </div>
158
249
 
159
- <DialogFooter>
250
+ <SheetFooter className="border-t px-6">
160
251
  <Button variant="outline" onClick={() => onOpenChange(false)}>
161
252
  {t('cancel')}
162
253
  </Button>
163
- <Button onClick={handleConfirm} disabled={!app || submitting}>
164
- {submitting ? t('opening') : t('confirm')}
254
+ <Button
255
+ onClick={handleConfirm}
256
+ disabled={!app}
257
+ loading={submitting}
258
+ loadingLabel={t('opening')}
259
+ >
260
+ {t('confirm')}
165
261
  </Button>
166
- </DialogFooter>
167
- </DialogContent>
168
- </Dialog>
262
+ </SheetFooter>
263
+ </ResizableSheetContent>
264
+ </Sheet>
169
265
  );
170
266
  }
@@ -2626,6 +2626,11 @@
2626
2626
  "testConnectionSuccess": "Connection validated successfully.",
2627
2627
  "testConnectionOkWithInfo": "Connection validated: {info}",
2628
2628
  "testConnectionWarning": "Credentials are valid, but: {warning}",
2629
+ "testConnectionWarningCodes": {
2630
+ "google_oauth_testing": "The OAuth consent screen is in Testing: Google refresh tokens expire after 7 days, so channels must be reconnected every week. Publish the app (In production) to keep them connected.",
2631
+ "youtube_audit_pending": "Until the YouTube API audit is approved for this Google Cloud project, uploaded videos stay private.",
2632
+ "google_business_access_pending": "The Business Profile APIs require approved access (Basic API Access): until Google approves this project the quota is 0 and connections fail."
2633
+ },
2629
2634
  "testConnectionError": "Connection test failed.",
2630
2635
  "testConnectionErrorFallback": "Could not validate the credentials.",
2631
2636
  "testConnectionNotPersisted": "Save the profile to record the result.",
@@ -2666,9 +2671,14 @@
2666
2671
  "fieldBusinessId": "Business ID",
2667
2672
  "fieldAppId": "Meta App ID",
2668
2673
  "fieldAppSecret": "Meta App Secret",
2674
+ "fieldInstagramAppId": "Instagram App ID (Instagram > API setup with Instagram login)",
2675
+ "fieldInstagramAppSecret": "Instagram App Secret (Instagram > API setup with Instagram login)",
2669
2676
  "fieldWabaId": "WhatsApp Business Account ID (WABA)",
2670
2677
  "fieldVerifyToken": "Webhook Verify Token",
2671
2678
  "fieldGraphApiVersion": "Graph API Version",
2679
+ "fieldLinkedInApiVersion": "LinkedIn API version (YYYYMM)",
2680
+ "fieldXClientId": "Client ID (OAuth 2.0)",
2681
+ "fieldXClientSecret": "Client Secret (OAuth 2.0)",
2672
2682
  "fieldEnvironmentTest": "Test",
2673
2683
  "fieldEnvironmentProduction": "Production",
2674
2684
  "copiedToClipboard": "Copied to clipboard",
@@ -2832,7 +2842,70 @@
2832
2842
  "fileStored": "A file is stored and encrypted. Click to replace it.",
2833
2843
  "fileSelected": "New file selected. It will be encrypted on save.",
2834
2844
  "fieldRtdnServiceAccount": "Notifications Service Account (RTDN)",
2835
- "fieldRtdnAudience": "Notifications Audience (RTDN)"
2845
+ "fieldRtdnAudience": "Notifications Audience (RTDN)",
2846
+ "fieldLoginConfigId": "Login Configuration ID (Facebook Login for Business)",
2847
+ "metaSocialSectionTitle": "Social network connection",
2848
+ "metaSocialDescription": "This profile is the HedHog app on Meta. Pages and Instagram accounts are connected per brand, under Social → Brands → Channels.",
2849
+ "metaSocialRedirectUriLabel": "OAuth redirect URI",
2850
+ "metaSocialRedirectUriUnavailable": "Fill in the API URL setting (api-url) to show the URI",
2851
+ "metaSocialStepsTitle": "How to configure it in the Meta dashboard",
2852
+ "metaSocialStep1": "Create a Business app and add Facebook Login for Business and the Instagram API with Facebook Login.",
2853
+ "metaSocialStep2": "Create a Login Configuration of type \"User access token\" with the permissions pages_show_list, business_management, pages_read_engagement, pages_manage_posts, instagram_basic and instagram_content_publish, and paste its ID in the Login Configuration ID field.",
2854
+ "metaSocialStep3": "Add the URI above to \"Valid OAuth Redirect URIs\".",
2855
+ "metaSocialStep4": "In App settings > Basic, fill in the privacy policy, the terms of service and the data deletion instructions URL.",
2856
+ "instagramSocialSectionTitle": "Instagram connection",
2857
+ "instagramSocialDescription": "This profile is the HedHog app on the Instagram API with Instagram Login. Professional Instagram accounts (Business or Creator) are connected per brand, under Social → Brands → Channels, with or without a Facebook Page.",
2858
+ "instagramSocialRedirectUriLabel": "OAuth redirect URI",
2859
+ "instagramSocialRedirectUriUnavailable": "Fill in the API URL setting (api-url) to show the URI",
2860
+ "instagramSocialStepsTitle": "How to configure it in the Meta dashboard",
2861
+ "instagramSocialStep1": "In the Meta app, add the Instagram product and open \"API setup with Instagram login\".",
2862
+ "instagramSocialStep2": "Copy the Instagram app ID and Instagram app secret shown there and paste them above. They are not the Meta app ID and secret.",
2863
+ "instagramSocialStep3": "Under \"Set up Instagram business login\" > Business login settings, add the URI above to \"OAuth redirect URIs\" exactly as shown, with no trailing slash.",
2864
+ "instagramSocialStep4": "In the same settings, fill in the Deauthorize callback URL and the Data deletion request URL with the page of your privacy instructions.",
2865
+ "instagramSocialStep5": "Until App Review is approved, add the accounts you will connect as Instagram testers of the app and keep the Production mode: Sandbox is a local simulator and is refused in production.",
2866
+ "linkedinSocialSectionTitle": "LinkedIn connection",
2867
+ "linkedinSocialDescription": "This profile is the HedHog app on the LinkedIn Community Management API. Company pages are connected per brand, under Social → Brands → Channels, by a member who is an administrator or content administrator of the page.",
2868
+ "linkedinSocialRedirectUriLabel": "Authorized redirect URL",
2869
+ "linkedinSocialRedirectUriUnavailable": "Fill in the API URL setting (api-url) to show the URL",
2870
+ "linkedinSocialStepsTitle": "How to configure it in the LinkedIn Developer Portal",
2871
+ "linkedinSocialStep1": "Create a dedicated app, with no other products, linked to the company page and verified by a super admin of that LinkedIn Page.",
2872
+ "linkedinSocialStep2": "In the Products tab, request the Community Management API. Access starts in the Development tier.",
2873
+ "linkedinSocialStep3": "In the Auth tab, add the URL above to \"Authorized redirect URLs for your app\" exactly as shown, then copy the Client ID and Primary Client Secret and paste them above.",
2874
+ "linkedinSocialStep4": "Before going to production, request the Standard tier, which requires a screencast of each use case of the app.",
2875
+ "linkedinSocialStep5": "Use the Production mode: it already calls LinkedIn in the Development tier, within its limits. Sandbox is a local simulator and is refused in production.",
2876
+ "xSocialSectionTitle": "X connection",
2877
+ "xSocialSectionDescription": "This profile is the HedHog app on the pay-per-use X API. X accounts are connected per brand, in Social → Brands → Channels, by someone who signs in to X with the brand account.",
2878
+ "xSocialRedirectUriLabel": "Callback URI",
2879
+ "xSocialRedirectUriUnavailable": "Fill in the API URL setting (api-url) to show the URL",
2880
+ "xSocialStepsTitle": "How to set it up in the X Developer Console",
2881
+ "xSocialStep1": "In the Developer Console, create a Project and an App. Under User authentication settings, turn on OAuth 2.0 with the Web App (confidential client) type and the Read and write permissions.",
2882
+ "xSocialStep2": "Register the URL above as the Callback URI, exactly as shown, in every environment.",
2883
+ "xSocialStep3": "Copy the OAuth 2.0 Client ID and Client Secret into the fields above (not the API Key and API Secret).",
2884
+ "xSocialStep4": "Under Billing, buy credits and set a spending limit: the API is pay-per-use (US$ 0.015 per post, US$ 0.20 with a link).",
2885
+ "xSocialStep5": "Use the Production mode to connect real accounts. Sandbox is a local simulator and is refused in production.",
2886
+ "fieldOauthAppStatus": "OAuth consent screen status",
2887
+ "fieldOauthAppStatusTesting": "Testing (the refresh token expires in 7 days and the channel must be reconnected)",
2888
+ "fieldOauthAppStatusProduction": "In production (the refresh token does not expire in 7 days)",
2889
+ "googleSocialSectionTitle": "Google connection",
2890
+ "youtubeSocialDescription": "This profile is the HedHog project on Google Cloud for YouTube. Channels are connected per brand, under Social → Brands → Channels.",
2891
+ "googleBusinessSocialDescription": "This profile is the HedHog project on Google Cloud for Google Business Profile. Locations are connected per brand, under Social → Brands → Channels.",
2892
+ "googleSocialRedirectUriLabel": "OAuth authorized redirect URI",
2893
+ "googleSocialRedirectUriUnavailable": "Fill in the API URL setting (api-url) to show the URI",
2894
+ "googleSocialStepsTitle": "How to configure it in the Google Cloud Console",
2895
+ "youtubeSocialStep1": "Create a Google Cloud project just for YouTube and enable the YouTube Data API v3.",
2896
+ "youtubeSocialStep2": "Configure the OAuth consent screen with the youtube.upload, youtube.readonly and youtube.force-ssl scopes (sensitive, they require Google verification before the app is published) and yt-analytics.readonly (non-sensitive).",
2897
+ "youtubeSocialStep3": "Create an OAuth client ID of type \"Web application\", add the URI above to \"Authorized redirect URIs\" and paste the Client ID and Client Secret above.",
2898
+ "youtubeSocialStep4": "Request the YouTube API compliance audit: until it is approved, every video uploaded by the project stays private.",
2899
+ "youtubeSocialStep5": "The default project quota allows about 100 video uploads per day. When it runs out, publishing is rescheduled for after the reset (midnight Pacific Time).",
2900
+ "googleBusinessSocialStep1": "The Business Profile must have been verified for more than 60 days and have a website.",
2901
+ "googleBusinessSocialStep2": "Create a Google Cloud project just for Business Profile and request Basic API Access, providing that project number.",
2902
+ "googleBusinessSocialStep3": "Enable the My Business Account Management API, the My Business Business Information API and the Google My Business API. Until access is approved the quota is 0 and every call fails.",
2903
+ "googleBusinessSocialStep4": "Configure the OAuth consent screen with the business.manage scope.",
2904
+ "googleBusinessSocialStep5": "Create an OAuth client ID of type \"Web application\", add the URI above to \"Authorized redirect URIs\" and paste the Client ID and Client Secret above.",
2905
+ "googleSocialTestingStep": "While the consent screen is in \"Testing\", choose \"Testing\" in the status above and add the Google accounts that will be connected as test users.",
2906
+ "metaSocialStep5": "Complete Business Verification and request App Review for the permissions. Until it is approved, only the app administrators, developers and testers can connect.",
2907
+ "socialRedirectUriMismatch": "The API URL setting ({setting}) differs from the API this panel uses ({env}). The URI above follows the setting, which is what the backend sends to the network.",
2908
+ "socialGuideLink": "Open the full setup guide"
2836
2909
  },
2837
2910
  "SystemPagesPage": {
2838
2911
  "title": "Pages",
@@ -3248,7 +3321,13 @@
3248
3321
  "redeemInvalidDescription": "This link works only once and expires after a minute. Ask the operator to start a new simulation.",
3249
3322
  "redeemMissingCode": "No simulation code found in the link.",
3250
3323
  "noAdminAccessTitle": "This user has no access to the admin panel",
3251
- "noAdminAccessDescription": "{name} has no administrative role, so there is nothing to show here. Simulate them in the application they actually use."
3324
+ "noAdminAccessDescription": "{name} has no administrative role, so there is nothing to show here. Simulate them in the application they actually use.",
3325
+ "apps": {
3326
+ "admin": "Hub (administration)",
3327
+ "class": "Hcode Class",
3328
+ "partners": "Partner Portal",
3329
+ "training": "Hcode Training"
3330
+ }
3252
3331
  },
3253
3332
  "MessageTemplatesPage": {
3254
3333
  "title": "Message templates",
@@ -2681,6 +2681,11 @@
2681
2681
  "testConnectionSuccess": "Conexão validada com sucesso.",
2682
2682
  "testConnectionOkWithInfo": "Conexão validada: {info}",
2683
2683
  "testConnectionWarning": "Credenciais válidas, mas: {warning}",
2684
+ "testConnectionWarningCodes": {
2685
+ "google_oauth_testing": "A tela de consentimento OAuth está em Teste: os refresh tokens do Google expiram em 7 dias e os canais precisam ser reconectados toda semana. Publique o app (Em produção) para mantê-los conectados.",
2686
+ "youtube_audit_pending": "Enquanto a auditoria da API do YouTube não for aprovada para este projeto do Google Cloud, os vídeos enviados ficam privados.",
2687
+ "google_business_access_pending": "As APIs do Perfil da Empresa exigem acesso aprovado (Basic API Access): até o Google aprovar este projeto, a cota é 0 e as conexões falham."
2688
+ },
2684
2689
  "testConnectionError": "Falha ao testar a conexão.",
2685
2690
  "testConnectionErrorFallback": "Não foi possível validar as credenciais.",
2686
2691
  "testConnectionNotPersisted": "Salve o perfil para registrar o resultado.",
@@ -2721,9 +2726,14 @@
2721
2726
  "fieldBusinessId": "Business ID",
2722
2727
  "fieldAppId": "Meta App ID",
2723
2728
  "fieldAppSecret": "Meta App Secret",
2729
+ "fieldInstagramAppId": "ID do app do Instagram (Instagram > API setup with Instagram login)",
2730
+ "fieldInstagramAppSecret": "Chave secreta do app do Instagram (Instagram > API setup with Instagram login)",
2724
2731
  "fieldWabaId": "ID da Conta do WhatsApp Business (WABA)",
2725
2732
  "fieldVerifyToken": "Verify Token do Webhook",
2726
2733
  "fieldGraphApiVersion": "Versão da Graph API",
2734
+ "fieldLinkedInApiVersion": "Versão da API do LinkedIn (AAAAMM)",
2735
+ "fieldXClientId": "Client ID (OAuth 2.0)",
2736
+ "fieldXClientSecret": "Client Secret (OAuth 2.0)",
2727
2737
  "fieldEnvironmentTest": "Teste",
2728
2738
  "fieldEnvironmentProduction": "Produção",
2729
2739
  "copiedToClipboard": "Copiado para a área de transferência",
@@ -2887,7 +2897,70 @@
2887
2897
  "fileStored": "Arquivo armazenado e cifrado. Clique para substituir.",
2888
2898
  "fileSelected": "Novo arquivo selecionado. Será cifrado ao salvar.",
2889
2899
  "fieldRtdnServiceAccount": "Conta de Serviço das Notificações (RTDN)",
2890
- "fieldRtdnAudience": "Audience das Notificações (RTDN)"
2900
+ "fieldRtdnAudience": "Audience das Notificações (RTDN)",
2901
+ "fieldLoginConfigId": "Login Configuration ID (Facebook Login for Business)",
2902
+ "metaSocialSectionTitle": "Conexão das redes sociais",
2903
+ "metaSocialDescription": "Este perfil é o app do HedHog na Meta. As Páginas e contas Instagram são conectadas por marca, em Social → Marcas → Canais.",
2904
+ "metaSocialRedirectUriLabel": "URI de redirecionamento do OAuth",
2905
+ "metaSocialRedirectUriUnavailable": "Preencha a configuração URL da API (api-url) para exibir a URI",
2906
+ "metaSocialStepsTitle": "Como configurar no painel da Meta",
2907
+ "metaSocialStep1": "Crie um app do tipo Business e adicione o Facebook Login for Business e a API do Instagram com login do Facebook.",
2908
+ "metaSocialStep2": "Crie uma Login Configuration do tipo \"User access token\" com as permissões pages_show_list, business_management, pages_read_engagement, pages_manage_posts, instagram_basic e instagram_content_publish, e cole o ID dela no campo Login Configuration ID.",
2909
+ "metaSocialStep3": "Cadastre a URI acima em \"Valid OAuth Redirect URIs\".",
2910
+ "metaSocialStep4": "Em Configurações do app > Básico, preencha a política de privacidade, os termos de uso e a URL das instruções de exclusão de dados.",
2911
+ "instagramSocialSectionTitle": "Conexão com o Instagram",
2912
+ "instagramSocialDescription": "Este perfil é o app do HedHog na API do Instagram com login do Instagram. As contas profissionais do Instagram (Empresa ou Criador de conteúdo) são conectadas por marca, em Social → Marcas → Canais, com ou sem Página do Facebook.",
2913
+ "instagramSocialRedirectUriLabel": "URI de redirecionamento do OAuth",
2914
+ "instagramSocialRedirectUriUnavailable": "Preencha a configuração URL da API (api-url) para exibir a URI",
2915
+ "instagramSocialStepsTitle": "Como configurar no painel da Meta",
2916
+ "instagramSocialStep1": "No app da Meta, adicione o produto Instagram e abra \"API setup with Instagram login\".",
2917
+ "instagramSocialStep2": "Copie o ID do app do Instagram e a chave secreta do app do Instagram exibidos ali e cole acima. Eles não são o ID e a chave do app da Meta.",
2918
+ "instagramSocialStep3": "Em \"Set up Instagram business login\" > Business login settings, cadastre a URI acima em \"OAuth redirect URIs\" exatamente como aparece, sem barra no final.",
2919
+ "instagramSocialStep4": "Nas mesmas configurações, preencha a Deauthorize callback URL e a Data deletion request URL com a página das suas instruções de privacidade.",
2920
+ "instagramSocialStep5": "Até o App Review ser aprovado, adicione as contas que serão conectadas como testadoras do Instagram no app e mantenha o modo Production: Sandbox é um simulador local e é recusado em produção.",
2921
+ "linkedinSocialSectionTitle": "Conexão com o LinkedIn",
2922
+ "linkedinSocialDescription": "Este perfil é o app do HedHog na Community Management API do LinkedIn. As páginas de empresa são conectadas por marca, em Social → Marcas → Canais, por um membro que seja administrador ou administrador de conteúdo da página.",
2923
+ "linkedinSocialRedirectUriLabel": "URL de redirecionamento autorizada",
2924
+ "linkedinSocialRedirectUriUnavailable": "Preencha a configuração URL da API (api-url) para exibir a URL",
2925
+ "linkedinSocialStepsTitle": "Como configurar no LinkedIn Developer Portal",
2926
+ "linkedinSocialStep1": "Crie um app dedicado, sem outros produtos, vinculado à página da empresa e verificado por um superadministrador dessa Página do LinkedIn.",
2927
+ "linkedinSocialStep2": "Na aba Products, solicite a Community Management API. O acesso começa no nível Development.",
2928
+ "linkedinSocialStep3": "Na aba Auth, cadastre a URL acima em \"Authorized redirect URLs for your app\" exatamente como aparece e copie o Client ID e o Primary Client Secret para os campos acima.",
2929
+ "linkedinSocialStep4": "Antes de ir para produção, solicite o nível Standard, que exige um screencast de cada caso de uso do app.",
2930
+ "linkedinSocialStep5": "Use o modo Production: ele já chama o LinkedIn no nível Development, dentro dos limites dele. Sandbox é um simulador local e é recusado em produção.",
2931
+ "xSocialSectionTitle": "Conexão com o X",
2932
+ "xSocialSectionDescription": "Este perfil é o app do HedHog na API do X, paga por uso. As contas do X são conectadas por marca, em Social → Marcas → Canais, por quem entra no X com a conta da marca.",
2933
+ "xSocialRedirectUriLabel": "Callback URI",
2934
+ "xSocialRedirectUriUnavailable": "Preencha a configuração URL da API (api-url) para exibir a URL",
2935
+ "xSocialStepsTitle": "Como configurar no X Developer Console",
2936
+ "xSocialStep1": "No Developer Console, crie um Project e um App. Em User authentication settings, ative o OAuth 2.0 com o tipo Web App (confidential client) e as permissões Read and write.",
2937
+ "xSocialStep2": "Cadastre a URL acima como Callback URI, exatamente como aparece, em cada ambiente.",
2938
+ "xSocialStep3": "Copie o Client ID e o Client Secret do OAuth 2.0 para os campos acima (não são a API Key nem a API Secret).",
2939
+ "xSocialStep4": "Em Billing, compre créditos e defina um teto de gasto: a API é paga por uso (US$ 0,015 por post, US$ 0,20 com link).",
2940
+ "xSocialStep5": "Use o modo Production para conectar contas reais. Sandbox é um simulador local e é recusado em produção.",
2941
+ "fieldOauthAppStatus": "Status da tela de consentimento OAuth",
2942
+ "fieldOauthAppStatusTesting": "Em teste (o token de atualização expira em 7 dias e o canal precisa ser reconectado)",
2943
+ "fieldOauthAppStatusProduction": "Em produção (o token de atualização não expira em 7 dias)",
2944
+ "googleSocialSectionTitle": "Conexão com o Google",
2945
+ "youtubeSocialDescription": "Este perfil é o projeto do HedHog no Google Cloud para o YouTube. Os canais são conectados por marca, em Social → Marcas → Canais.",
2946
+ "googleBusinessSocialDescription": "Este perfil é o projeto do HedHog no Google Cloud para o Perfil da Empresa no Google. Os locais são conectados por marca, em Social → Marcas → Canais.",
2947
+ "googleSocialRedirectUriLabel": "URI de redirecionamento autorizado do OAuth",
2948
+ "googleSocialRedirectUriUnavailable": "Preencha a configuração URL da API (api-url) para exibir a URI",
2949
+ "googleSocialStepsTitle": "Como configurar no Google Cloud Console",
2950
+ "youtubeSocialStep1": "Crie um projeto no Google Cloud só para o YouTube e ative a YouTube Data API v3.",
2951
+ "youtubeSocialStep2": "Configure a tela de consentimento OAuth com os escopos youtube.upload, youtube.readonly e youtube.force-ssl (sensíveis, exigem a verificação do Google antes de o app ser publicado) e yt-analytics.readonly (não sensível).",
2952
+ "youtubeSocialStep3": "Crie um ID do cliente OAuth do tipo \"Aplicativo da Web\", cadastre a URI acima em \"URIs de redirecionamento autorizados\" e cole o Client ID e o Client Secret acima.",
2953
+ "youtubeSocialStep4": "Solicite a auditoria de conformidade da API do YouTube: até ela ser aprovada, todo vídeo enviado pelo projeto fica privado.",
2954
+ "youtubeSocialStep5": "A cota padrão do projeto permite cerca de 100 envios de vídeo por dia. Ao esgotar, a publicação é reagendada para depois do reset (meia-noite no horário do Pacífico).",
2955
+ "googleBusinessSocialStep1": "O Perfil da Empresa precisa estar verificado há mais de 60 dias e ter um site cadastrado.",
2956
+ "googleBusinessSocialStep2": "Crie um projeto no Google Cloud só para o Perfil da Empresa e solicite o acesso básico à API (Basic API Access) informando o número desse projeto.",
2957
+ "googleBusinessSocialStep3": "Ative a My Business Account Management API, a My Business Business Information API e a Google My Business API. Até o acesso ser aprovado a cota é 0 e toda chamada falha.",
2958
+ "googleBusinessSocialStep4": "Configure a tela de consentimento OAuth com o escopo business.manage.",
2959
+ "googleBusinessSocialStep5": "Crie um ID do cliente OAuth do tipo \"Aplicativo da Web\", cadastre a URI acima em \"URIs de redirecionamento autorizados\" e cole o Client ID e o Client Secret acima.",
2960
+ "googleSocialTestingStep": "Enquanto a tela de consentimento estiver em \"Teste\", escolha \"Em teste\" no status acima e adicione como usuários de teste as contas Google que serão conectadas.",
2961
+ "metaSocialStep5": "Conclua a Verificação da empresa e peça a App Review das permissões. Até a aprovação, só administradores, desenvolvedores e testadores do app conseguem conectar.",
2962
+ "socialRedirectUriMismatch": "A configuração URL da API ({setting}) é diferente da API que este painel usa ({env}). A URI acima segue a configuração, que é o que o backend envia à rede.",
2963
+ "socialGuideLink": "Ver o guia completo de configuração"
2891
2964
  },
2892
2965
  "SystemPagesPage": {
2893
2966
  "title": "Páginas",
@@ -3303,7 +3376,13 @@
3303
3376
  "redeemInvalidDescription": "O link funciona uma única vez e expira em um minuto. Peça ao operador para iniciar uma nova simulação.",
3304
3377
  "redeemMissingCode": "Nenhum código de simulação foi encontrado no link.",
3305
3378
  "noAdminAccessTitle": "Este usuário não tem acesso ao painel",
3306
- "noAdminAccessDescription": "{name} não tem papel administrativo, então não há o que mostrar aqui. Simule esse acesso na aplicação que a pessoa realmente usa."
3379
+ "noAdminAccessDescription": "{name} não tem papel administrativo, então não há o que mostrar aqui. Simule esse acesso na aplicação que a pessoa realmente usa.",
3380
+ "apps": {
3381
+ "admin": "Hub (administração)",
3382
+ "class": "Hcode Class",
3383
+ "partners": "Portal de Parceiros",
3384
+ "training": "Hcode Training"
3385
+ }
3307
3386
  },
3308
3387
  "MessageTemplatesPage": {
3309
3388
  "title": "Modelos de mensagem",
@@ -15,8 +15,10 @@ columns:
15
15
  table: file_mimetype
16
16
  column: id
17
17
  onDelete: CASCADE
18
+ # Em bytes. bigint: int parava em 2.147.483.647 (~2 GB) e video maior nao
19
+ # entrava. O PrismaService devolve o valor como number (extension de result).
18
20
  - name: size
19
- type: int
21
+ type: bigint
20
22
  unsigned: true
21
23
  default: 0
22
24
  # Tira o arquivo das duas rotas que servem por id sem autenticacao