diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/index/CardUser.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/index/CardUser.kt index 39b9b30453..4445dff198 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/index/CardUser.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/index/CardUser.kt @@ -259,7 +259,7 @@ val cardUser: FC = FC { props -> } div { - className = ClassName("row text-muted border-bottom border-gray mx-3") + className = ClassName("row text-muted border-bottom border-gray mx-3 mt-2") div { className = ClassName("col-9") p { diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/userprofile/UserProfileView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/userprofile/UserProfileView.kt index 9c2f44ec4b..b18493a1ec 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/userprofile/UserProfileView.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/userprofile/UserProfileView.kt @@ -208,13 +208,13 @@ fun ChildrenBuilder.renderLeftUserMenu( } } - user?.gitHub?.let { extraLinks(faGithub, it) } + user?.gitHub?.let { extraLinks(faGithub, it, listOf(UsefulUrls.GITHUB, UsefulUrls.GITEE)) } - user?.twitter?.let { extraLinks(faTwitter, it) } + user?.twitter?.let { extraLinks(faTwitter, it, listOf(UsefulUrls.TWITTER, UsefulUrls.XCOM)) } - user?.linkedin?.let { extraLinks(faLink, it) } + user?.linkedin?.let { extraLinks(faLinkedIn, it, listOf(UsefulUrls.LINKEDIN)) } - user?.website?.let { extraLinks(faLink, it) } + user?.website?.let { extraLinks(faLink, it, listOf(UsefulUrls.HTTPS, UsefulUrls.HTTP)) } if (organizations.isNotEmpty()) { div { @@ -243,16 +243,21 @@ fun ChildrenBuilder.renderLeftUserMenu( /** * @param icon * @param info + * @param patterns */ -fun ChildrenBuilder.extraLinks(icon: FontAwesomeIconModule, info: String) { - div { - className = ClassName("mb-2") - fontAwesomeIcon(icon = icon) { - it.className = "fas fa-sm fa-fw mr-2 text-gray-900" - } - a { - href = info - +info.substringAfterLast("/") +fun ChildrenBuilder.extraLinks(icon: FontAwesomeIconModule, info: String, patterns: List) { + val foundPattern = patterns.map { it.value }.findLast { info.startsWith(it) } + foundPattern?.let { + val trimmedUserName = info.substringAfterLast(foundPattern) + div { + className = ClassName("mb-2") + fontAwesomeIcon(icon = icon) { + it.className = "fas fa-sm fa-fw mr-2 text-gray-900" + } + a { + href = info + +trimmedUserName + } } } } diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsEmailMenuView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsEmailMenuView.kt deleted file mode 100644 index 471ec63912..0000000000 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsEmailMenuView.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.saveourtool.save.frontend.components.views.usersettings - -import com.saveourtool.save.frontend.components.basic.cardComponent -import com.saveourtool.save.frontend.components.inputform.InputTypes - -import react.VFC -import react.dom.html.ReactHTML.button -import react.dom.html.ReactHTML.div -import react.dom.html.ReactHTML.hr -import react.dom.html.ReactHTML.input -import web.cssom.ClassName -import web.html.ButtonType -import web.html.InputType - -@Suppress("MISSING_KDOC_TOP_LEVEL") -class UserSettingsEmailMenuView : UserSettingsView() { - private val emailCard = cardComponent(isBordered = false, hasBg = true) - @Suppress("EMPTY_BLOCK_STRUCTURE_ERROR", "TOO_LONG_FUNCTION") - override fun renderMenu(): VFC = VFC { - emailCard { - div { - className = ClassName("row mt-2 ml-2 mr-2") - div { - className = ClassName("col-5 text-left align-self-center") - +"User email:" - } - div { - className = ClassName("col-7 input-group pl-0") - input { - type = InputType.email - className = ClassName("form-control") - state.userInfo?.email?.let { - defaultValue = it - } - placeholder = "email@example.com" - onChange = { - changeFields(InputTypes.USER_EMAIL, it) - } - } - } - } - - hr {} - div { - className = ClassName("row d-flex justify-content-center") - div { - className = ClassName("col-3 d-sm-flex align-items-center justify-content-center") - button { - type = ButtonType.button - className = ClassName("btn btn-sm btn-outline-primary") - onClick = { - updateUser() - } - +"Save changes" - } - } - } - } - } -} diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsOrganizationsMenuView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsOrganizationsMenuView.kt deleted file mode 100644 index 071f900d95..0000000000 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsOrganizationsMenuView.kt +++ /dev/null @@ -1,187 +0,0 @@ -package com.saveourtool.save.frontend.components.views.usersettings - -import com.saveourtool.save.domain.Role -import com.saveourtool.save.entities.OrganizationStatus -import com.saveourtool.save.entities.OrganizationWithUsers -import com.saveourtool.save.frontend.components.basic.AVATAR_ORGANIZATION_PLACEHOLDER -import com.saveourtool.save.frontend.components.basic.cardComponent -import com.saveourtool.save.frontend.components.basic.organizations.responseChangeOrganizationStatus -import com.saveourtool.save.frontend.components.views.actionButtonClasses -import com.saveourtool.save.frontend.components.views.actionIconClasses -import com.saveourtool.save.frontend.externals.fontawesome.faRedo -import com.saveourtool.save.frontend.externals.fontawesome.faTrashAlt -import com.saveourtool.save.frontend.externals.fontawesome.fontAwesomeIcon -import com.saveourtool.save.frontend.utils.actionButton -import com.saveourtool.save.frontend.utils.buttonBuilder -import com.saveourtool.save.frontend.utils.spanWithClassesAndText -import com.saveourtool.save.v1 -import react.* -import react.dom.html.ReactHTML.div -import react.dom.html.ReactHTML.h1 -import react.dom.html.ReactHTML.img -import react.dom.html.ReactHTML.li -import react.dom.html.ReactHTML.ul -import react.router.dom.Link -import web.cssom.ClassName - -@Suppress("MISSING_KDOC_TOP_LEVEL", "TOO_LONG_FUNCTION", "LongMethod") -class UserSettingsOrganizationsMenuView : UserSettingsView() { - private val organizationListCard = cardComponent(isBordered = false, hasBg = true) - private val comparator: Comparator = - compareBy { it.organization.status.ordinal } - .thenBy { it.organization.name } - - /** - * Removes [oldOrganizationWithUsers] by [selfOrganizationWithUserList], adds [newOrganizationWithUsers] in [selfOrganizationWithUserList] - * and sorts the resulting list by their status and then by name - * - * @param organizationWithUsers - * @param newStatus - */ - private fun updateOrganizationWithUserInOrganizationWithUsersList(oldOrganizationWithUsers: OrganizationWithUsers, newOrganizationWithUsers: OrganizationWithUsers) { - setState { - selfOrganizationWithUserList = selfOrganizationWithUserList.minusElement(oldOrganizationWithUsers) - .plusElement(newOrganizationWithUsers) - .sortedWith(comparator) - } - } - - /** - * Returned the [organizationWithUsers] with the updated [OrganizationStatus] field to the [newStatus] in the organization field - */ - private fun changeOrganizationWithUserStatus(organizationWithUsers: OrganizationWithUsers, newStatus: OrganizationStatus) = - organizationWithUsers.copy(organization = organizationWithUsers.organization.copy(status = newStatus)) - - @Suppress("CyclomaticComplexMethod") - override fun renderMenu(): VFC = VFC { - organizationListCard { - div { - className = ClassName("d-sm-flex align-items-center justify-content-center mb-4 mt-4") - h1 { - className = ClassName("h3 mb-0 mt-2 text-gray-800") - +"Organizations" - } - } - - ul { - className = ClassName("list-group list-group-flush") - state.selfOrganizationWithUserList.forEach { organizationWithUsers -> - val organizationDto = organizationWithUsers.organization - li { - className = ClassName("list-group-item") - div { - className = ClassName("row justify-content-between align-items-center") - div { - val textClassName = when (organizationDto.status) { - OrganizationStatus.CREATED -> "text-primary" - OrganizationStatus.DELETED -> "text-secondary" - OrganizationStatus.BANNED -> "text-danger" - } - className = ClassName("align-items-center ml-3 $textClassName") - img { - className = ClassName("avatar avatar-user width-full border color-bg-default rounded-circle mr-2") - src = organizationDto.avatar?.let { - "/api/$v1/avatar$it" - } ?: AVATAR_ORGANIZATION_PLACEHOLDER - height = 60.0 - width = 60.0 - } - when (organizationDto.status) { - OrganizationStatus.CREATED -> Link { - to = "/${organizationDto.name}" - +organizationDto.name - } - OrganizationStatus.DELETED -> { - +organizationDto.name - spanWithClassesAndText("text-secondary", organizationDto.status.name.lowercase()) - } - OrganizationStatus.BANNED -> { - +organizationDto.name - spanWithClassesAndText("text-danger", organizationDto.status.name.lowercase()) - } - } - } - div { - className = ClassName("col-5 text-right") - val role = state.userInfo?.name?.let { organizationWithUsers.userRoles[it] } ?: Role.NONE - if (role.isHigherOrEqualThan(Role.OWNER)) { - when (organizationDto.status) { - OrganizationStatus.CREATED -> actionButton { - title = "WARNING: You are about to delete this organization" - errorTitle = "You cannot delete the organization ${organizationDto.name}" - message = "Are you sure you want to delete the organization ${organizationDto.name}?" - buttonStyleBuilder = { childrenBuilder -> - with(childrenBuilder) { - fontAwesomeIcon(icon = faTrashAlt, classes = actionIconClasses.joinToString(" ")) - } - } - classes = actionButtonClasses.joinToString(" ") - modalButtons = { action, closeWindow, childrenBuilder, _ -> - with(childrenBuilder) { - buttonBuilder(label = "Yes, delete ${organizationDto.name}", style = "danger", classes = "mr-2") { - action() - closeWindow() - } - buttonBuilder("Cancel") { - closeWindow() - } - } - } - onActionSuccess = { _ -> - updateOrganizationWithUserInOrganizationWithUsersList( - organizationWithUsers, - changeOrganizationWithUserStatus(organizationWithUsers, OrganizationStatus.DELETED), - ) - } - conditionClick = false - sendRequest = { _ -> - responseChangeOrganizationStatus(organizationDto.name, OrganizationStatus.DELETED) - } - } - OrganizationStatus.DELETED -> actionButton { - title = "WARNING: You are about to recover this organization" - errorTitle = "You cannot recover the organization ${organizationDto.name}" - message = "Are you sure you want to recover the organization ${organizationDto.name}?" - buttonStyleBuilder = { childrenBuilder -> - with(childrenBuilder) { - fontAwesomeIcon(icon = faRedo, classes = actionIconClasses.joinToString(" ")) - } - } - classes = actionButtonClasses.joinToString(" ") - modalButtons = { action, closeWindow, childrenBuilder, _ -> - with(childrenBuilder) { - buttonBuilder(label = "Yes, recover ${organizationDto.name}", style = "danger", classes = "mr-2") { - action() - closeWindow() - } - buttonBuilder("Cancel") { - closeWindow() - } - } - } - onActionSuccess = { _ -> - updateOrganizationWithUserInOrganizationWithUsersList( - organizationWithUsers, - changeOrganizationWithUserStatus(organizationWithUsers, OrganizationStatus.CREATED), - ) - } - conditionClick = false - sendRequest = { _ -> - responseChangeOrganizationStatus(organizationDto.name, OrganizationStatus.CREATED) - } - } - OrganizationStatus.BANNED -> Unit - } - } - div { - className = ClassName("mr-3") - +role.formattedName - } - } - } - } - } - } - } - } -} diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsProfileMenuView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsProfileMenuView.kt deleted file mode 100644 index 96e07f1232..0000000000 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsProfileMenuView.kt +++ /dev/null @@ -1,180 +0,0 @@ -package com.saveourtool.save.frontend.components.views.usersettings - -import com.saveourtool.save.frontend.components.basic.cardComponent -import com.saveourtool.save.frontend.components.inputform.InputTypes -import com.saveourtool.save.frontend.components.modal.displayModal -import com.saveourtool.save.frontend.components.modal.mediumTransparentModalStyle -import com.saveourtool.save.frontend.utils.* - -import react.VFC -import react.dom.html.ReactHTML.div -import react.dom.html.ReactHTML.hr -import react.dom.html.ReactHTML.input -import web.cssom.ClassName -import web.html.InputType - -@Suppress("MISSING_KDOC_TOP_LEVEL") -class UserSettingsProfileMenuView : UserSettingsView() { - private val card = cardComponent(isBordered = false, hasBg = true) - @Suppress("TOO_LONG_FUNCTION", "LongMethod", "EMPTY_BLOCK_STRUCTURE_ERROR") - override fun renderMenu(): VFC = VFC { - val deleteUserWindowOpenness = useWindowOpenness() - - displayModal( - deleteUserWindowOpenness.isOpen(), - "Deletion of user profile", - "Are you sure you want to permanently delete your profile? You will never be able to restore it again.", - mediumTransparentModalStyle, - deleteUserWindowOpenness.closeWindowAction(), - ) { - buttonBuilder("Yes") { - deleteUser() - deleteUserWindowOpenness.closeWindow() - } - buttonBuilder("Cancel", "secondary") { - deleteUserWindowOpenness.closeWindow() - } - } - - card { - div { - className = ClassName("row mt-2 ml-2 mr-2") - div { - className = ClassName("col-5 mt-2 text-left align-self-center") - +"User name:" - } - div { - className = ClassName("col-7 mt-2 input-group pl-0") - input { - type = InputType.text - className = ClassName("form-control") - state.userInfo?.name?.let { - defaultValue = it - } - onChange = { - changeFields(InputTypes.USER_NAME, it) - } - } - } - state.conflictErrorMessage?.let { - div { - className = ClassName("invalid-feedback d-block") - +it - } - } - - div { - className = ClassName("col-5 mt-2 text-left align-self-center") - +"Company/affiliation:" - } - div { - className = ClassName("col-7 mt-2 input-group pl-0") - input { - type = InputType.text - className = ClassName("form-control") - state.userInfo?.company?.let { - defaultValue = it - } - onChange = { - changeFields(InputTypes.COMPANY, it) - } - } - } - - div { - className = ClassName("col-5 mt-2 text-left align-self-center") - +"Location:" - } - div { - className = ClassName("col-7 mt-2 input-group pl-0") - input { - type = InputType.text - className = ClassName("form-control") - state.userInfo?.location?.let { - defaultValue = it - } - onChange = { - changeFields(InputTypes.LOCATION, it) - } - } - } - - div { - className = ClassName("col-5 mt-2 text-left align-self-center") - +"Linkedin:" - } - div { - className = ClassName("col-7 mt-2 input-group pl-0") - input { - type = InputType.text - className = ClassName("form-control") - state.userInfo?.linkedin?.let { - defaultValue = it - } - onChange = { - changeFields(InputTypes.LINKEDIN, it) - } - } - } - - div { - className = ClassName("col-5 mt-2 text-left align-self-center") - +"GitHub:" - } - div { - className = ClassName("col-7 mt-2 input-group pl-0") - input { - type = InputType.text - className = ClassName("form-control") - state.userInfo?.gitHub?.let { - defaultValue = it - } - onChange = { - changeFields(InputTypes.GITHUB, it) - } - } - } - - div { - className = ClassName("col-5 mt-2 text-left align-self-center") - +"Twitter:" - } - div { - className = ClassName("col-7 mt-2 input-group pl-0") - input { - type = InputType.text - className = ClassName("form-control") - state.userInfo?.twitter?.let { - defaultValue = it - } - onChange = { - changeFields(InputTypes.TWITTER, it) - } - } - } - } - - hr {} - div { - className = ClassName("row d-flex justify-content-center") - div { - className = ClassName("col-8 d-sm-flex align-items-center justify-content-center") - - div { - className = ClassName("col-4") - buttonBuilder("Save changes", style = "primary", classes = "mr-3") { - updateUser() - } - } - - div { - className = ClassName("col-4") - buttonBuilder("Delete your profile", style = "danger") { - deleteUserWindowOpenness.openWindow() - } - } - } - } - } - } -} diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsTokenMenuView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsTokenMenuView.kt deleted file mode 100644 index 01ca6572da..0000000000 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsTokenMenuView.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.saveourtool.save.frontend.components.views.usersettings - -import com.saveourtool.save.frontend.components.basic.cardComponent -import com.saveourtool.save.frontend.utils.apiUrl -import com.saveourtool.save.frontend.utils.jsonHeaders -import com.saveourtool.save.frontend.utils.noopLoadingHandler -import com.saveourtool.save.frontend.utils.post - -import kotlinext.js.assign -import react.VFC -import react.dom.html.ReactHTML.button -import react.dom.html.ReactHTML.div -import react.dom.html.ReactHTML.h1 -import react.dom.html.ReactHTML.input -import web.cssom.ClassName -import web.html.ButtonType - -import kotlinx.coroutines.launch - -@Suppress("MISSING_KDOC_TOP_LEVEL") -class UserSettingsTokenMenuView : UserSettingsView() { - private val tokenCard = cardComponent(isBordered = false, hasBg = true) - @Suppress("TOO_LONG_FUNCTION") - override fun renderMenu(): VFC = VFC { - tokenCard { - div { - className = ClassName("d-sm-flex align-items-center justify-content-center mb-4") - h1 { - className = ClassName("h3 mb-0 mt-2 text-gray-800") - +"Personal access tokens" - } - } - - div { - className = ClassName("row justify-content-center") - button { - type = ButtonType.button - className = ClassName("btn btn-outline-primary mb-2 mr-2") - +"Generate new token" - onClick = { - generateToken() - } - } - } - - state.token?.let { - div { - className = ClassName("col-12 mt-3") - input { - value = state.token ?: "" - required = true - className = ClassName("form-control") - } - div { - className = ClassName("invalid-feedback d-block") - +"This is your unique token. It will be shown to you only once. Please remember it." - } - } - } - } - } - - @Suppress("MAGIC_NUMBER") - private fun generateToken() { - var token = "ghp_" - val charPool = ('a'..'z') + ('A'..'Z') + ('0'..'9') - while (token.length < 40) { - token += charPool.random() - } - setState( - assign(state) { - this.token = token - } - ) { - scope.launch { - post( - "$apiUrl/users/${state.userInfo!!.name}/save/token", - jsonHeaders, - state.token, - loadingHandler = ::noopLoadingHandler, - ) - } - } - } -} diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsView.kt deleted file mode 100644 index ece42a1b4d..0000000000 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/UserSettingsView.kt +++ /dev/null @@ -1,386 +0,0 @@ -/** - * A view with settings user - */ - -package com.saveourtool.save.frontend.components.views.usersettings - -import com.saveourtool.save.entities.OrganizationWithUsers -import com.saveourtool.save.filters.OrganizationFilter -import com.saveourtool.save.frontend.components.basic.avatarForm -import com.saveourtool.save.frontend.components.inputform.InputTypes -import com.saveourtool.save.frontend.components.views.AbstractView -import com.saveourtool.save.frontend.externals.fontawesome.* -import com.saveourtool.save.frontend.http.getUser -import com.saveourtool.save.frontend.http.postImageUpload -import com.saveourtool.save.frontend.utils.* -import com.saveourtool.save.info.UserInfo -import com.saveourtool.save.utils.AvatarType -import com.saveourtool.save.v1 -import com.saveourtool.save.validation.FrontendRoutes - -import js.core.jso -import org.w3c.fetch.Headers -import react.* -import react.dom.events.ChangeEvent -import react.dom.html.ReactHTML.div -import react.dom.html.ReactHTML.form -import react.dom.html.ReactHTML.h1 -import react.dom.html.ReactHTML.img -import react.dom.html.ReactHTML.label -import react.dom.html.ReactHTML.nav -import react.router.dom.Link -import web.cssom.* -import web.html.HTMLInputElement - -import kotlinx.browser.window -import kotlinx.coroutines.launch -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json - -/** - * `Props` retrieved from router - */ -@Suppress("MISSING_KDOC_CLASS_ELEMENTS") -external interface UserSettingsProps : PropsWithChildren { - /** - * Currently logged in user or null - */ - var userName: String? -} - -/** - * [State] of project view component - */ -@Suppress("MISSING_KDOC_TOP_LEVEL", "TYPE_ALIAS") -external interface UserSettingsViewState : State { - /** - * Currently logged in user or null - */ - var userInfo: UserInfo? - - /** - * Token for user - */ - var token: String? - - /** - * A list of organization with users connected to user - */ - var selfOrganizationWithUserList: List - - /** - * Conflict error message - */ - var conflictErrorMessage: String? - - /** - * Flag to handle avatar Window - */ - var isAvatarWindowOpen: Boolean - - /** - * User avatar - */ - var avatar: String -} - -@Suppress("MISSING_KDOC_TOP_LEVEL") -abstract class UserSettingsView : AbstractView(Style.SAVE_LIGHT) { - private val fieldsMap: MutableMap = mutableMapOf() - private val renderMenu = renderMenu() - - init { - state.selfOrganizationWithUserList = emptyList() - state.isAvatarWindowOpen = false - } - - /** - * @param fieldName - * @param target - */ - fun changeFields( - fieldName: InputTypes, - target: ChangeEvent, - ) { - val tg = target.target - val value = tg.value - fieldsMap[fieldName] = value - } - - override fun componentDidMount() { - super.componentDidMount() - val comparator: Comparator = - compareBy { it.organization.status.ordinal } - .thenBy { it.organization.name } - - scope.launch { - val user = props.userName - ?.let { getUser(it) } - val organizationDtos = getOrganizationWithUsersList() - setState { - userInfo = user - userInfo?.let { updateFieldsMap(it) } - selfOrganizationWithUserList = organizationDtos.sortedWith(comparator) - avatar = user?.avatar?.let { "/api/$v1/avatar$it" } ?: AVATAR_PROFILE_PLACEHOLDER - } - } - } - - private fun updateFieldsMap(userInfo: UserInfo) { - userInfo.name.let { fieldsMap[InputTypes.USER_NAME] = it } - userInfo.email?.let { fieldsMap[InputTypes.USER_EMAIL] = it } - userInfo.company?.let { fieldsMap[InputTypes.COMPANY] = it } - userInfo.location?.let { fieldsMap[InputTypes.LOCATION] = it } - userInfo.linkedin?.let { fieldsMap[InputTypes.LINKEDIN] = it } - userInfo.gitHub?.let { fieldsMap[InputTypes.GITHUB] = it } - userInfo.twitter?.let { fieldsMap[InputTypes.TWITTER] = it } - } - - /** - * @return element - */ - abstract fun renderMenu(): FC - - @Suppress("TOO_LONG_FUNCTION", "LongMethod", "MAGIC_NUMBER") - override fun ChildrenBuilder.render() { - avatarForm { - isOpen = state.isAvatarWindowOpen - title = AVATAR_TITLE - onCloseWindow = { - setState { - isAvatarWindowOpen = false - } - } - imageUpload = { file -> - scope.launch { - postImageUpload(file, props.userName!!, AvatarType.USER, ::noopLoadingHandler) - } - } - } - - div { - className = ClassName("row justify-content-center") - // ===================== LEFT COLUMN ======================================================================= - div { - className = ClassName("col-2 mr-3") - div { - className = ClassName("card card-body mt-0 pt-0 pr-0 pl-0 border-secondary") - div { - className = ClassName("col mr-2 pr-0 pl-0") - style = jso { - background = "#e1e9ed".unsafeCast() - } - div { - className = ClassName("mb-0 font-weight-bold text-gray-800") - form { - div { - className = ClassName("row g-3 ml-3 mr-3 pb-2 pt-2 border-bottom") - div { - className = ClassName("col-4 pl-0 pr-0") - label { - className = ClassName("btn") - title = AVATAR_TITLE - onClick = { - setState { - isAvatarWindowOpen = true - } - } - img { - className = ClassName("avatar avatar-user width-full border color-bg-default rounded-circle") - src = state.avatar - height = 60.0 - width = 60.0 - onError = { - setState { - avatar = AVATAR_PLACEHOLDER - } - } - } - } - } - div { - className = ClassName("col-6 pl-0") - style = jso { - display = Display.flex - alignItems = AlignItems.center - } - h1 { - className = ClassName("h5 mb-0 text-gray-800") - +"${props.userName}" - } - } - } - } - } - } - - div { - className = ClassName("col mr-2 pr-0 pl-0") - nav { - div { - className = ClassName("pl-3 ui vertical menu profile-setting") - form { - div { - className = ClassName("item mt-2") - div { - className = ClassName("header") - +"Basic Setting" - } - div { - className = ClassName("menu") - div { - className = ClassName("mt-2") - Link { - className = ClassName("item") - to = "/${props.userName}/${FrontendRoutes.SETTINGS_PROFILE}" - fontAwesomeIcon(icon = faUser) { - it.className = "fas fa-sm fa-fw mr-2 text-gray-600" - } - +"Profile settings" - } - } - div { - className = ClassName("mt-2") - Link { - className = ClassName("item") - to = "/${props.userName}/${FrontendRoutes.SETTINGS_EMAIL}" - fontAwesomeIcon(icon = faEnvelope) { - it.className = "fas fa-sm fa-fw mr-2 text-gray-600" - } - +"Email management" - } - } - div { - className = ClassName("mt-2") - Link { - className = ClassName("item") - to = "/${props.userName}/${FrontendRoutes.SETTINGS_ORGANIZATIONS}" - fontAwesomeIcon(icon = faCity) { - it.className = "fas fa-sm fa-fw mr-2 text-gray-600" - } - +"Organizations" - } - } - } - } - } - form { - div { - className = ClassName("item mt-2") - div { - className = ClassName("header") - +"Security Setting" - } - div { - className = ClassName("menu") - div { - className = ClassName("mt-2") - Link { - className = ClassName("item") - to = "/${props.userName}/${FrontendRoutes.SETTINGS_TOKEN}" - fontAwesomeIcon(icon = faKey) { - it.className = "fas fa-sm fa-fw mr-2 text-gray-600" - } - +"Personal access tokens" - } - } - } - } - } - } - } - } - } - } - - // ===================== RIGHT COLUMN ======================================================================= - div { - className = ClassName("col-6") - renderMenu { - userName = props.userName - } - } - } - } - - @Suppress("MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION") - fun updateUser() { - val newName = fieldsMap[InputTypes.USER_NAME]?.trim() - val nameInDb = state.userInfo!!.name - val oldName = if (newName != nameInDb) nameInDb else null - val newUserInfo = UserInfo( - name = newName ?: nameInDb, - oldName = oldName, - originalLogins = state.userInfo!!.originalLogins, - projects = state.userInfo!!.projects, - email = fieldsMap[InputTypes.USER_EMAIL]?.trim(), - company = fieldsMap[InputTypes.COMPANY]?.trim(), - location = fieldsMap[InputTypes.LOCATION]?.trim(), - linkedin = fieldsMap[InputTypes.LINKEDIN]?.trim(), - gitHub = fieldsMap[InputTypes.GITHUB]?.trim(), - twitter = fieldsMap[InputTypes.TWITTER]?.trim(), - avatar = state.userInfo!!.avatar, - status = state.userInfo!!.status, - ) - - val headers = Headers().also { - it.set("Accept", "application/json") - it.set("Content-Type", "application/json") - } - scope.launch { - val response = post( - "$apiUrl/users/save", - headers, - Json.encodeToString(newUserInfo), - loadingHandler = ::classLoadingHandler, - ) - if (response.isConflict()) { - val responseText = response.unpackMessage() - setState { - conflictErrorMessage = responseText - } - } else { - setState { - conflictErrorMessage = null - } - } - } - } - - @Suppress("MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION") - fun deleteUser() { - scope.launch { - val response = get( - url = "$apiUrl/users/delete/${state.userInfo!!.name}", - headers = jsonHeaders, - loadingHandler = ::classLoadingHandler, - responseHandler = ::noopResponseHandler, - ) - if (response.ok) { - val replyToLogout = post( - "${window.location.origin}/logout", - Headers(), - "ping", - loadingHandler = ::classLoadingHandler, - ) - if (replyToLogout.ok) { - window.location.href = "${window.location.origin}/#" - window.location.reload() - } - } - } - } - - @Suppress("TYPE_ALIAS") - private suspend fun getOrganizationWithUsersList() = post( - url = "$apiUrl/organizations/by-filters", - headers = jsonHeaders, - body = Json.encodeToString(OrganizationFilter.all), - loadingHandler = ::classLoadingHandler, - ) - .unsafeMap { it.decodeFromJsonString>() } - - companion object { - private const val AVATAR_TITLE = "Change avatar owner" - } -} diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/right/SettingsInputFields.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/right/SettingsInputFields.kt index 49d6b5ffe0..5589d7ad91 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/right/SettingsInputFields.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/usersettings/right/SettingsInputFields.kt @@ -111,7 +111,7 @@ data class SettingsInputFields( linkedin = this.linkedIn.value?.trim() ?: userInfo.linkedin, gitHub = this.github.value?.trim() ?: userInfo.gitHub, twitter = this.twitter.value?.trim() ?: userInfo.twitter, - website = this.website.value?.trim() ?: userInfo.twitter, + website = this.website.value?.trim() ?: userInfo.website, realName = this.realName.value?.trim() ?: userInfo.realName, freeText = this.freeText.value?.trim() ?: userInfo.freeText, ) diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/fontawesome/BrandIcons.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/fontawesome/BrandIcons.kt index 61cb198cb9..776e70371d 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/fontawesome/BrandIcons.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/fontawesome/BrandIcons.kt @@ -12,6 +12,6 @@ external val faGithub: FontAwesomeIconModule @JsNonModule external val faTwitter: FontAwesomeIconModule -@JsModule("@fortawesome/free-brands-svg-icons/faLinkedIn") +@JsModule("@fortawesome/free-brands-svg-icons/faLinkedinIn") @JsNonModule external val faLinkedIn: FontAwesomeIconModule diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/routing/BasicRouting.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/routing/BasicRouting.kt index e7031fdec3..a61ed22aea 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/routing/BasicRouting.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/routing/BasicRouting.kt @@ -240,22 +240,6 @@ val basicRouting: FC = FC { props -> type = SETTINGS_DELETE } to SETTINGS_DELETE, - props.viewWithFallBack( - UserSettingsProfileMenuView::class.react.create { userName = props.userInfo?.name } - ) to "${props.userInfo?.name}/$SETTINGS_PROFILE", - - props.viewWithFallBack( - UserSettingsEmailMenuView::class.react.create { userName = props.userInfo?.name } - ) to "${props.userInfo?.name}/$SETTINGS_EMAIL", - - props.viewWithFallBack( - UserSettingsTokenMenuView::class.react.create { userName = props.userInfo?.name } - ) to "${props.userInfo?.name}/$SETTINGS_TOKEN", - - props.viewWithFallBack( - UserSettingsOrganizationsMenuView::class.react.create { userName = props.userInfo?.name } - ) to "${props.userInfo?.name}/$SETTINGS_ORGANIZATIONS", - ).forEach { (view, route) -> PathRoute { this.element = view diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/utils/UsefulUrls.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/utils/UsefulUrls.kt new file mode 100644 index 0000000000..9a64944111 --- /dev/null +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/utils/UsefulUrls.kt @@ -0,0 +1,17 @@ +package com.saveourtool.save.frontend.utils + +/** + * Enum only for storing URLs to well-known website + * + * @property value real url of a website + */ +enum class UsefulUrls(val value: String) { + GITEE("https://gitee.com/"), + GITHUB("https://github.com/"), + HTTP("http://"), + HTTPS("https://"), + LINKEDIN("https://linkedin.com/"), + TWITTER("https://twitter.com/"), + XCOM("https://x.com/"), + ; +}