@hed-hog/core 0.0.295 → 0.0.296

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 (144) hide show
  1. package/dist/auth/auth.controller.d.ts +4 -4
  2. package/dist/auth/auth.service.d.ts +4 -4
  3. package/dist/challenge/challenge.service.d.ts +2 -2
  4. package/dist/core.module.d.ts.map +1 -1
  5. package/dist/core.module.js +4 -1
  6. package/dist/core.module.js.map +1 -1
  7. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +1 -1
  8. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +2 -2
  9. package/dist/index.d.ts +13 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +14 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/integration/index.d.ts +4 -0
  14. package/dist/integration/index.d.ts.map +1 -0
  15. package/dist/integration/index.js +20 -0
  16. package/dist/integration/index.js.map +1 -0
  17. package/dist/integration/integration-api.validation.d.ts +2 -0
  18. package/dist/integration/integration-api.validation.d.ts.map +1 -0
  19. package/dist/integration/integration-api.validation.js +126 -0
  20. package/dist/integration/integration-api.validation.js.map +1 -0
  21. package/dist/integration/integration.module.d.ts +3 -0
  22. package/dist/integration/integration.module.d.ts.map +1 -0
  23. package/dist/integration/integration.module.js +54 -0
  24. package/dist/integration/integration.module.js.map +1 -0
  25. package/dist/integration/services/domain-event.publisher.d.ts +31 -0
  26. package/dist/integration/services/domain-event.publisher.d.ts.map +1 -0
  27. package/dist/integration/services/domain-event.publisher.js +79 -0
  28. package/dist/integration/services/domain-event.publisher.js.map +1 -0
  29. package/dist/integration/services/event-subscriber.registry.d.ts +37 -0
  30. package/dist/integration/services/event-subscriber.registry.d.ts.map +1 -0
  31. package/dist/integration/services/event-subscriber.registry.js +86 -0
  32. package/dist/integration/services/event-subscriber.registry.js.map +1 -0
  33. package/dist/integration/services/inbox.service.d.ts +55 -0
  34. package/dist/integration/services/inbox.service.d.ts.map +1 -0
  35. package/dist/integration/services/inbox.service.js +173 -0
  36. package/dist/integration/services/inbox.service.js.map +1 -0
  37. package/dist/integration/services/index.d.ts +12 -0
  38. package/dist/integration/services/index.d.ts.map +1 -0
  39. package/dist/integration/services/index.js +28 -0
  40. package/dist/integration/services/index.js.map +1 -0
  41. package/dist/integration/services/integration-developer-api.service.d.ts +30 -0
  42. package/dist/integration/services/integration-developer-api.service.d.ts.map +1 -0
  43. package/dist/integration/services/integration-developer-api.service.js +55 -0
  44. package/dist/integration/services/integration-developer-api.service.js.map +1 -0
  45. package/dist/integration/services/integration-link.service.d.ts +52 -0
  46. package/dist/integration/services/integration-link.service.d.ts.map +1 -0
  47. package/dist/integration/services/integration-link.service.js +128 -0
  48. package/dist/integration/services/integration-link.service.js.map +1 -0
  49. package/dist/integration/services/integration-settings.service.d.ts +23 -0
  50. package/dist/integration/services/integration-settings.service.d.ts.map +1 -0
  51. package/dist/integration/services/integration-settings.service.js +81 -0
  52. package/dist/integration/services/integration-settings.service.js.map +1 -0
  53. package/dist/integration/services/outbox-polling.coordinator.d.ts +45 -0
  54. package/dist/integration/services/outbox-polling.coordinator.d.ts.map +1 -0
  55. package/dist/integration/services/outbox-polling.coordinator.js +143 -0
  56. package/dist/integration/services/outbox-polling.coordinator.js.map +1 -0
  57. package/dist/integration/services/outbox.notifier.d.ts +30 -0
  58. package/dist/integration/services/outbox.notifier.d.ts.map +1 -0
  59. package/dist/integration/services/outbox.notifier.js +57 -0
  60. package/dist/integration/services/outbox.notifier.js.map +1 -0
  61. package/dist/integration/services/outbox.processor.d.ts +42 -0
  62. package/dist/integration/services/outbox.processor.d.ts.map +1 -0
  63. package/dist/integration/services/outbox.processor.job.d.ts +43 -0
  64. package/dist/integration/services/outbox.processor.job.d.ts.map +1 -0
  65. package/dist/integration/services/outbox.processor.job.js +100 -0
  66. package/dist/integration/services/outbox.processor.job.js.map +1 -0
  67. package/dist/integration/services/outbox.processor.js +208 -0
  68. package/dist/integration/services/outbox.processor.js.map +1 -0
  69. package/dist/integration/services/outbox.service.d.ts +53 -0
  70. package/dist/integration/services/outbox.service.d.ts.map +1 -0
  71. package/dist/integration/services/outbox.service.js +149 -0
  72. package/dist/integration/services/outbox.service.js.map +1 -0
  73. package/dist/integration/types/event.types.d.ts +88 -0
  74. package/dist/integration/types/event.types.d.ts.map +1 -0
  75. package/dist/integration/types/event.types.js +35 -0
  76. package/dist/integration/types/event.types.js.map +1 -0
  77. package/dist/integration/types/index.d.ts +3 -0
  78. package/dist/integration/types/index.d.ts.map +1 -0
  79. package/dist/integration/types/index.js +19 -0
  80. package/dist/integration/types/index.js.map +1 -0
  81. package/dist/integration/types/subscriber.types.d.ts +31 -0
  82. package/dist/integration/types/subscriber.types.d.ts.map +1 -0
  83. package/dist/integration/types/subscriber.types.js +3 -0
  84. package/dist/integration/types/subscriber.types.js.map +1 -0
  85. package/dist/oauth/oauth.controller.js.map +1 -1
  86. package/dist/oauth/oauth.service.d.ts +3 -3
  87. package/dist/oauth/oauth.service.d.ts.map +1 -1
  88. package/dist/oauth/oauth.service.js.map +1 -1
  89. package/dist/profile/profile.controller.d.ts +3 -3
  90. package/dist/profile/profile.service.d.ts +3 -3
  91. package/dist/setting/setting.controller.d.ts +12 -8
  92. package/dist/setting/setting.controller.d.ts.map +1 -1
  93. package/dist/setting/setting.service.d.ts +12 -8
  94. package/dist/setting/setting.service.d.ts.map +1 -1
  95. package/dist/setting/setting.service.js +21 -1
  96. package/dist/setting/setting.service.js.map +1 -1
  97. package/dist/user/user.controller.d.ts +4 -4
  98. package/dist/user/user.service.d.ts +9 -9
  99. package/hedhog/data/dashboard_component_role.yaml +223 -223
  100. package/hedhog/data/dashboard_role.yaml +18 -18
  101. package/hedhog/data/route.yaml +2 -0
  102. package/hedhog/data/setting_group.yaml +955 -470
  103. package/hedhog/data/setting_subgroup.yaml +303 -0
  104. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +44 -18
  105. package/hedhog/frontend/app/configurations/[slug]/page.tsx.ejs +134 -27
  106. package/hedhog/frontend/app/configurations/layout.tsx.ejs +84 -23
  107. package/hedhog/frontend/app/dashboard/components/widgets/permissions-chart.tsx.ejs +62 -62
  108. package/hedhog/frontend/app/dashboard/page.tsx.ejs +29 -29
  109. package/hedhog/frontend/app/preferences/page.tsx.ejs +2 -5
  110. package/hedhog/table/inbox_event.yaml +40 -0
  111. package/hedhog/table/integration_link.yaml +33 -0
  112. package/hedhog/table/outbox_event.yaml +45 -0
  113. package/hedhog/table/setting.yaml +7 -0
  114. package/hedhog/table/setting_subgroup.yaml +19 -0
  115. package/package.json +8 -8
  116. package/src/ai/ai.service.ts +3 -3
  117. package/src/auth/auth.controller.ts +11 -11
  118. package/src/auth/auth.service.ts +8 -8
  119. package/src/core.module.ts +4 -1
  120. package/src/index.ts +15 -0
  121. package/src/integration/README.md +397 -0
  122. package/src/integration/USAGE_EXAMPLE.md +279 -0
  123. package/src/integration/index.ts +4 -0
  124. package/src/integration/integration-api.validation.ts +154 -0
  125. package/src/integration/integration.module.ts +53 -0
  126. package/src/integration/services/domain-event.publisher.ts +136 -0
  127. package/src/integration/services/event-subscriber.registry.ts +89 -0
  128. package/src/integration/services/inbox.service.ts +218 -0
  129. package/src/integration/services/index.ts +12 -0
  130. package/src/integration/services/integration-developer-api.service.ts +96 -0
  131. package/src/integration/services/integration-link.service.ts +154 -0
  132. package/src/integration/services/integration-settings.service.ts +128 -0
  133. package/src/integration/services/outbox-polling.coordinator.ts +146 -0
  134. package/src/integration/services/outbox.notifier.ts +48 -0
  135. package/src/integration/services/outbox.processor.job.ts +97 -0
  136. package/src/integration/services/outbox.processor.ts +266 -0
  137. package/src/integration/services/outbox.service.ts +209 -0
  138. package/src/integration/types/event.types.ts +93 -0
  139. package/src/integration/types/index.ts +3 -0
  140. package/src/integration/types/subscriber.types.ts +37 -0
  141. package/src/oauth/oauth.controller.ts +17 -17
  142. package/src/oauth/oauth.service.ts +20 -20
  143. package/src/setting/setting.service.ts +27 -2
  144. package/src/task/task.service.ts +5 -5
