Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions apps/web/src/components/settings/ThemingSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ export default function ThemingSettings({ preferences, onSaved }: ThemingSetting
</fieldset>

<fieldset className="space-y-2">
<legend className="text-sm font-medium">
<legend className="text-sm font-medium" data-testid="theming-language-legend">
{t('language.title', { defaultValue: 'Language' })}
</legend>
<p className="text-xs text-muted-foreground">
Expand All @@ -276,6 +276,7 @@ export default function ThemingSettings({ preferences, onSaved }: ThemingSetting
<button
key={value}
type="button"
data-testid={`locale-option-${value}`}
onClick={() => void handleAppearanceChange({ locale: value })}
aria-pressed={localePreference === value}
disabled={isSavingAppearance}
Expand All @@ -297,12 +298,18 @@ export default function ThemingSettings({ preferences, onSaved }: ThemingSetting
</div>

{appearanceSuccess && (
<div className="rounded-md border border-emerald-500/40 bg-emerald-500/10 px-3 py-2 text-sm text-emerald-600">
<div
className="rounded-md border border-emerald-500/40 bg-emerald-500/10 px-3 py-2 text-sm text-emerald-600"
data-testid="theming-appearance-success"
>
{appearanceSuccess}
</div>
)}
{appearanceError && (
<div className="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive">
<div
className="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive"
data-testid="theming-appearance-error"
>
{appearanceError}
</div>
)}
Expand Down
154 changes: 81 additions & 73 deletions apps/web/src/components/settings/TicketFormsCard.tsx

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apps/web/src/components/tickets/CreateTicketPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export default function CreateTicketPage() {
)}
{forms.length > 0 && (
<div>
<label className="mb-1 block text-sm font-medium" htmlFor="ct-form">Start from a form <span className="text-muted-foreground">(optional)</span></label>
<label className="mb-1 block text-sm font-medium" htmlFor="ct-form">{t('createTicketPage.startFromForm')}</label>
<select
id="ct-form"
data-testid="create-ticket-form-picker"
Expand All @@ -284,7 +284,7 @@ export default function CreateTicketPage() {
}
}}
>
<option value="">Blank ticket</option>
<option value="">{t('createTicketPage.blankTicketOption')}</option>
{forms.map((f) => (<option key={f.id} value={f.id}>{f.name}</option>))}
</select>
{selectedForm?.description && <p className="mt-1 text-xs text-muted-foreground">{selectedForm.description}</p>}
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/components/tickets/TicketFormFields.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next';
import type { TicketFormField } from '@breeze/shared';

interface Props {
Expand All @@ -13,6 +14,7 @@ interface Props {
const inputCls = 'w-full rounded-md border bg-background px-2.5 py-1.5 text-sm';

export default function TicketFormFields({ fields, values, errors, onChange }: Props) {
const { t } = useTranslation('tickets');
return (
<div className="space-y-3">
{fields.map((f) => {
Expand Down Expand Up @@ -70,7 +72,7 @@ export default function TicketFormFields({ fields, values, errors, onChange }: P
value={(values[f.key] as string) ?? ''}
onChange={(e) => onChange(f.key, e.target.value)}
>
<option value="">Select…</option>
<option value="">{t('ticketFormFields.selectPlaceholder')}</option>
{(f.options ?? []).map((o) => (
<option key={o} value={o}>
{o}
Expand Down
90 changes: 90 additions & 0 deletions apps/web/src/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2171,6 +2171,96 @@
"inboundEmail": "Inbound Email",
"cannedResponses": "Canned responses"
},
"ticketFormsCard": {
"heading": "Intake forms",
"subheading": "Custom fields collected when a ticket is created. Share one form across all organizations or scope it to a single org.",
"newForm": "New form",
"scopeLegend": "Scope",
"allOrgsOption": "All organizations",
"partnerWideHint": "(partner-wide)",
"orgOnlyOption": "This organization only",
"selectOrgPlaceholder": "Select an organization…",
"orgsLoadFailedNotice": "Organizations failed to load, so an org-scoped form can't be created yet.",
"retry": "Retry",
"visibilityLegend": "Visibility",
"limitOrgsLabel": "Limit to specific organizations",
"allowlistOrgsLoadFailedNotice": "Organizations failed to load, so the allowlist can't be edited yet.",
"nameLabel": "Name",
"optional": "(optional)",
"descriptionLabel": "Description",
"categoryLabel": "Category",
"noCategoryOption": "No category",
"titleTemplateLabel": "Title template",
"titleTemplateHint": "Use {{syntax}} to insert responses.",
"descriptionIntroLabel": "Description intro",
"defaultPriorityLabel": "Default priority",
"noDefaultPriorityOption": "No default",
"priorityOptions": {
"low": "Low",
"normal": "Normal",
"high": "High",
"urgent": "Urgent"
},
"showInPortal": "Show in portal",
"active": "Active",
"fieldsHeading": "Fields",
"addField": "Add field",
"noFieldsYet": "No fields yet. Add one to start collecting responses.",
"fieldLabelPlaceholder": "Field label",
"fieldTypeAriaLabel": "Field type",
"fieldTypeOptions": {
"text": "text",
"textarea": "textarea",
"select": "select",
"checkbox": "checkbox",
"date": "date",
"number": "number"
},
"keyLabel": "key: {{key}}",
"required": "Required",
"moveFieldUp": "Move field up",
"moveFieldDown": "Move field down",
"more": "More",
"less": "Less",
"remove": "Remove",
"helpTextPlaceholder": "Help text (optional)",
"placeholderInputPlaceholder": "Placeholder (optional)",
"optionsPlaceholder": "One option per line",
"previewHeading": "Preview",
"previewEmpty": "Add fields to see a preview.",
"save": "Save",
"saving": "Saving",
"cancel": "Cancel",
"loading": "Loading.",
"loadFailed": "Failed to load intake forms.",
"emptyState": "No intake forms yet. Create one to collect structured details on new tickets.",
"allOrgsTooltip": "Partner-wide form — available to every organization",
"allOrgsBadge": "All orgs",
"limitedOrgsTooltip": "Partner-wide form limited to specific organizations",
"orgCount_one": "{{count}} org",
"orgCount_other": "{{count}} orgs",
"portalBadge": "Portal",
"inactiveBadge": "Inactive",
"fieldsCount_one": "{{count}} field",
"fieldsCount_other": "{{count}} fields",
"categorySuffix": " · {{category}}",
"categoryFallback": "Category",
"edit": "Edit",
"delete": "Delete",
"confirmDelete": "Confirm delete",
"validation": {
"nameRequired": "Name is required.",
"selectOrgRequired": "Select an organization for an organization-scoped form.",
"limitOrgsRequired": "Select at least one organization or uncheck \"Limit to specific organizations\".",
"fieldPrefix": "Field {{path}}: ",
"unknownFieldKey": "Title template references unknown field \"{{tokenSyntax}}\". Available keys: {{keys}}.",
"noKeysAvailable": "none"
},
"formSaved": "Form saved",
"saveFailed": "Failed to save form. Retry.",
"formDeleted": "Form deleted",
"deleteFailed": "Failed to delete form."
},
"orgSecuritySettings": {
"title": "Security",
"description": "Define password, MFA, and session requirements for your organization.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/locales/en/tickets.json
Original file line number Diff line number Diff line change
Expand Up @@ -250,13 +250,18 @@
"createTicket": "Create ticket",
"creationFailed": "Ticket creation failed. Retry.",
"createdToast": "Ticket {{number}} created",
"startFromForm": "Start from a form (optional)",
"blankTicketOption": "Blank ticket",
"priorityOptions": {
"low": "Low",
"normal": "Normal",
"high": "High",
"urgent": "Urgent"
}
},
"ticketFormFields": {
"selectPlaceholder": "Select…"
},
"ticketsPage": {
"title": "Tickets",
"createTicket": "Create ticket",
Expand Down
90 changes: 90 additions & 0 deletions apps/web/src/locales/pt-BR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2171,6 +2171,96 @@
"inboundEmail": "E-mail de entrada",
"cannedResponses": "Respostas prontas"
},
"ticketFormsCard": {
"heading": "Formulários de abertura",
"subheading": "Campos personalizados coletados na abertura de um chamado. Compartilhe um formulário entre todas as organizações ou restrinja-o a uma única organização.",
"newForm": "Novo formulário",
"scopeLegend": "Escopo",
"allOrgsOption": "Todas as organizações",
"partnerWideHint": "(em todo o parceiro)",
"orgOnlyOption": "Somente esta organização",
"selectOrgPlaceholder": "Selecionar uma organização…",
"orgsLoadFailedNotice": "Falha ao carregar as organizações, portanto um formulário restrito a uma organização ainda não pode ser criado.",
"retry": "Tentar novamente",
"visibilityLegend": "Visibilidade",
"limitOrgsLabel": "Restringir a organizações específicas",
"allowlistOrgsLoadFailedNotice": "Falha ao carregar as organizações, portanto a lista de permissões ainda não pode ser editada.",
"nameLabel": "Nome",
"optional": "(opcional)",
"descriptionLabel": "Descrição",
"categoryLabel": "Categoria",
"noCategoryOption": "Sem categoria",
"titleTemplateLabel": "Modelo de título",
"titleTemplateHint": "Use {{syntax}} para inserir respostas.",
"descriptionIntroLabel": "Introdução da descrição",
"defaultPriorityLabel": "Prioridade padrão",
"noDefaultPriorityOption": "Sem padrão",
"priorityOptions": {
"low": "Baixa",
"normal": "Normal",
"high": "Alta",
"urgent": "Urgente"
},
"showInPortal": "Exibir no portal",
"active": "Ativo",
"fieldsHeading": "Campos",
"addField": "Adicionar campo",
"noFieldsYet": "Ainda não há campos. Adicione um para começar a coletar respostas.",
"fieldLabelPlaceholder": "Rótulo do campo",
"fieldTypeAriaLabel": "Tipo do campo",
"fieldTypeOptions": {
"text": "texto",
"textarea": "área de texto",
"select": "seleção",
"checkbox": "caixa de seleção",
"date": "data",
"number": "número"
},
"keyLabel": "chave: {{key}}",
"required": "Obrigatório",
"moveFieldUp": "Mover campo para cima",
"moveFieldDown": "Mover campo para baixo",
"more": "Mais",
"less": "Menos",
"remove": "Remover",
"helpTextPlaceholder": "Texto de ajuda (opcional)",
"placeholderInputPlaceholder": "Espaço reservado (opcional)",
"optionsPlaceholder": "Uma opção por linha",
"previewHeading": "Pré-visualização",
"previewEmpty": "Adicione campos para ver uma pré-visualização.",
"save": "Salvar",
"saving": "Salvando",
"cancel": "Cancelar",
"loading": "Carregando.",
"loadFailed": "Falha ao carregar os formulários de abertura.",
"emptyState": "Ainda não há formulários de abertura. Crie um para coletar detalhes estruturados nos novos chamados.",
"allOrgsTooltip": "Formulário em todo o parceiro — disponível para todas as organizações",
"allOrgsBadge": "Todas as orgs",
"limitedOrgsTooltip": "Formulário em todo o parceiro restrito a organizações específicas",
"orgCount_one": "{{count}} org",
"orgCount_other": "{{count}} orgs",
"portalBadge": "Portal",
"inactiveBadge": "Inativo",
"fieldsCount_one": "{{count}} campo",
"fieldsCount_other": "{{count}} campos",
"categorySuffix": " · {{category}}",
"categoryFallback": "Categoria",
"edit": "Editar",
"delete": "Excluir",
"confirmDelete": "Confirmar exclusão",
"validation": {
"nameRequired": "O nome é obrigatório.",
"selectOrgRequired": "Selecione uma organização para um formulário restrito a uma organização.",
"limitOrgsRequired": "Selecione pelo menos uma organização ou desmarque \"Restringir a organizações específicas\".",
"fieldPrefix": "Campo {{path}}: ",
"unknownFieldKey": "O modelo de título faz referência a um campo desconhecido \"{{tokenSyntax}}\". Chaves disponíveis: {{keys}}.",
"noKeysAvailable": "nenhuma"
},
"formSaved": "Formulário salvo",
"saveFailed": "Falha ao salvar o formulário. Tente novamente.",
"formDeleted": "Formulário excluído",
"deleteFailed": "Falha ao excluir o formulário."
},
"orgSecuritySettings": {
"title": "Segurança",
"description": "Defina requisitos de senha, MFA e sessão para sua organização.",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/locales/pt-BR/tickets.json
Original file line number Diff line number Diff line change
Expand Up @@ -250,13 +250,18 @@
"createTicket": "Criar chamado",
"creationFailed": "Falha ao criar chamado. Tente novamente.",
"createdToast": "Chamado {{number}} criado",
"startFromForm": "Começar a partir de um formulário (opcional)",
"blankTicketOption": "Chamado em branco",
"priorityOptions": {
"low": "Baixa",
"normal": "Normal",
"high": "Alta",
"urgent": "Urgente"
}
},
"ticketFormFields": {
"selectPlaceholder": "Selecionar…"
},
"ticketsPage": {
"title": "Chamados",
"createTicket": "Criar chamado",
Expand Down
2 changes: 2 additions & 0 deletions docker/Dockerfile.api.dev
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ COPY turbo.json tsconfig.json ./
# Copy package.json files for dependency installation
COPY apps/api/package.json ./apps/api/
COPY packages/shared/package.json ./packages/shared/
COPY packages/extension-api/package.json ./packages/extension-api/

# Install all dependencies (including devDependencies for development)
RUN pnpm install

# Copy source code (will be overridden by volume mounts in dev)
COPY apps/api ./apps/api
COPY packages/shared ./packages/shared
COPY packages/extension-api ./packages/extension-api

# Set working directory to api
WORKDIR /app/apps/api
Expand Down
29 changes: 29 additions & 0 deletions e2e-tests/pages/SettingsAppearancePage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { BasePage } from './BasePage';

/**
* /settings/profile — the "Appearance" (Theming) card, including the
* Language fieldset. Also exposes the org-switcher trigger in the Header
* island (pre-existing testid, unrelated component) so specs can assert
* that a locale switch propagates to islands other than the settings page
* itself, without a full reload.
*/
export class SettingsAppearancePage extends BasePage {
url = '/settings/profile';

languageLegend = () => this.page.getByTestId('theming-language-legend');
localeOptionEn = () => this.page.getByTestId('locale-option-en');
localeOptionPtBR = () => this.page.getByTestId('locale-option-pt-BR');
appearanceSuccess = () => this.page.getByTestId('theming-appearance-success');
appearanceError = () => this.page.getByTestId('theming-appearance-error');

// Header island (apps/web/src/components/layout/OrgSwitcher.tsx) — mounted
// as a sibling Astro island (`<Header client:load transition:persist />`
// in DashboardLayout.astro), independent from the `<ProfilePage client:load
// />` island the Language fieldset above lives in.
orgSwitcherTrigger = () => this.page.getByTestId('org-switcher-trigger');

async goto() {
await this.page.goto(this.url);
await this.languageLegend().waitFor();
}
}
Loading
Loading