diff --git a/README.md b/README.md index d7ce3b15d0..7af7233746 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ To build the project and run all tests, execute `./gradlew build`. For more detailed instructions, including **deployment instructions**, see [save-deploy/README.md](save-deploy/README.md). ## Local deployment +0. Install Java 17 (LTS). We recommend [azul](https://www.azul.com/downloads/#downloads-table-zulu). 1. Ensure that docker daemon is running and `docker compose` is installed. We suggest [Docker Desktop](https://www.docker.com/products/docker-desktop/). 2. Run `./gradlew deployLocal -Psave.profile=dev` to start the MySql DB, Minio and will run all Spring Microservices with Docker Compose. 3. Run `./gradlew -Psave.profile=dev :save-frontend:run` to start save-frontend using webpack-dev-server, requests to REST API will be diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/TimelineComponent.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/TimelineComponent.kt index c5d53500da..04b7451581 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/TimelineComponent.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/TimelineComponent.kt @@ -2,17 +2,17 @@ package com.saveourtool.save.frontend.components.basic -import com.saveourtool.save.frontend.externals.fontawesome.faPlus import com.saveourtool.save.frontend.utils.buttonBuilder -import js.core.jso import react.* import react.dom.html.ReactHTML.div import web.cssom.* import kotlinx.datetime.LocalDateTime +const val HOVERABLE_CONST = "hoverable" + val timelineComponent: FC = FC { props -> - val hoverable = props.onNodeClick?.let { "hoverable" }.orEmpty() + val hoverable = props.onNodeClick?.let { HOVERABLE_CONST }.orEmpty() div { className = ClassName("mb-3") @@ -22,50 +22,51 @@ val timelineComponent: FC = FC { props -> +title } } - div { - className = ClassName("p-0 timeline-container") + + props.onAddClick?.let { onClickCallback -> + buttonBuilder( + label = "Add date", + style = "secondary", + isOutline = true, + classes = "btn btn-primary" + ) { + onClickCallback() + } + } + + if (props.dates.isNotEmpty()) { div { - className = ClassName("steps-container") + className = ClassName("p-0 timeline-container") div { - style = jso { - position = "absolute".unsafeCast() - right = "1%".unsafeCast() - top = "0%".unsafeCast() - zIndex = "4".unsafeCast() - } - props.onAddClick?.let { onClickCallback -> - buttonBuilder(faPlus, style = "secondary", isOutline = true, classes = "rounded-circle btn-sm mt-1 mr-1") { - onClickCallback() - } + className = ClassName("steps-container") + div { + className = ClassName("line") } - } - div { - className = ClassName("line completed") - } - props.dates.toList() - .sortedBy { it.second } - .forEach { (label, dateTime) -> - div { - className = ClassName("step completed $hoverable") - props.onNodeClick?.let { onClickCallback -> - style = jso { cursor = "pointer".unsafeCast() } - onClick = { onClickCallback(dateTime, label) } - } + props.dates.toList() + .sortedBy { it.second } + .forEach { (label, dateTime) -> div { - className = ClassName("text-label completed") - +label + className = ClassName("step $hoverable") + props.onNodeClick?.let { onClickCallback -> + onClick = { onClickCallback(dateTime, label) } + } + + div { + className = ClassName("text-label") + +label + } + div { + className = ClassName("date-label") + +dateTime.date.toString() + } } div { - className = ClassName("date-label completed") - +dateTime.date.toString() + className = ClassName("line") } } - div { - className = ClassName("line completed") - } + div { + className = ClassName("line-end") } - div { - className = ClassName("line-end") } } } diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityBadge.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityBadge.kt index c550660cf5..011819eae5 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityBadge.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityBadge.kt @@ -8,42 +8,71 @@ import com.saveourtool.save.frontend.themes.Colors import js.core.jso import react.FC import react.Props +import react.dom.html.ReactHTML.a import react.dom.html.ReactHTML.div +import react.dom.html.ReactHTML.h4 import web.cssom.* +import web.cssom.TextDecoration.Companion.underline private const val FOR_GREEN = 34 private const val FOR_YELLOW = 67 private const val MAX_VALUE = 100 val vulnerabilityBadge: FC = FC { props -> + val (color, criticalityLabel) = when (props.vulnerability.progress) { + in 0..FOR_GREEN -> Colors.SUCCESS.value to "Low" + in FOR_GREEN..FOR_YELLOW -> Colors.WARNING.value to "Moderate" + in FOR_YELLOW..MAX_VALUE -> Colors.DANGER.value to "Critical" + else -> throw IllegalStateException("Progress should be in [0; 100], got ${props.vulnerability.progress}") + } div { - className = ClassName("col") - val (color, criticalityLabel) = when (props.vulnerability.progress) { - in 0..FOR_GREEN -> Colors.SUCCESS.value to "Low" - in FOR_GREEN..FOR_YELLOW -> Colors.WARNING.value to "Moderate" - in FOR_YELLOW..MAX_VALUE -> Colors.DANGER.value to "Critical" - else -> throw IllegalStateException("Progress should be in [0; 100], got ${props.vulnerability.progress}") + className = ClassName("card shadow") + style = jso { + height = HEADER_HEIGHT.unsafeCast() } - div { + className = ClassName("card-body") div { - className = ClassName("row mb-2") + className = ClassName("row") div { - className = ClassName("mx-auto") + className = ClassName("col-3 mr-1") @Suppress("MAGIC_NUMBER", "MagicNumber") - progressBar(props.vulnerability.progress, color = color, size = 150, lineWidth = 45) div { - className = ClassName("text-center font-weight-bold text-uppercase mx-auto p-1") - style = jso { - border = "1px solid $color".unsafeCast() - borderRadius = "1rem".unsafeCast() - this.color = color.unsafeCast() - position = "absolute".unsafeCast() - top = "70%".unsafeCast() - left = "50%".unsafeCast() - transform = "translate(-50%, -50%)".unsafeCast() + className = ClassName("row") + progressBar(props.vulnerability.progress, color = color, size = "6.5rem", lineWidth = "3.5rem") + } + } + div { + className = ClassName("col-6") + div { + className = ClassName("row align-items-center mb-2") + h4 { + className = ClassName("text-gray-900 mb-2") + +"Criticality Scoring" + } + } + div { + className = ClassName("row align-items-center mb-2") + div { + className = ClassName("text-center text-xs font-weight-bold text-uppercase p-1") + style = jso { + border = "0.1rem solid $color".unsafeCast() + borderRadius = "1rem".unsafeCast() + this.color = color.unsafeCast() + } + +criticalityLabel + } + } + div { + className = ClassName("row") + a { + className = ClassName("nav-link text-xs d-flex pl-0 active") + href = "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator" + style = jso { + textDecoration = underline + } + +"NVD CVSS" } - +criticalityLabel } } } @@ -59,4 +88,9 @@ external interface VulnerabilityBadgeProps : Props { * [VulnerabilityDto] to display */ var vulnerability: VulnerabilityDto + + /** + * horizontal alignment + */ + var horizontalAlignment: String } diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityGeneralInfo.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityGeneralInfo.kt index a2ae11ae00..0550b23415 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityGeneralInfo.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityGeneralInfo.kt @@ -47,10 +47,13 @@ val vulnerabilityGeneralInfo: FC = FC { props -> div { className = ClassName("card-body") div { - className = ClassName("row") + className = ClassName("row mb-4") div { - className = ClassName("ml-3 font-weight-bold text-primary text-uppercase mb-4") - +name + className = ClassName("col-6 align-self-center") + div { + className = ClassName("font-weight-bold text-primary-blue text-uppercase") + +name + } } if (isEditDisabled) { buttonBuilder( @@ -73,7 +76,7 @@ val vulnerabilityGeneralInfo: FC = FC { props -> } } textarea { - className = ClassName("auto_height form-control-plaintext pt-0 pb-0") + className = ClassName("auto_height form-control-plaintext pt-0 pb-0 text-gray-900") value = shortDescription rows = 2 } @@ -102,11 +105,11 @@ val vulnerabilityGeneralInfo: FC = FC { props -> } hr { } h6 { - className = ClassName("font-weight-bold text-primary mb-4") + className = ClassName("font-weight-bold text-primary-blue mb-4") +"Description" } textarea { - className = ClassName("auto_height form-control-plaintext pt-0 pb-0") + className = ClassName("auto_height form-control-plaintext pt-0 pb-0 text-gray-900") value = vulnerability.description disabled = isEditDisabled rows = 8 @@ -121,7 +124,7 @@ val vulnerabilityGeneralInfo: FC = FC { props -> if (!vulnerabilityIdentifier.isNullOrEmpty()) { hr { } h6 { - className = ClassName("font-weight-bold text-primary mb-4") + className = ClassName("font-weight-bold text-primary-blue mb-4") +"Original Identifier" } div { @@ -131,7 +134,7 @@ val vulnerabilityGeneralInfo: FC = FC { props -> if (props.canEditVulnerability || props.vulnerability.tags.isNotEmpty()) { hr { } h6 { - className = ClassName("font-weight-bold text-primary mb-4") + className = ClassName("font-weight-bold text-primary-blue mb-4") +"Tags" } div { @@ -146,7 +149,7 @@ val vulnerabilityGeneralInfo: FC = FC { props -> if (!relatedLink.isNullOrEmpty()) { hr { } h6 { - className = ClassName("font-weight-bold text-primary mb-4") + className = ClassName("font-weight-bold text-primary-blue mb-4") +"Related link" } Link { @@ -157,7 +160,7 @@ val vulnerabilityGeneralInfo: FC = FC { props -> userInfo.run { hr { } h6 { - className = ClassName("font-weight-bold text-primary mb-3") + className = ClassName("font-weight-bold text-primary-blue mb-3") +"Author" } renderUserAvatarWithName(this@run, isHorizontal = true, classes = "mr-2") { @@ -169,7 +172,7 @@ val vulnerabilityGeneralInfo: FC = FC { props -> organization?.run { hr { } h6 { - className = ClassName("font-weight-bold text-primary mb-3") + className = ClassName("font-weight-bold text-primary-blue mb-3") +"Organization" } Link { @@ -184,7 +187,7 @@ val vulnerabilityGeneralInfo: FC = FC { props -> if (participants.isNotEmpty()) { hr { } h6 { - className = ClassName("font-weight-bold text-primary mb-4") + className = ClassName("font-weight-bold text-primary-blue mb-4") +"Collaborators" } userBoard { diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityInfoTab.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityInfoTab.kt index 0b1d20ec89..ccd5d254db 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityInfoTab.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityInfoTab.kt @@ -38,7 +38,8 @@ val vulnerabilityInfoTab: FC = FC { props -> val (deleteProject, setDeleteProject) = useState(null) - val isAbleToEdit = props.currentUserInfo.isSuperAdmin() || props.currentUserInfo in props.vulnerability.getAllParticipants() + val isAbleToEdit = + props.currentUserInfo.isSuperAdmin() || props.currentUserInfo in props.vulnerability.getAllParticipants() val (vulnerabilityProjects, setVulnerabilityProjects) = useState>(emptyList()) @@ -176,7 +177,8 @@ val vulnerabilityInfoTab: FC = FC { props -> } } - if (props.vulnerability.dates.isNotEmpty()) { + // if dates are empty - we will not show the timeline for non-owners + if (props.vulnerability.dates.isNotEmpty() || isAbleToEdit) { timelineComponent { dates = props.vulnerability.getDatesWithLabels() onAddClick = { dateWindowOpenness.openWindow() } @@ -193,8 +195,6 @@ val vulnerabilityInfoTab: FC = FC { props -> } .takeIf { isAbleToEdit } } - } else { - renderPlaceholder(isAbleToEdit, "No dates mentioned") { dateWindowOpenness.openWindow() } } renderProjects( @@ -274,7 +274,7 @@ private fun ChildrenBuilder.renderProjects( div { className = ClassName("mt-4") div { - className = ClassName("mb-3 text-xs text-center font-weight-bold text-primary text-uppercase") + className = ClassName("mb-3 text-xs text-center font-weight-bold text-primary-blue text-uppercase") +sectionName } val data = projects.filter { it.type == projectType } @@ -306,7 +306,7 @@ private fun ChildrenBuilder.renderProjectCard(project: VulnerabilityProjectDto) div { className = ClassName("card card-body") h4 { - className = ClassName("text-center text-primary") + className = ClassName("text-center text-primary-blue") Link { className = ClassName("mb-2") to = project.url @@ -345,7 +345,7 @@ private fun ChildrenBuilder.renderPlaceholder( ) { if (isAbleToEdit) { div { - className = ClassName("d-flex justify-content-center") + className = ClassName("d-flex justify-content-center vulnerability-placeholder") onClick = { onClickFun() } style = jso { cursor = "pointer".unsafeCast() diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityView.kt index d633136d88..ffaacaa8f3 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityView.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityView.kt @@ -6,7 +6,6 @@ package com.saveourtool.save.frontend.components.views.vuln -import com.saveourtool.save.domain.Role import com.saveourtool.save.entities.CommentDto import com.saveourtool.save.entities.vulnerability.VulnerabilityDto import com.saveourtool.save.entities.vulnerability.VulnerabilityLanguage @@ -39,6 +38,8 @@ import kotlinx.browser.window import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +const val HEADER_HEIGHT = "9rem" + @Suppress( "MAGIC_NUMBER", "TOO_LONG_FUNCTION", @@ -47,7 +48,6 @@ import kotlinx.serialization.json.Json "EMPTY_BLOCK_STRUCTURE_ERROR", ) val vulnerabilityView: FC = FC { props -> - particles() useBackground(Style.VULN_LIGHT) useTooltip() @@ -173,53 +173,53 @@ val vulnerabilityView: FC = FC { props -> useOnce(fetchVulnerability) - div { - className = ClassName("") + val isSuperAdmin = props.currentUserInfo?.isSuperAdmin() == true + val isOwner = vulnerability.userInfo.id?.let { props.currentUserInfo?.id == it } ?: false + val isParticipant = props.currentUserInfo in vulnerability.participants - val isSuperAdmin = props.currentUserInfo?.globalRole?.isHigherOrEqualThan(Role.SUPER_ADMIN) == true - val isOwner = vulnerability.userInfo.id?.let { props.currentUserInfo?.id == it } ?: false - val isParticipant = props.currentUserInfo in vulnerability.participants + div { + className = ClassName("d-flex align-items-center justify-content-center mb-4") + h1 { + className = ClassName("h3 mb-0 text-center text-gray-800") + +vulnerability.name + } + languageSpan(vulnerability.language) + } + div { + className = ClassName("row justify-content-center align-items-center") div { - className = ClassName("d-flex align-items-center justify-content-center mb-4") - h1 { - className = ClassName("h3 mb-0 text-center text-gray-800") - +vulnerability.name + className = ClassName("col-3 mr-3") + vulnerabilityBadge { + this.vulnerability = vulnerability } - languageSpan(vulnerability.language) } - div { - className = ClassName("row justify-content-center align-items-center") + className = ClassName("col-6") div { - className = ClassName("col-3 mr-3") - vulnerabilityBadge { - this.vulnerability = vulnerability - } - } - div { - className = ClassName("col-6") - div { - className = ClassName("row justify-content-center align-items-center") - tab( - selectedMenu.name, - VulnerabilityTab.values().map { it.name }, - "nav nav-tabs mt-3" - ) { value -> setSelectedMenu { VulnerabilityTab.valueOf(value) } } + className = ClassName("card shadow") + style = jso { + height = HEADER_HEIGHT.unsafeCast() } + tab( + selectedMenu.name, + VulnerabilityTab.values().map { it.name }, + "nav nav-tabs mt-3" + ) { value -> setSelectedMenu { VulnerabilityTab.valueOf(value) } } - val isAbleToEdit = props.currentUserInfo.isSuperAdmin() || props.currentUserInfo in vulnerability.getAllParticipants() + val isAbleToEdit = + props.currentUserInfo.isSuperAdmin() || props.currentUserInfo in vulnerability.getAllParticipants() // separate it to button menu div { - className = ClassName("row justify-content-center align-items-center mt-3") + className = ClassName("row justify-content-center mt-3") div { className = ClassName("d-flex justify-content-end my-2") if (selectedMenu == VulnerabilityTab.INFO) { if (isAbleToEdit) { buttonBuilder( faPlus, - classes = "mr-2 btn-sm", + classes = "mr-2", isOutline = true, title = "Add more info" ) { @@ -230,7 +230,7 @@ val vulnerabilityView: FC = FC { props -> buttonBuilder( if (isTableView) faImage else faTable, "secondary", - classes = "mr-2 btn-sm", + classes = "mr-2", isOutline = true, title = "Change to ${if (isTableView) "card" else "table"} mode" ) { @@ -244,7 +244,7 @@ val vulnerabilityView: FC = FC { props -> "danger", isOutline = true, title = "Delete vulnerability", - classes = "mr-2 btn-sm" + classes = "mr-2" ) { deleteVulnerabilityWindowOpenness.openWindow() } @@ -264,35 +264,35 @@ val vulnerabilityView: FC = FC { props -> } } } + } + div { + className = ClassName("row justify-content-center") + // ===================== LEFT COLUMN ======================================================================= div { - className = ClassName("row justify-content-center") - // ===================== LEFT COLUMN ======================================================================= - div { - className = ClassName("col-3 mr-3") - vulnerabilityGeneralInfo { - this.vulnerability = vulnerability - this.fetchVulnerability = fetchVulnerability - this.canEditVulnerability = isOwner || isSuperAdmin || isParticipant - } + className = ClassName("col-3 mr-3") + vulnerabilityGeneralInfo { + this.vulnerability = vulnerability + this.fetchVulnerability = fetchVulnerability + this.canEditVulnerability = isOwner || isSuperAdmin || isParticipant } - // ===================== RIGHT COLUMN ======================================================================= + } + // ===================== RIGHT COLUMN ======================================================================= + div { + className = ClassName("col-6") div { - className = ClassName("col-6") + className = ClassName("mt-3") + when (selectedMenu) { + VulnerabilityTab.INFO -> vulnerabilityInfoTab { + this.vulnerability = vulnerability + this.currentUserInfo = props.currentUserInfo + this.fetchVulnerability = fetchVulnerability + this.isTableView = isTableView + this.addProjectWindowOpenness = addProjectWindowOpenness + } - div { - className = ClassName("mb-4 mt-3") - when (selectedMenu) { - VulnerabilityTab.INFO -> vulnerabilityInfoTab { - this.vulnerability = vulnerability - this.currentUserInfo = props.currentUserInfo - this.fetchVulnerability = fetchVulnerability - this.isTableView = isTableView - this.addProjectWindowOpenness = addProjectWindowOpenness - } - VulnerabilityTab.COMMENTS -> vulnerabilityCommentTab { - this.vulnerability = vulnerability - this.currentUserInfo = props.currentUserInfo - } + VulnerabilityTab.COMMENTS -> vulnerabilityCommentTab { + this.vulnerability = vulnerability + this.currentUserInfo = props.currentUserInfo } } } diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/progressbar/ReactCircle.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/progressbar/ReactCircle.kt index 8487878a67..12e85b0978 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/progressbar/ReactCircle.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/progressbar/ReactCircle.kt @@ -4,10 +4,7 @@ package com.saveourtool.save.frontend.externals.progressbar -import react.Component -import react.PropsWithChildren -import react.ReactElement -import react.State +import react.* /** * External declaration of [ReactCircleProps] react component @@ -65,4 +62,9 @@ external interface ReactCircleProps : PropsWithChildren { * Show/hide only the "%" symbol. */ var showPercentageSymbol: Boolean + + /** + * Custom styling for text. + */ + var textStyle: CSSProperties } diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/progressbar/ReactCircleBuilder.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/progressbar/ReactCircleBuilder.kt index 1b59b5a213..d5609c697f 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/progressbar/ReactCircleBuilder.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/progressbar/ReactCircleBuilder.kt @@ -12,20 +12,24 @@ import react.react * @param lineWidth of the circle's stroke * @param color of percentage text and "progress" portion of circle * @param handler + * @param showPercentageSymbol */ -@Suppress("MAGIC_NUMBER") +@Suppress("LongParameterList", "TOO_MANY_PARAMETERS") fun ChildrenBuilder.progressBar( progress: Int, - size: Int = 100, - lineWidth: Int = 50, + size: String = "10rem", + lineWidth: String = "5rem", color: String = Colors.SUCCESS.value, + showPercentageSymbol: Boolean = false, handler: ChildrenBuilder.(ReactCircleProps) -> Unit = {}, ) { + // FixMe: setting textStyle as jso this does not work in Circle, investigate why ReactCircle::class.react { - this.size = size.toString() - this.lineWidth = lineWidth.toString() + this.size = size + this.lineWidth = lineWidth this.progress = progress.toString() this.progressColor = color + this.showPercentageSymbol = showPercentageSymbol this.textColor = color handler(this) } diff --git a/save-frontend/src/main/resources/scss/_cards.scss b/save-frontend/src/main/resources/scss/_cards.scss index 36337ddf46..1e1b7c0240 100644 --- a/save-frontend/src/main/resources/scss/_cards.scss +++ b/save-frontend/src/main/resources/scss/_cards.scss @@ -1,5 +1,3 @@ -// Custom Card Styling - .card { .card-header { // Format Dropdowns in Card Headings @@ -34,3 +32,20 @@ } } } + +.vulnerability-placeholder svg { + transition: 0.4s; +} + +.vulnerability-placeholder:hover { + border: #5711d9; + border-style: solid; +} + +.vulnerability-placeholder:hover svg { + transition: 0.4s; + transform: scale(1.2); + color: #5711d9; +} + + diff --git a/save-frontend/src/main/resources/scss/utilities/_timeline.scss b/save-frontend/src/main/resources/scss/utilities/_timeline.scss index 889e22a151..166cf8c6c5 100644 --- a/save-frontend/src/main/resources/scss/utilities/_timeline.scss +++ b/save-frontend/src/main/resources/scss/utilities/_timeline.scss @@ -1,9 +1,10 @@ -$color-timeline-default: #D2D3D8; -$color-step-completed: #5C6174; -$color-in-progress: #13CB8F; -$color-label-default: $color-timeline-default; -$color-label-completed: #17a2b8; -$color-label-loading: $color-in-progress; +$color-step: #4b1bc4; +$color-line: #563d7c; +$color-label-default: #0275d8; + +.text-primary-blue { + color: $color-label-default; +} .timeline-container { display: flex; @@ -12,106 +13,77 @@ $color-label-loading: $color-in-progress; transition: all 200ms ease; box-shadow: none; flex-grow: 1; - border-radius: 5px; + border-radius: 1rem; + .steps-container { - padding-top: 28px; - padding-bottom: 28px; + padding-top: 2.5rem; + padding-bottom: 2.5rem; position: relative; display: flex; align-items: center; justify-content: center; + .step { + transition: 0.4s; z-index: 1; position: relative; display: flex; align-items: center; justify-content: center; - transition: all 200ms ease; flex-grow: 0; - height: 1.2rem; - width: 1.2rem; - border: 4px solid $color-timeline-default; + height: 1.6rem; + width: 1.6rem; + border: 0.2rem solid $color-step; border-radius: 50%; - .preloader, svg { - display: none; - } - &.completed { - background: $color-step-completed; - border: none; - } - &.in-progress { - background: $color-in-progress; - border: none; - .preloader { - display: block; - border: 2px solid #fff; - border-radius: 50%; - border-left-color: transparent; - animation-name: spin; - animation-duration: 2000ms; - animation-iteration-count: infinite; - animation-timing-function: linear; - } - } - .date-label { - position: absolute; - top: 1.75rem; - filter: none; - z-index: 3; - width: 6.5rem; - text-align: center; - color: $color-label-default; - transition: all 200ms ease; - font-weight: 5; - pointer-events: none; - &.completed { - color: $color-label-completed; - } - &.loading { - color: $color-label-loading; - } - } - .text-label { - position: absolute; - filter: none; - bottom: 1.75rem; - z-index: 3; - color: $color-label-default; - transition: all 200ms ease; - font-weight: 700; - width: 6.5rem; - text-align: center; - pointer-events: none; - &.completed { - color: $color-label-completed; - } - &.loading { - color: $color-label-loading; - } - } + background: white; } - .step.completed.hoverable:hover { - border: 8px solid red; + + .date-label { + transition: 0.4s; + position: absolute; + top: 1.75rem; + filter: none; + z-index: 3; + width: 6.5rem; + text-align: center; + color: black; + font-weight: 5; + pointer-events: none; } + + .text-label { + transition: 0.4s; + position: absolute; + filter: none; + bottom: 1.75rem; + z-index: 3; + color: $color-label-default; + font-weight: 700; + width: 6.5rem; + text-align: center; + pointer-events: none; + } + + .step.hoverable:hover { + transform: scale(1.1); + transition: 0.4s; + background: red; + cursor: pointer; + } + .line { transition: all 200ms ease; - height: 2px; + height: 0.2rem; flex-grow: 1; max-width: 100%; - background: $color-timeline-default; - &.completed { - background: $color-step-completed; - } + background: $color-line; } + .line-end { - color: $color-step-completed; - border-top: 0.3rem solid transparent; - border-bottom: 0.3rem solid transparent; - border-left: 0.6rem solid; - width: 0; - height: 0; - max-width: 0; - max-height: 0; + color: $color-line; + border-top: 0.5rem solid transparent; + border-bottom: 0.5rem solid transparent; + border-left: 1.2rem solid; } } -} \ No newline at end of file +}