feat: project edit page add translations on config step

This commit is contained in:
2026-04-04 18:46:15 +02:00
parent bc6d0cabaa
commit 2a286c6cfa
5 changed files with 165 additions and 159 deletions
@@ -56,19 +56,21 @@
<!-- Page Header -->
<div class="space-y-4">
<h1 class="text-[40px]/12 font-bold text-black">
{$t("wizard.configuration.title")}
{$t("pages.project.edit.configuration.title")}
</h1>
<p class="text-content text-base font-normal">{$t("wizard.configuration.subtitle")}</p>
<p class="text-content text-base font-normal">
{$t("pages.project.edit.configuration.subtitle")}
</p>
</div>
<!-- Languages Section -->
<div class="space-y-6">
<div class="space-y-4">
<h2 class="text-2xl font-bold text-black">
{$t("wizard.configuration.languages.title")}
{$t("pages.project.edit.configuration.languages.title")}
</h2>
<p class="text-content text-base font-normal">
{$t("wizard.configuration.languages.description")}
{$t("pages.project.edit.configuration.languages.description")}
</p>
</div>
<LanguageSelector languages={configuration.languages} onChange={handleLanguagesChange} />
@@ -78,25 +80,20 @@
<div class="space-y-6">
<div class="space-y-4">
<h2 class="text-2xl font-bold text-black">
{$t("wizard.configuration.rounds.title")}
{$t("pages.project.edit.configuration.rounds.title")}
</h2>
<p class="text-content text-base font-normal">
{$t("wizard.configuration.rounds.description")}
{$t("pages.project.edit.configuration.rounds.description")}
</p>
</div>
<RoundSelector bind:rounds={configuration.fundingRounds} onChange={handleRoundsChange} />
</div>
<!-- Continue Button -->
<div class="flex justify-start">
<Button
kind="secondary"
size="md"
onclick={handleContinue}
data-testid="config-continue-btn"
>
<div class="mb- flex justify-start">
<Button kind="secondary" size="md" onclick={handleContinue}>
{#snippet children()}
{$t("wizard.configuration.continue")}
{$t("pages.project.edit.configuration.continue")}
{/snippet}
</Button>
</div>
@@ -133,13 +133,13 @@
<Select
bind:value={primaryLanguage}
id="primary-language-{primaryLanguage}"
labelText={$t("wizard.configuration.languages.primaryLabel")}
labelText={$t("pages.project.edit.configuration.languages.primaryLabel")}
required={true}
error={showError ? errors.languages : undefined}
onBlur={handleBlur}
onChange={handlePrimaryChange}
>
<option value="">{$t("wizard.configuration.languages.selectPlaceholder")}</option>
<option value="">{$t("common.select")}</option>
{#each availableLanguages as lang}
<option value={lang.code} disabled={isLanguageDisabled(lang.code, primaryLanguage)}
>{lang.name}</option
@@ -154,12 +154,10 @@
<Select
bind:value={secondaryLanguages[index]}
id="secondary-language-{index}-{secondary}"
labelText={$t("wizard.configuration.languages.secondaryLabel")}
labelText={$t("pages.project.edit.configuration.languages.secondaryLabel")}
onChange={(value) => handleSecondaryChange(index, value)}
>
<option value=""
>{$t("wizard.configuration.languages.selectPlaceholder")}</option
>
<option value="">{$t("common.select")}</option>
{#each availableLanguages as lang}
<option
value={lang.code}
@@ -175,7 +173,7 @@
onclick={() => removeSecondaryLanguage(index)}
data-testid="language-remove-btn-{index}"
class="hover:bg-light-muted text-secondary hover:text-tertiary rounded-lg p-2 transition-colors"
aria-label={$t("wizard.configuration.languages.removeButton")}
aria-label={$t("common.remove")}
>
<svg
class="h-5 w-5"
@@ -206,6 +204,6 @@
<circle cx="12" cy="12" r="10" />
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v8m-4-4h8" />
</svg>
{$t("wizard.configuration.languages.addButton")}
{$t("pages.project.edit.configuration.languages.addButton")}
</button>
</div>
@@ -39,7 +39,7 @@
checked={rounds === 1}
onchange={() => handleChange(1)}
id="round-1-radio"
label={$t("wizard.configuration.rounds.option1")}
label={$t("pages.project.edit.configuration.rounds.option1")}
/>
<!-- 2 Rounds Option -->
@@ -49,6 +49,6 @@
checked={rounds === 2}
onchange={() => handleChange(2)}
id="round-2-radio"
label={$t("wizard.configuration.rounds.option2")}
label={$t("pages.project.edit.configuration.rounds.option2")}
/>
</div>
+117 -117
View File
@@ -52,12 +52,12 @@
// Define the six wizard steps (reactive to language changes)
const steps = $derived<Tab[]>([
{ id: 1, label: $t("pages.project.edit.tabs.configuration.title") },
{ id: 2, label: $t("pages.project.edit.tabs.campaign.title") },
{ id: 3, label: $t("pages.project.edit.tabs.rewards.title") },
{ id: 4, label: $t("pages.project.edit.tabs.collaborations.title") },
{ id: 5, label: $t("pages.project.edit.tabs.budget.title") },
{ id: 6, label: $t("pages.project.edit.tabs.aboutYou.title") },
{ id: 1, label: $t("pages.project.edit.tabs.configuration") },
{ id: 2, label: $t("pages.project.edit.tabs.campaign") },
{ id: 3, label: $t("pages.project.edit.tabs.rewards") },
{ id: 4, label: $t("pages.project.edit.tabs.collaborations") },
{ id: 5, label: $t("pages.project.edit.tabs.budget") },
{ id: 6, label: $t("pages.project.edit.tabs.aboutYou") },
]);
// Reactive values from store
@@ -95,131 +95,131 @@
}
</script>
<div class="mx-auto max-w-6xl">
<!-- Storage Error Alert -->
{#if $persistenceError}
<div
class="bg-tertiary/10 border-tertiary mb-6 rounded-lg border p-4"
role="alert"
aria-live="assertive"
>
<div class="flex items-start gap-3">
<svg
class="text-tertiary h-5 w-5 flex-shrink-0"
fill="currentColor"
viewBox="0 0 20 20"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
clip-rule="evenodd"
/>
</svg>
<div class="flex-1">
<h3 class="text-secondary text-sm font-semibold">
{$t("wizard.errors.storage.title")}
</h3>
<p class="text-tertiary mt-1 text-sm">
{#if $persistenceError === "storage_quota_exceeded"}
{$t("wizard.errors.storage.quota_exceeded")}
{:else}
{$t("wizard.errors.storage.save_failed")}
{/if}
</p>
</div>
<button
type="button"
class="text-tertiary hover:text-secondary flex-shrink-0"
onclick={() => persistenceError.set(null)}
aria-label={$t("wizard.errors.storage.close")}
>
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
<div class="wrapper">
<div class="p-10 pb-20">
<!-- Storage Error Alert -->
{#if $persistenceError}
<div
class="bg-tertiary/10 border-tertiary mb-6 rounded-lg border p-4"
role="alert"
aria-live="assertive"
>
<div class="flex items-start gap-3">
<svg
class="text-tertiary h-5 w-5 flex-shrink-0"
fill="currentColor"
viewBox="0 0 20 20"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
clip-rule="evenodd"
/>
</svg>
</button>
<div class="flex-1">
<h3 class="text-secondary text-sm font-semibold">
{$t("wizard.errors.storage.title")}
</h3>
<p class="text-tertiary mt-1 text-sm">
{#if $persistenceError === "storage_quota_exceeded"}
{$t("wizard.errors.storage.quota_exceeded")}
{:else}
{$t("wizard.errors.storage.save_failed")}
{/if}
</p>
</div>
<button
type="button"
class="text-tertiary hover:text-secondary flex-shrink-0"
onclick={() => persistenceError.set(null)}
aria-label={$t("wizard.errors.storage.close")}
>
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
{/if}
{/if}
<!-- Header with title and action buttons -->
<div
class="bg-soft-purple border-variant1 mb-6 flex items-center justify-between gap-4 rounded-3xl border px-6 py-4 shadow-sm"
>
<!-- Left section: Icon + Title/Subtitle -->
<div class="flex flex-1 items-center gap-4">
<!-- Edit icon (rotated 180°) -->
<div class="flex shrink-0 items-center justify-center">
<div class="rotate-180">
<EditIcon width="24" height="24" />
<!-- Header with title and action buttons -->
<div
class="bg-soft-purple border-variant1 mb-6 flex items-center justify-between gap-4 rounded-3xl border px-6 py-4 shadow-sm"
>
<!-- Left section: Icon + Title/Subtitle -->
<div class="flex flex-1 items-center gap-4">
<!-- Edit icon (rotated 180°) -->
<div class="flex shrink-0 items-center justify-center">
<div class="rotate-180">
<EditIcon width="24" height="24" />
</div>
</div>
<!-- Title and subtitle column -->
<div class="flex min-w-0 flex-1 flex-col justify-center">
<input
type="text"
value={title}
oninput={(e) => onTitleChange?.(e.currentTarget.value)}
placeholder={$t("system.loading")}
class="w-full border-0 bg-transparent pb-0 text-2xl leading-8 font-bold text-black focus:ring-0 focus:outline-none"
/>
<input
type="text"
value={subtitle}
oninput={(e) => onSubtitleChange?.(e.currentTarget.value)}
placeholder={$t("system.loading")}
class="w-full border-0 bg-transparent pt-0 text-sm leading-6 font-normal text-black focus:ring-0 focus:outline-none"
/>
</div>
</div>
<!-- Title and subtitle column -->
<div class="flex min-w-0 flex-1 flex-col justify-center">
<input
type="text"
value={title}
oninput={(e) => onTitleChange?.(e.currentTarget.value)}
placeholder={$t("wizard.header.titlePlaceholder")}
class="w-full border-0 bg-transparent pb-0 text-2xl leading-8 font-bold text-black focus:ring-0 focus:outline-none"
/>
<input
type="text"
value={subtitle}
oninput={(e) => onSubtitleChange?.(e.currentTarget.value)}
placeholder={$t("wizard.header.subtitlePlaceholder")}
class="w-full border-0 bg-transparent pt-0 text-sm leading-6 font-normal text-black focus:ring-0 focus:outline-none"
/>
<!-- Save success Toast -->
{#if showSuccessToast}
<Toast variant="success" bind:showToast={showSuccessToast}
>{$t("wizard.successToast")}</Toast
>
{/if}
<!-- Right section: Action Buttons -->
<div class="flex shrink-0 items-center gap-[16px]">
<Button kind="ghost" size="md" disabled={true}>
<EyeIcon width="20" height="20" />
{$t("common.preview")}
</Button>
<Button
kind="secondary"
size="md"
onclick={handleSave}
disabled={$hasUnsavedChanges ? false : true}
>
{$t("common.save")}
</Button>
<Button
class="disabled:pointer-events-none disabled:opacity-24"
kind="primary"
size="md"
onclick={handlePublish}
disabled={currentStep !== 7}
>
{$t("common.publish")}
</Button>
</div>
</div>
<!-- Save success Toast -->
{#if showSuccessToast}
<Toast variant="success" bind:showToast={showSuccessToast}
>{$t("wizard.successToast")}</Toast
>
{/if}
<!-- Tab Navigation -->
<div class="mb-8">
<TabNavigation tabs={steps} currentTab={currentStep} onTabClick={handleTabClick} />
</div>
<!-- Right section: Action Buttons -->
<div class="flex shrink-0 items-center gap-[16px]">
<Button kind="ghost" size="md" disabled={true} data-testid="wizard-preview-btn">
<EyeIcon width="20" height="20" />
{$t("wizard.buttons.preview")}
</Button>
<Button
kind="secondary"
size="md"
onclick={handleSave}
disabled={$hasUnsavedChanges ? false : true}
data-testid="wizard-save-btn"
>
{$t("wizard.buttons.save")}
</Button>
<Button
class="disabled:pointer-events-none disabled:opacity-24"
kind="primary"
size="md"
onclick={handlePublish}
data-testid="wizard-publish-btn"
disabled={currentStep !== 7}
>
{$t("wizard.buttons.publish")}
</Button>
<!-- Step Content -->
<div class="min-h-100">
{@render currentStepContent()}
</div>
</div>
<!-- Tab Navigation -->
<div class="mb-8">
<TabNavigation tabs={steps} currentTab={currentStep} onTabClick={handleTabClick} />
</div>
<!-- Step Content -->
<div class="min-h-100">
{@render currentStepContent()}
</div>
</div>
+29 -18
View File
@@ -4,7 +4,12 @@
"greeting": "Hola",
"farewell": "Adiós",
"donate": "Donar",
"donateAmount": "Dona {{ amount }}"
"donateAmount": "Dona {{ amount }}",
"save": "Guardar",
"publish": "Publicar",
"preview": "Vista previa",
"select": "Elige una opción",
"remove": "Quitar"
},
"domain": {
"_": "Business concepts (project, reward, contribution, etc.)",
@@ -25,24 +30,30 @@
"project": {
"edit": {
"tabs": {
"configuration": {
"title": "Configuración"
"configuration": "Configuración",
"campaign": "Campaña",
"rewards": "Recompensas",
"collaborations": "Colaboraciones",
"budget": "Presupuesto",
"aboutYou": "Sobre ti"
},
"configuration": {
"title": "Configuración",
"subtitle": "Configura los datos básicos de tu campaña",
"languages": {
"title": "Idiomas de la campaña",
"description": "Cuáles son los idiomas en los que publicarás el contenido de la campaña",
"primaryLabel": "Idioma principal del contenido",
"secondaryLabel": "Idioma secundario del contenido",
"addButton": "Añadir idioma"
},
"campaign": {
"title": "Campaña"
"rounds": {
"title": "Elige las rondas que tendrá tu campaña",
"description": "La configuración predeterminada son 40 días cada ronda, puedes solicitar modificaciones a tu asesor si tu proyecto es seleccionado",
"option1": "1 Ronda",
"option2": "2 Rondas"
},
"rewards": {
"title": "Recompensas"
},
"collaborations": {
"title": "Colaboraciones"
},
"budget": {
"title": "Presupuesto"
},
"aboutYou": {
"title": "Sobre ti"
}
"continue": "Continuar con info de campaña"
},
"rewards": {
"edit": "Editar"
@@ -56,6 +67,7 @@
},
"system": {
"_": "System messages (errors, notifications, validation, etc.)",
"loading": "Cargando...",
"notImplemented": "Esta funcionalidad aún no está implementada. Estamos trabajando en ello, ¡gracias por tu paciencia!"
},
"reward": {
@@ -1118,7 +1130,6 @@
}
}
},
"loading": "Cargando...",
"lang": {
"error": {
"notAvailable": "El idioma seleccionado \"{{lang}}\" no está disponible para este proyecto. Mostrando contenido en el idioma disponible."