@@ -0,0 +1,303 @@
1
+ - slug: database-cleanup
2
+ name:
3
+ en: Database Cleanup
4
+ pt: Limpeza do Banco de Dados
5
+ description:
6
+ en: Settings for cleanup routines and database maintenance jobs
7
+ pt: Configurações para rotinas de limpeza e tarefas de manutenção do banco de dados
8
+
9
+ - slug: session-retention
10
+ name:
11
+ en: Session Retention
12
+ pt: Retenção de Sessões
13
+ description:
14
+ en: Rules for retaining and removing expired user sessions
15
+ pt: Regras para retenção e remoção de sessões expiradas de usuários
16
+
17
+ - slug: email-password-auth
18
+ name:
19
+ en: Email and Password Authentication
20
+ pt: Autenticação por Email e Senha
21
+ description:
22
+ en: Controls for traditional email and password sign-in
23
+ pt: Controles para autenticação tradicional com email e senha
24
+
25
+ - slug: password-policy
26
+ name:
27
+ en: Password Policy
28
+ pt: Política de Senha
29
+ description:
30
+ en: Requirements and validation rules for user passwords
31
+ pt: Requisitos e regras de validação para senhas de usuários
32
+
33
+ - slug: email-restrictions
34
+ name:
35
+ en: Email Restrictions
36
+ pt: Restrições de Email
37
+ description:
38
+ en: Domain allowlists and blocklists for email-based access
39
+ pt: Listas de permissão e bloqueio de domínios para acesso baseado em email
40
+
41
+ - slug: mfa-general
42
+ name:
43
+ en: MFA General
44
+ pt: MFA Geral
45
+ description:
46
+ en: Core multi-factor authentication behavior and challenge settings
47
+ pt: Comportamento principal da autenticação multifator e configurações de desafio
48
+
49
+ - slug: mfa-totp
50
+ name:
51
+ en: MFA TOTP
52
+ pt: MFA TOTP
53
+ description:
54
+ en: Time-based one-time password configuration
55
+ pt: Configuração de senhas temporárias baseadas em tempo
56
+
57
+ - slug: mfa-email
58
+ name:
59
+ en: MFA Email
60
+ pt: MFA por Email
61
+ description:
62
+ en: Email-based multi-factor authentication settings
63
+ pt: Configurações de autenticação multifator baseada em email
64
+
65
+ - slug: token-configuration
66
+ name:
67
+ en: Token Configuration
68
+ pt: Configuração de Tokens
69
+ description:
70
+ en: Access and refresh token lifetime settings
71
+ pt: Configurações de duração de tokens de acesso e atualização
72
+
73
+ - slug: session-management
74
+ name:
75
+ en: Session Management
76
+ pt: Gerenciamento de Sessões
77
+ description:
78
+ en: Limits and policies for concurrent user sessions
79
+ pt: Limites e políticas para sessões simultâneas de usuários
80
+
81
+ - slug: regional-settings
82
+ name:
83
+ en: Regional Settings
84
+ pt: Configurações Regionais
85
+ description:
86
+ en: Default language and timezone options for the system
87
+ pt: Opções padrão de idioma e fuso horário para o sistema
88
+
89
+ - slug: date-time-formats
90
+ name:
91
+ en: Date and Time Formats
92
+ pt: Formatos de Data e Hora
93
+ description:
94
+ en: Formatting rules for dates and times across the application
95
+ pt: Regras de formatação para datas e horários em toda a aplicação
96
+
97
+ - slug: oauth-general
98
+ name:
99
+ en: OAuth General
100
+ pt: OAuth Geral
101
+ description:
102
+ en: Shared settings for OAuth providers and role assignment
103
+ pt: Configurações compartilhadas para provedores OAuth e atribuição de funções
104
+
105
+ - slug: google
106
+ name:
107
+ en: Google
108
+ pt: Google
109
+ description:
110
+ en: Google OAuth Provider
111
+ pt: Provedor OAuth do Google
112
+
113
+ - slug: facebook
114
+ name:
115
+ en: Facebook
116
+ pt: Facebook
117
+ description:
118
+ en: Facebook OAuth Provider
119
+ pt: Provedor OAuth do Facebook
120
+
121
+ - slug: microsoft
122
+ name:
123
+ en: Microsoft
124
+ pt: Microsoft
125
+ description:
126
+ en: Microsoft OAuth Provider
127
+ pt: Provedor OAuth da Microsoft
128
+
129
+ - slug: github
130
+ name:
131
+ en: GitHub
132
+ pt: GitHub
133
+ description:
134
+ en: GitHub OAuth Provider
135
+ pt: Provedor OAuth do GitHub
136
+
137
+ - slug: microsoft-entra-id
138
+ name:
139
+ en: Microsoft Entra ID
140
+ pt: Microsoft Entra ID
141
+ description:
142
+ en: Microsoft Entra ID (Azure AD) OAuth Provider
143
+ pt: Provedor OAuth Microsoft Entra ID (Azure AD)
144
+
145
+ - slug: theme-selector
146
+ name:
147
+ en: Theme Selector
148
+ pt: Seletor de Tema
149
+ description:
150
+ en: Global theme mode selection settings
151
+ pt: Configurações de seleção global do modo de tema
152
+
153
+ - slug: light-theme-colors
154
+ name:
155
+ en: Light Theme Colors
156
+ pt: Cores do Tema Claro
157
+ description:
158
+ en: Palette configuration for the light theme
159
+ pt: Configuração da paleta para o tema claro
160
+
161
+ - slug: dark-theme-colors
162
+ name:
163
+ en: Dark Theme Colors
164
+ pt: Cores do Tema Escuro
165
+ description:
166
+ en: Palette configuration for the dark theme
167
+ pt: Configuração da paleta para o tema escuro
168
+
169
+ - slug: theme-customization
170
+ name:
171
+ en: Theme Customization
172
+ pt: Personalização de Tema
173
+ description:
174
+ en: Layout, typography, and other theme customization options
175
+ pt: Opções de layout, tipografia e outras personalizações do tema
176
+
177
+ - slug: branding
178
+ name:
179
+ en: Branding
180
+ pt: Identidade Visual
181
+ description:
182
+ en: Logo, icon, and brand presentation settings
183
+ pt: Configurações de logo, ícone e apresentação da marca
184
+
185
+ - slug: storage-general
186
+ name:
187
+ en: Storage General
188
+ pt: Armazenamento Geral
189
+ description:
190
+ en: Shared configuration for file storage providers and limits
191
+ pt: Configuração compartilhada para provedores de armazenamento e limites
192
+
193
+ - slug: local-storage
194
+ name:
195
+ en: Local Storage
196
+ pt: Armazenamento Local
197
+ description:
198
+ en: Filesystem-based storage configuration
199
+ pt: Configuração de armazenamento baseado em sistema de arquivos
200
+
201
+ - slug: s3-storage
202
+ name:
203
+ en: Amazon S3 Storage
204
+ pt: Armazenamento Amazon S3
205
+ description:
206
+ en: AWS S3 bucket access and storage settings
207
+ pt: Configurações de acesso e armazenamento em bucket AWS S3
208
+
209
+ - slug: abs-storage
210
+ name:
211
+ en: Azure Blob Storage
212
+ pt: Armazenamento Azure Blob
213
+ description:
214
+ en: Azure Blob Storage account and container settings
215
+ pt: Configurações de conta e contêiner do Azure Blob Storage
216
+
217
+ - slug: gcs-storage
218
+ name:
219
+ en: Google Cloud Storage
220
+ pt: Armazenamento Google Cloud
221
+ description:
222
+ en: Google Cloud Storage authentication settings
223
+ pt: Configurações de autenticação do Google Cloud Storage
224
+
225
+ - slug: mail-general
226
+ name:
227
+ en: Mail General
228
+ pt: Email Geral
229
+ description:
230
+ en: Shared mail delivery settings and provider selection
231
+ pt: Configurações compartilhadas de entrega de email e seleção de provedor
232
+
233
+ - slug: smtp-config
234
+ name:
235
+ en: SMTP Configuration
236
+ pt: Configuração SMTP
237
+ description:
238
+ en: SMTP server connection and credential settings
239
+ pt: Configurações de conexão e credenciais do servidor SMTP
240
+
241
+ - slug: gmail-config
242
+ name:
243
+ en: Gmail Configuration
244
+ pt: Configuração do Gmail
245
+ description:
246
+ en: OAuth settings for Gmail-based email delivery
247
+ pt: Configurações OAuth para envio de email via Gmail
248
+
249
+ - slug: ses-config
250
+ name:
251
+ en: Amazon SES Configuration
252
+ pt: Configuração do Amazon SES
253
+ description:
254
+ en: AWS SES credentials and regional settings
255
+ pt: Credenciais e configurações regionais do AWS SES
256
+
257
+ - slug: openai
258
+ name:
259
+ en: OpenAI
260
+ pt: OpenAI
261
+ description:
262
+ en: OpenAI service credential settings
263
+ pt: Configurações de credenciais do serviço OpenAI
264
+
265
+ - slug: gemini
266
+ name:
267
+ en: Gemini
268
+ pt: Gemini
269
+ description:
270
+ en: Google Gemini service credential settings
271
+ pt: Configurações de credenciais do serviço Google Gemini
272
+
273
+ - slug: outbox-general
274
+ name:
275
+ en: Outbox General
276
+ pt: Outbox Geral
277
+ description:
278
+ en: Core settings for the outbox event delivery system
279
+ pt: Configurações principais do sistema de entrega de eventos outbox
280
+
281
+ - slug: startup-behavior
282
+ name:
283
+ en: Startup Behavior
284
+ pt: Comportamento na Inicialização
285
+ description:
286
+ en: Startup processing settings for pending outbox events
287
+ pt: Configurações de processamento na inicialização para eventos pendentes do outbox
288
+
289
+ - slug: processing-config
290
+ name:
291
+ en: Processing Configuration
292
+ pt: Configuração de Processamento
293
+ description:
294
+ en: Background processing cadence and batch settings for outbox events
295
+ pt: Configurações de frequência e lotes do processamento em segundo plano dos eventos outbox
296
+
297
+ - slug: retry-config
298
+ name:
299
+ en: Retry Configuration
300
+ pt: Configuração de Reprocessamento
301
+ description:
302
+ en: Retry, backoff, and dead letter handling for failed outbox events
303
+ pt: Configurações de reprocessamento, backoff e fila de falha para eventos outbox com erro
@@ -19,7 +19,7 @@ import {
19
19
  SettingTypeEnum,
20
20
  } from '@hed-hog/api-types';
