diff --git a/internal/portal/src/common/Checkbox/Checkbox.scss b/internal/portal/src/common/Checkbox/Checkbox.scss
index f76105ba..5074d700 100644
--- a/internal/portal/src/common/Checkbox/Checkbox.scss
+++ b/internal/portal/src/common/Checkbox/Checkbox.scss
@@ -33,8 +33,21 @@
}
&:disabled ~ .checkbox__checkmark {
- background-color: var(--disabled-bg);
- border-color: var(--disabled-border);
+ background-color: var(
+ --disabled-bg,
+ var(--colors-background-neutral-subtle)
+ );
+ border-color: var(--disabled-border, var(--colors-border-neutral-subtle));
+ cursor: not-allowed;
+ opacity: 0.6;
+ }
+
+ &:checked:disabled ~ .checkbox__checkmark {
+ background-color: var(--colors-background-primary);
+ opacity: 0.6;
+ }
+
+ &:disabled {
cursor: not-allowed;
}
}
@@ -87,6 +100,14 @@
}
}
+ &:has(input:disabled) {
+ cursor: not-allowed;
+
+ .checkbox__label {
+ opacity: 0.6;
+ }
+ }
+
&__error {
position: absolute;
bottom: -20px;
diff --git a/internal/portal/src/common/TopicPicker/TopicPicker.scss b/internal/portal/src/common/TopicPicker/TopicPicker.scss
index 062d394f..70b635ad 100644
--- a/internal/portal/src/common/TopicPicker/TopicPicker.scss
+++ b/internal/portal/src/common/TopicPicker/TopicPicker.scss
@@ -1,11 +1,10 @@
.topic-picker {
border: 1px solid var(--colors-outline-neutral);
border-radius: var(--radius-m);
- max-height: 100%;
- height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
+ max-height: 100%;
&__header {
border-bottom: 1px solid var(--colors-outline-neutral);
diff --git a/internal/portal/src/common/TopicPicker/TopicPicker.tsx b/internal/portal/src/common/TopicPicker/TopicPicker.tsx
index 1e08afa3..78954dfe 100644
--- a/internal/portal/src/common/TopicPicker/TopicPicker.tsx
+++ b/internal/portal/src/common/TopicPicker/TopicPicker.tsx
@@ -19,30 +19,32 @@ interface TopicPickerProps {
const detectSeparator = (topics: string[]): string => {
// Common separators to check
- const possibleSeparators = ['/', '.', '-'];
-
+ const possibleSeparators = ["/", ".", "-"];
+
// Find the first separator that appears in all topics
// and is the first occurring separator in each topic
- return possibleSeparators.find(sep =>
- topics.every(topic => {
- const sepIndex = topic.indexOf(sep);
- if (sepIndex === -1) return false;
-
- // Check if any other separator appears before this one
- const otherSepsIndex = possibleSeparators
- .filter(s => s !== sep)
- .map(s => topic.indexOf(s))
- .filter(idx => idx !== -1);
-
- return otherSepsIndex.every(idx => idx === -1 || idx > sepIndex);
- })
- ) || '-'; // Fallback to '-' if no consistent separator is found
+ return (
+ possibleSeparators.find((sep) =>
+ topics.every((topic) => {
+ const sepIndex = topic.indexOf(sep);
+ if (sepIndex === -1) return false;
+
+ // Check if any other separator appears before this one
+ const otherSepsIndex = possibleSeparators
+ .filter((s) => s !== sep)
+ .map((s) => topic.indexOf(s))
+ .filter((idx) => idx !== -1);
+
+ return otherSepsIndex.every((idx) => idx === -1 || idx > sepIndex);
+ })
+ ) || "-"
+ ); // Fallback to '-' if no consistent separator is found
};
const topics: Topic[] = (() => {
const topicsList = CONFIGS.TOPICS.split(",");
const separator = detectSeparator(topicsList);
-
+
return topicsList.map((topic) => {
const parts = topic.split(separator);
return {
@@ -174,9 +176,10 @@ const TopicPicker = ({
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ")}`}
- checked={areAllSelected}
- indeterminate={isIndeterminate}
+ checked={isEverythingSelected || areAllSelected}
+ indeterminate={!isEverythingSelected && isIndeterminate}
onChange={() => toggleCategorySelection(categoryTopics)}
+ disabled={isEverythingSelected}
/>
{isExpanded && (
@@ -184,10 +187,14 @@ const TopicPicker = ({
{categoryTopics.map((topic) => (
toggleTopic(topic.id)}
label={topic.id}
monospace
+ disabled={isEverythingSelected}
/>
))}
diff --git a/internal/portal/src/scenes/CreateDestination/CreateDestination.scss b/internal/portal/src/scenes/CreateDestination/CreateDestination.scss
index 7095b1dc..1aeac43d 100644
--- a/internal/portal/src/scenes/CreateDestination/CreateDestination.scss
+++ b/internal/portal/src/scenes/CreateDestination/CreateDestination.scss
@@ -3,10 +3,10 @@
grid-template-columns: 272px 1fr;
width: 100%;
gap: 132px;
- padding-top: calc(var(--base-grid-multiplier) * 18);
margin: 0 auto;
flex-direction: row;
- height: 100vh;
+ padding-top: calc(var(--base-grid-multiplier) * 18);
+ height: calc(100vh - calc(var(--base-grid-multiplier) * 18));
&__sidebar {
display: flex;
@@ -55,16 +55,23 @@
}
&__step {
+ height: 100%;
+ overflow: hidden;
display: flex;
flex-direction: column;
padding-bottom: var(--spacing-16);
box-sizing: border-box;
- h1 {
- margin-top: 64px;
- margin-bottom: 0;
- ~ p {
- margin-top: 0;
+ &__header {
+ padding-left: 4px;
+ padding-right: 4px;
+
+ h1 {
+ margin-top: 64px;
+ margin-bottom: 0;
+ ~ p {
+ margin-top: 0;
+ }
}
}
@@ -72,41 +79,55 @@
display: flex;
flex-direction: column;
flex: 1;
+ min-height: 0;
+
+ > div {
+ padding: 4px;
+ }
}
&__fields {
flex: 0 1 auto;
- max-height: 100%;
+ min-height: 0;
}
&__actions {
margin-top: var(--spacing-8);
display: flex;
justify-content: flex-end;
+ flex-shrink: 0;
}
}
}
.destination-types {
- display: flex;
- flex-direction: column;
- gap: 1rem;
+ border: 1px solid var(--colors-outline-neutral);
+ border-radius: var(--radius-m);
+ overflow: hidden;
+ height: 100%;
+
+ &__container {
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ }
}
-.destination-type-card {
+.destination-type-option {
cursor: pointer;
position: relative;
- border: 1px solid var(--colors-outline-neutral);
- border-radius: var(--radius-m);
+ border-bottom: 1px solid var(--colors-outline-neutral);
+
+ &:last-child {
+ border-bottom: none;
+ }
&:has(input[type="radio"]:checked) {
- border-color: var(--colors-outline-primary-focus);
- outline: 1px solid var(--colors-outline-primary-focus);
+ background-color: var(--colors-background-hover);
}
&:hover {
- border-color: var(--colors-outline-primary-focus);
- outline: 1px solid var(--colors-outline-primary-focus);
+ background-color: var(--colors-background-hover);
}
input[type="radio"] {
diff --git a/internal/portal/src/scenes/CreateDestination/CreateDestination.tsx b/internal/portal/src/scenes/CreateDestination/CreateDestination.tsx
index 64e8e3ba..8a831de0 100644
--- a/internal/portal/src/scenes/CreateDestination/CreateDestination.tsx
+++ b/internal/portal/src/scenes/CreateDestination/CreateDestination.tsx
@@ -21,6 +21,7 @@ type Step = {
FormFields: (props: {
defaultValue: Record;
onChange: (value: Record) => void;
+ destinations?: DestinationTypeReference[];
}) => React.ReactNode;
action: string;
};
@@ -84,35 +85,39 @@ const DESTINATION_TYPE_STEP: Step = {
FormFields: ({
destinations,
defaultValue,
+ onChange,
}: {
- destinations: DestinationTypeReference[];
+ destinations?: DestinationTypeReference[];
defaultValue: Record;
+ onChange?: (value: Record) => void;
}) => (
- {destinations?.map((destination) => (
-
- ))}
+
+ {destinations?.map((destination) => (
+
+ ))}
+
),
action: "Next",
@@ -122,12 +127,18 @@ const CONFIGURATION_STEP: Step = {
title: "Configure destination",
sidebar_shortname: "Configure destination",
description: "Configure the destination you want to send to your destination",
+ isValid: (values: Record) => {
+ // Form validation will be handled by the form's native validation
+ return true;
+ },
FormFields: ({
defaultValue,
destinations,
+ onChange,
}: {
defaultValue: Record;
- destinations: DestinationTypeReference[];
+ destinations?: DestinationTypeReference[];
+ onChange?: (value: Record) => void;
}) => {
const destinationType = destinations?.find(
(d) => d.type === defaultValue.type
@@ -164,6 +175,15 @@ export default function CreateDestination() {
const currentStep = steps[currentStepIndex];
const nextStep = steps[currentStepIndex + 1] || null;
+ // Validate the current step when it changes or stepValues change
+ useEffect(() => {
+ if (currentStep.isValid) {
+ setIsValid(currentStep.isValid(stepValues));
+ } else {
+ setIsValid(false);
+ }
+ }, [currentStepIndex, stepValues, currentStep]);
+
const createDestination = (values: Record) => {
setIsCreating(true);
@@ -244,16 +264,19 @@ export default function CreateDestination() {
-
{currentStep.title}
-
{currentStep.description}
+
+
{currentStep.title}
+
{currentStep.description}
+