21
21
  import { useApp } from '@hed-hog/next-app-provider';
22
- import { Upload } from 'lucide-react';
22
+ import { Eye, EyeOff, Upload } from 'lucide-react';
23
23
  import { useTranslations } from 'next-intl';
24
24
  import { useEffect, useRef, useState } from 'react';
25
25
 
@@ -34,6 +34,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
34
34
  const component =
35
35
  setting.component?.replaceAll('_', '-') || SettingComponentEnum.INPUT_TEXT;
36
36
  const [localValue, setLocalValue] = useState(setting.value);
37
+ const [showSecretValue, setShowSecretValue] = useState(false);
37
38
  const debounceTimerRef = useRef<NodeJS.Timeout | undefined>(undefined);
38
39
  const isTextInputRef = useRef<boolean>(false);
39
40
 
@@ -154,7 +155,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
154
155
  type="text"
155
156
  value={parsedValue || ''}
156
157
  onChange={(e) => handleChangeValue(formatValue(e.target.value))}
157
- className={cn(className, 'bg-background min-w-[200px]')}
158
+ className={cn(className, 'w-full min-w-0 bg-background md:min-w-50')}
158
159
  />
159
160
  );
160
161
 
@@ -167,19 +168,38 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
167
168
  onChange={(e) =>
168
169
  handleChangeValue(formatValue(parseFloat(e.target.value) || 0))
169
170
  }
170
- className={cn(className, 'bg-background min-w-[200px]')}
171
+ className={cn(className, 'w-full min-w-0 bg-background md:min-w-50')}
171
172
  />
172
173
  );
173
174
 
174
175
  case SettingComponentEnum.INPUT_SECRET:
175
176
  isTextInputRef.current = true;
176
177
  return (
177
- <Input
178
- type="password"
179
- value={parsedValue || ''}
180
- onChange={(e) => handleChangeValue(formatValue(e.target.value))}
181
- className={cn(className, 'bg-background min-w-[200px]')}
182
- />
178
+ <div className="relative w-full min-w-0 md:min-w-50">
179
+ <Input
180
+ type={showSecretValue ? 'text' : 'password'}
181
+ value={parsedValue || ''}
182
+ onChange={(e) => handleChangeValue(formatValue(e.target.value))}
183
+ className={cn(
184
+ className,
185
+ 'w-full min-w-0 bg-background pr-10 md:min-w-50'
186
+ )}
187
+ />
188
+ <Button
189
+ type="button"
190
+ variant="ghost"
191
+ size="icon"
192
+ className="absolute right-1 top-1/2 h-8 w-8 -translate-y-1/2"
193
+ onClick={() => setShowSecretValue((prev) => !prev)}
194
+ aria-label={showSecretValue ? 'Hide password' : 'Show password'}
195
+ >
196
+ {showSecretValue ? (
197
+ <EyeOff className="h-4 w-4" />
198
+ ) : (
199
+ <Eye className="h-4 w-4" />
200
+ )}
201
+ </Button>
202
+ </div>
183
203
  );
184
204
 
185
205
  case SettingComponentEnum.INPUT_FILE:
@@ -242,17 +262,17 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
242
262
  };
243
263
 
244
264
  return (
245
- <div className="flex flex-col gap-2 min-w-[200px]">
265
+ <div className="flex w-full min-w-0 flex-col gap-2 md:min-w-50">
246
266
  {parsedValue && (
247
267
  <div>
248
268
  <img
249
269
  src={parsedValue || ''}
250
270
  alt={t('uploadedFileAlt')}
251
- className="w-[200px]"
271
+ className="max-w-full md:w-50"
252
272
  />
253
273
  </div>
254
274
  )}
255
- <div className="flex items-center gap-2">
275
+ <div className="flex w-full min-w-0 items-center gap-2">
256
276
  <input
257
277
  ref={fileInputRef}
258
278
  type="file"
@@ -274,7 +294,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
274
294
  type="text"
275
295
  value={parsedValue || ''}
276
296
  onChange={(e) => handleChangeValue(formatValue(e.target.value))}
277
- className="flex-1 bg-background w-[150px]"
297
+ className="min-w-0 flex-1 bg-background"
278
298
  placeholder={t('fileUrlPlaceholder')}
279
299
  disabled={isUploading}
280
300
  />
@@ -301,7 +321,10 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
301
321
  : [];
302
322
  return (
303
323
  <div
304
- className={cn('flex flex-col space-y-2 min-w-[200px]', className)}
324
+ className={cn(
325
+ 'flex w-full min-w-0 flex-col space-y-2 md:min-w-50',
326
+ className
327
+ )}
305
328
  >
306
329
  {options.map((option) => (
307
330
  <div key={option.id} className="flex items-center space-x-2">
@@ -332,7 +355,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
332
355
  onCheckedChange={(checked) =>
333
356
  handleChangeValue(formatValue(checked), true)
334
357
  }
335
- className={cn(className, 'min-w-[200px]')}
358
+ className={cn(className)}
336
359
  />
337
360
  );
338
361
 
@@ -344,7 +367,10 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
344
367
  onValueChange={(val) => handleChangeValue(formatValue(val), true)}
345
368
  >
346
369
  <SelectTrigger
347
- className={cn('w-full bg-background min-w-[200px]', className)}
370
+ className={cn(
371
+ 'w-full min-w-0 bg-background md:min-w-50',
372
+ className
373
+ )}
348
374
  >
349
375
  <SelectValue placeholder={t('selectOption')} />
350
376
  </SelectTrigger>
@@ -364,7 +390,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
364
390
  <RadioGroup
365
391
  value={parsedValue?.toString() || ''}
366
392
  onValueChange={(val) => handleChangeValue(formatValue(val), true)}
367
- className={cn(className, 'min-w-[200px]')}
393
+ className={cn(className, 'w-full min-w-0 md:min-w-50')}
368
394
  >
369
395
  {radioOptions.map((option) => (
370
396
  <div key={option.id} className="flex items-center space-x-2">
@@ -382,7 +408,7 @@ export const SettingField = ({ setting, className }: SettingFieldProps) => {
382
408
  case SettingComponentEnum.COLOR_PICKER:
383
409
  isTextInputRef.current = true;
384
410
  return (
385
- <div className="flex items-center gap-2 min-w-[200px] max-w-[200px]">
411
+ <div className="flex w-full min-w-0 items-center gap-2 md:max-w-50">
386
412
  <Input
387
413
  type="color"
388
414
  value={parsedValue || '#000000'}
@@ -1,13 +1,43 @@
1
1
  'use client';
2
2
 
3
- import { Card, CardContent } from '@/components/ui/card';
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardDescription,
7
+ CardHeader,
8
+ CardTitle,
9
+ } from '@/components/ui/card';
4
10
  import { PaginatedResult } from '@/types/pagination-result';
5
- import { Setting } from '@hed-hog/api-types';
11
+ import { Setting, SettingSubgroup } from '@hed-hog/api-types';
6
12
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
7
13
  import { Loader } from 'lucide-react';
8
14
  import { useParams } from 'next/navigation';
15
+ import { useMemo } from 'react';
9
16
  import { SettingField } from './components/setting-field';
10
17
 
18
+ type SettingSection = {
19
+ key: string;
20
+ subgroupId: number | null;
21
+ subgroup: SettingSubgroup | null;
22
+ settings: Setting[];
23
+ };
24
+
25
+ type SettingWithLegacySubgroup = Setting & {
26
+ subgroup_id?: number | null;
27
+ };
28
+
29
+ const getLocalizedValue = (
30
+ value?: string | Record<string, string>
31
+ ): string | undefined => {
32
+ if (!value) return undefined;
33
+
34
+ if (typeof value === 'string') {
35
+ return value;
36
+ }
37
+
38
+ return value.en || Object.values(value)[0];
39
+ };
40
+
11
41
  export default function Page() {
12
42
  const params = useParams();
13
43
  const slug = params?.slug;
@@ -23,39 +53,116 @@ export default function Page() {
23
53
  },
24
54
  });
25
55
 
56
+ const groupedSections = useMemo<SettingSection[]>(() => {
57
+ const sectionsMap = new Map<string, SettingSection>();
58
+
59
+ for (const setting of (settings?.data ||
60
+ []) as SettingWithLegacySubgroup[]) {
61
+ const subgroupId =
62
+ setting.subgroupId ??
63
+ setting.subgroup_id ??
64
+ setting.setting_subgroup?.id ??
65
+ null;
66
+ const subgroup = setting.setting_subgroup ?? null;
67
+ const sectionKey =
68
+ subgroupId === null ? 'ungrouped' : `subgroup-${subgroupId}`;
69
+
70
+ if (!sectionsMap.has(sectionKey)) {
71
+ sectionsMap.set(sectionKey, {
72
+ key: sectionKey,
73
+ subgroupId,
74
+ subgroup,
75
+ settings: [],
76
+ });
77
+ }
78
+
79
+ sectionsMap.get(sectionKey)?.settings.push(setting);
80
+ }
81
+
82
+ const sections = Array.from(sectionsMap.values());
83
+
84
+ sections.forEach((section) => {
85
+ section.settings.sort((a, b) => a.slug.localeCompare(b.slug));
86
+ });
87
+
88
+ sections.sort((a, b) => {
89
+ if (a.subgroupId === null && b.subgroupId !== null) return 1;
90
+ if (a.subgroupId !== null && b.subgroupId === null) return -1;
91
+
92
+ const aSlug = a.subgroup?.slug || '';
93
+ const bSlug = b.subgroup?.slug || '';
94
+
95
+ const slugSort = aSlug.localeCompare(bSlug);
96
+ if (slugSort !== 0) return slugSort;
97
+
98
+ return (
99
+ (a.subgroupId ?? Number.MAX_SAFE_INTEGER) -
100
+ (b.subgroupId ?? Number.MAX_SAFE_INTEGER)
101
+ );
102
+ });
103
+
104
+ return sections;
105
+ }, [settings?.data]);
106
+
26
107
  if (!slug) {
27
108
  return <></>;
28
109
  }
29
110
 
30
111
  return (
31
- <div className="space-y-6">
32
- <form className="space-y-4">
33
- <Card className="border-none bg-accent">
34
- {isLoading && (
112
+ <div className="min-w-0 space-y-6">
113
+ <form className="min-w-0 space-y-6">
114
+ {isLoading && (
115
+ <Card className="border-none bg-accent">
35
116
  <CardContent className="flex h-32 w-full items-center justify-center">
36
117
  <Loader className="animate-spin text-muted-foreground w-4 h-4" />
37
118
  </CardContent>
38
- )}
39
- {(settings?.data || []).map((setting: Setting) => (
40
- <CardContent
41
- key={setting.id}
42
- className="flex-col gap-4 border-b last:border-0 border-border pb-4"
43
- >
44
- <div className="flex justify-between items-center">
45
- <div className="flex-col">
46
- <label className="font-semibold">{setting.name}</label>
47
- <p className="text-muted-foreground text-sm">
48
- {setting.description}
49
- </p>
50
- </div>
51
-
52
- <div className="h-full flex items-center">
53
- <SettingField setting={setting} />
54
- </div>
55
- </div>
56
- </CardContent>
57
- ))}
58
- </Card>
119
+ </Card>
120
+ )}
121
+
122
+ {!isLoading &&
123
+ groupedSections.map((section) => {
124
+ const subgroupName = getLocalizedValue(section.subgroup?.name);
125
+ const subgroupDescription = getLocalizedValue(
126
+ section.subgroup?.description
127
+ );
128
+
129
+ return (
130
+ <Card key={section.key} className="min-w-0 border-none bg-accent">
131
+ {section.subgroup && (
132
+ <CardHeader className="pb-3">
133
+ <CardTitle>
134
+ {subgroupName || section.subgroup.slug}
135
+ </CardTitle>
136
+ {subgroupDescription && (
137
+ <CardDescription>{subgroupDescription}</CardDescription>
138
+ )}
139
+ </CardHeader>
140
+ )}
141
+
142
+ <CardContent className="min-w-0 space-y-4">
143
+ {section.settings.map((setting: Setting) => (
144
+ <div
145
+ key={setting.id}
146
+ className="flex min-w-0 flex-col gap-3 border-b border-border pb-4 last:border-0 last:pb-0 md:flex-row md:items-center md:justify-between md:gap-4"
147
+ >
148
+ <div className="min-w-0 flex-1">
149
+ <label className="block font-semibold">
150
+ {setting.name}
151
+ </label>
152
+ <p className="text-muted-foreground text-sm wrap-break-word">
153
+ {setting.description}
154
+ </p>
155
+ </div>
156
+
157
+ <div className="flex w-full min-w-0 items-center md:h-full md:w-auto md:max-w-full md:justify-end">
158
+ <SettingField setting={setting} />
159
+ </div>
160
+ </div>
161
+ ))}
162
+ </CardContent>
163
+ </Card>
164
+ );
165
+ })}
59
166
  </form>
60
167
  </div>
61
168
  );