diff --git a/.github/workflows/build-and-push-docker-base-image.yml b/.github/workflows/build-and-push-docker-base-image.yml index 427435d..2cb2dac 100644 --- a/.github/workflows/build-and-push-docker-base-image.yml +++ b/.github/workflows/build-and-push-docker-base-image.yml @@ -40,5 +40,5 @@ jobs: build-args: ALPINE_VERSION=${{ env.ALPINE_VERSION }} push: true tags: | - ghcr.io/plankanban/planka:base-latest - ghcr.io/plankanban/planka:base-${{ env.ALPINE_VERSION }} + ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:base-latest + ghcr.io/${{ github.repository_owner }}/${{ github.repository }}:base-${{ env.ALPINE_VERSION }} diff --git a/.github/workflows/build-and-push-docker-image-dev.yml b/.github/workflows/build-and-push-docker-image-dev.yml index 7b591f2..fa5b98d 100644 --- a/.github/workflows/build-and-push-docker-image-dev.yml +++ b/.github/workflows/build-and-push-docker-image-dev.yml @@ -11,7 +11,7 @@ on: branches: [master] env: - REGISTRY_IMAGE: ghcr.io/plankanban/planka + REGISTRY_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.repository }} jobs: build: diff --git a/.github/workflows/build-and-push-docker-image.yml b/.github/workflows/build-and-push-docker-image.yml index 47bb737..51d2bde 100644 --- a/.github/workflows/build-and-push-docker-image.yml +++ b/.github/workflows/build-and-push-docker-image.yml @@ -31,12 +31,23 @@ jobs: result-encoding: string script: return context.payload.release.tag_name.replace('v', '') + - name: Generate docker image tags + id: metadata + uses: docker/metadata-action@v5 + with: + images: | + name=ghcr.io/${{ github.repository_owner }}/${{ github.repository }} + tags: | + type=raw,value=${{ steps.set-version.outputs.result }} + type=raw,value=latest + - name: Build and push uses: docker/build-push-action@v4 with: context: . platforms: linux/amd64,linux/arm64,linux/arm/v7 push: true - tags: | - ghcr.io/plankanban/planka:latest - ghcr.io/plankanban/planka:${{ steps.set-version.outputs.result }} + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index b077541..31a5368 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ RUN apk -U upgrade \ WORKDIR /app -COPY server/package.json server/package-lock.json . +COPY server/package.json server/package-lock.json ./ RUN npm install npm@latest --global \ && npm install pnpm --global \ @@ -17,7 +17,7 @@ FROM node:lts AS client WORKDIR /app -COPY client/package.json client/package-lock.json . +COPY client/package.json client/package-lock.json ./ RUN npm install npm@latest --global \ && npm install pnpm --global \ @@ -38,6 +38,7 @@ WORKDIR /app COPY --chown=node:node start.sh . COPY --chown=node:node server . +COPY --chown=node:node healthcheck.js . RUN mv .env.sample .env @@ -52,4 +53,8 @@ VOLUME /app/private/attachments EXPOSE 1337 -CMD ["./start.sh"] +HEALTHCHECK --interval=10s --timeout=2s --start-period=15s \ + CMD node ./healthcheck.js + + +CMD [ "bash", "start.sh" ] diff --git a/README.md b/README.md index 9b273c3..5ff3bba 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,14 @@ ## Features - Create projects, boards, lists, cards, labels and tasks -- Add card members, track time, set a due date, add attachments, write comments -- Markdown support in a card description and comment +- Add card members, track time, set due dates, add attachments, write comments +- Markdown support in card description and comments - Filter by members and labels -- Customize project background +- Customize project backgrounds - Real-time updates -- User notifications -- Internationalization +- Internal notifications +- Multiple interface languages +- Single sign-on via OpenID Connect ## How to deploy Planka @@ -44,3 +45,7 @@ See the [development section](https://docs.planka.cloud/docs/Development). ## License Planka is [AGPL-3.0 licensed](https://github.com/plankanban/planka/blob/master/LICENSE). + +## Contributors + +[![](https://contrib.rocks/image?repo=plankanban/planka)](https://github.com/plankanban/planka/graphs/contributors) diff --git a/charts/planka/Chart.yaml b/charts/planka/Chart.yaml index 1c32bf7..02158c7 100644 --- a/charts/planka/Chart.yaml +++ b/charts/planka/Chart.yaml @@ -15,13 +15,13 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.15 +version: 0.1.26 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.15.4" +appVersion: "1.16.4" dependencies: - alias: postgresql diff --git a/charts/planka/README.md b/charts/planka/README.md index ed305b0..f9250e1 100644 --- a/charts/planka/README.md +++ b/charts/planka/README.md @@ -46,7 +46,7 @@ helm install planka . --set secretkey=$SECRETKEY \ --set admin_email="demo@demo.demo" \ --set admin_password="demo" \ --set admin_name="Demo Demo" \ ---set admin_username="demo" +--set admin_username="demo" \ --set ingress.enabled=true \ --set ingress.hosts[0].host=planka.example.dev \ @@ -55,7 +55,7 @@ helm install planka . --set secretkey=$SECRETKEY \ --set admin_email="demo@demo.demo" \ --set admin_password="demo" \ --set admin_name="Demo Demo" \ ---set admin_username="demo" +--set admin_username="demo" \ --set ingress.enabled=true \ --set ingress.hosts[0].host=planka.example.dev \ --set ingress.tls[0].secretName=planka-tls \ @@ -76,6 +76,7 @@ admin_name: "Demo Demo" admin_username: "demo" # Admin user +# Ingress ingress: enabled: true hosts: diff --git a/charts/planka/templates/deployment.yaml b/charts/planka/templates/deployment.yaml index 06d56b9..2ffe596 100644 --- a/charts/planka/templates/deployment.yaml +++ b/charts/planka/templates/deployment.yaml @@ -55,14 +55,24 @@ spec: - mountPath: /app/private/attachments subPath: attachments name: planka + {{- if .Values.securityContext.readOnlyRootFilesystem }} + - mountPath: /app/logs + subPath: app-logs + name: emptydir + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} env: + {{- if not .Values.postgresql.enabled }} + - name: DATABASE_URL + value: {{ required "If the included postgresql deployment is disabled you need to define a Database URL in 'dburl'" .Values.dburl }} + {{- else }} - name: DATABASE_URL valueFrom: secretKeyRef: name: planka-postgresql-svcbind-custom-user key: uri + {{- end }} - name: BASE_URL {{- if .Values.baseUrl }} value: {{ .Values.baseUrl }} @@ -134,3 +144,7 @@ spec: {{- else }} emptyDir: {} {{- end }} + {{- if .Values.securityContext.readOnlyRootFilesystem }} + - name: emptydir + emptyDir: {} + {{- end }} diff --git a/charts/planka/templates/hpa.yaml b/charts/planka/templates/hpa.yaml index 09e4c39..257a09d 100644 --- a/charts/planka/templates/hpa.yaml +++ b/charts/planka/templates/hpa.yaml @@ -1,5 +1,5 @@ {{- if .Values.autoscaling.enabled }} -apiVersion: autoscaling/v2beta1 +apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: {{ include "planka.fullname" . }} @@ -17,12 +17,16 @@ spec: - type: Resource resource: name: cpu - targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} {{- end }} {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} - type: Resource resource: name: memory - targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} {{- end }} {{- end }} diff --git a/charts/planka/values.yaml b/charts/planka/values.yaml index 04cc5a8..8adf94f 100644 --- a/charts/planka/values.yaml +++ b/charts/planka/values.yaml @@ -105,12 +105,14 @@ postgresql: serviceBindings: enabled: true +## Set this if you disable the built-in postgresql deployment +dburl: + ## PVC-based data storage configuration persistence: enabled: false # existingClaim: netbox-data # storageClass: "-" - accessMode: ReadWriteOnce size: 10Gi diff --git a/client/.env b/client/.env index 4d4ab73..1b48387 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -REACT_APP_VERSION=1.15.4 +REACT_APP_VERSION=1.16.4 diff --git a/client/package-lock.json b/client/package-lock.json index 62cebb9..b1e6907 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -17,6 +17,8 @@ "initials": "^3.1.2", "js-cookie": "^3.0.5", "jwt-decode": "^4.0.0", + "linkify-react": "^4.1.3", + "linkifyjs": "^4.1.3", "lodash": "^4.17.21", "nanoid": "^5.0.3", "node-sass": "^9.0.0", @@ -11911,6 +11913,20 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-react": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/linkify-react/-/linkify-react-4.1.3.tgz", + "integrity": "sha512-rhI3zM/fxn5BfRPHfi4r9N7zgac4vOIxub1wHIWXLA5ENTMs+BGaIaFO1D1PhmxgwhIKmJz3H7uCP0Dg5JwSlA==", + "peerDependencies": { + "linkifyjs": "^4.0.0", + "react": ">= 15.0.0" + } + }, + "node_modules/linkifyjs": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.3.tgz", + "integrity": "sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==" + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", diff --git a/client/package.json b/client/package.json index 25cdce0..3f3c4e1 100755 --- a/client/package.json +++ b/client/package.json @@ -70,6 +70,8 @@ "initials": "^3.1.2", "js-cookie": "^3.0.5", "jwt-decode": "^4.0.0", + "linkify-react": "^4.1.3", + "linkifyjs": "^4.1.3", "lodash": "^4.17.21", "nanoid": "^5.0.3", "node-sass": "^9.0.0", diff --git a/client/src/actions/cards.js b/client/src/actions/cards.js index 0969910..2bbe194 100644 --- a/client/src/actions/cards.js +++ b/client/src/actions/cards.js @@ -23,10 +23,14 @@ createCard.failure = (localId, error) => ({ }, }); -const handleCardCreate = (card) => ({ +const handleCardCreate = (card, cardMemberships, cardLabels, tasks, attachments) => ({ type: ActionTypes.CARD_CREATE_HANDLE, payload: { card, + cardMemberships, + cardLabels, + tasks, + attachments, }, }); @@ -60,6 +64,34 @@ const handleCardUpdate = (card) => ({ }, }); +const duplicateCard = (id, card, taskIds) => ({ + type: ActionTypes.CARD_DUPLICATE, + payload: { + id, + card, + taskIds, + }, +}); + +duplicateCard.success = (localId, card, cardMemberships, cardLabels, tasks) => ({ + type: ActionTypes.CARD_DUPLICATE__SUCCESS, + payload: { + localId, + card, + cardMemberships, + cardLabels, + tasks, + }, +}); + +duplicateCard.failure = (id, error) => ({ + type: ActionTypes.CARD_DUPLICATE__FAILURE, + payload: { + id, + error, + }, +}); + const deleteCard = (id) => ({ type: ActionTypes.CARD_DELETE, payload: { @@ -94,6 +126,7 @@ export default { handleCardCreate, updateCard, handleCardUpdate, + duplicateCard, deleteCard, handleCardDelete, }; diff --git a/client/src/actions/core.js b/client/src/actions/core.js index 2d58587..92f081a 100644 --- a/client/src/actions/core.js +++ b/client/src/actions/core.js @@ -47,9 +47,11 @@ initializeCore.fetchConfig = (config) => ({ }, }); -const logout = () => ({ +const logout = (invalidateAccessToken) => ({ type: ActionTypes.LOGOUT, - payload: {}, + payload: { + invalidateAccessToken, + }, }); logout.invalidateAccessToken = () => ({ diff --git a/client/src/api/cards.js b/client/src/api/cards.js index 5240038..568ca8e 100755 --- a/client/src/api/cards.js +++ b/client/src/api/cards.js @@ -57,6 +57,12 @@ const updateCard = (id, data, headers) => item: transformCard(body.item), })); +const duplicateCard = (id, data, headers) => + socket.post(`/cards/${id}/duplicate`, data, headers).then((body) => ({ + ...body, + item: transformCard(body.item), + })); + const deleteCard = (id, headers) => socket.delete(`/cards/${id}`, undefined, headers).then((body) => ({ ...body, @@ -81,6 +87,7 @@ export default { getCard, updateCard, deleteCard, + duplicateCard, makeHandleCardCreate, makeHandleCardUpdate, makeHandleCardDelete, diff --git a/client/src/components/BoardMembershipsStep/BoardMembershipsStep.module.scss b/client/src/components/BoardMembershipsStep/BoardMembershipsStep.module.scss index deec2a3..6d50f19 100644 --- a/client/src/components/BoardMembershipsStep/BoardMembershipsStep.module.scss +++ b/client/src/components/BoardMembershipsStep/BoardMembershipsStep.module.scss @@ -4,7 +4,6 @@ max-height: 60vh; overflow-x: hidden; overflow-y: auto; - scrollbar-width: thin; width: 100%; &::-webkit-scrollbar { diff --git a/client/src/components/Boards/Boards.module.scss b/client/src/components/Boards/Boards.module.scss index 848dbe5..e38de2d 100644 --- a/client/src/components/Boards/Boards.module.scss +++ b/client/src/components/Boards/Boards.module.scss @@ -85,7 +85,6 @@ height: 56px; overflow-x: auto; overflow-y: hidden; - scrollbar-width: thin; &:hover { height: 38px; diff --git a/client/src/components/Card/ActionsStep.jsx b/client/src/components/Card/ActionsStep.jsx index dbd349e..bd9db9e 100644 --- a/client/src/components/Card/ActionsStep.jsx +++ b/client/src/components/Card/ActionsStep.jsx @@ -36,6 +36,7 @@ const ActionsStep = React.memo( onUpdate, onMove, onTransfer, + onDuplicate, onDelete, onUserAdd, onUserRemove, @@ -76,6 +77,11 @@ const ActionsStep = React.memo( openStep(StepTypes.MOVE); }, [openStep]); + const handleDuplicateClick = useCallback(() => { + onDuplicate(); + onClose(); + }, [onDuplicate, onClose]); + const handleDeleteClick = useCallback(() => { openStep(StepTypes.DELETE); }, [openStep]); @@ -207,6 +213,11 @@ const ActionsStep = React.memo( context: 'title', })} + + {t('action.duplicateCard', { + context: 'title', + })} + {t('action.deleteCard', { context: 'title', @@ -232,6 +243,7 @@ ActionsStep.propTypes = { onUpdate: PropTypes.func.isRequired, onMove: PropTypes.func.isRequired, onTransfer: PropTypes.func.isRequired, + onDuplicate: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, onUserAdd: PropTypes.func.isRequired, onUserRemove: PropTypes.func.isRequired, diff --git a/client/src/components/Card/Card.jsx b/client/src/components/Card/Card.jsx index 37521a0..4adddbc 100755 --- a/client/src/components/Card/Card.jsx +++ b/client/src/components/Card/Card.jsx @@ -41,6 +41,7 @@ const Card = React.memo( onUpdate, onMove, onTransfer, + onDuplicate, onDelete, onUserAdd, onUserRemove, @@ -185,6 +186,7 @@ const Card = React.memo( onUpdate={onUpdate} onMove={onMove} onTransfer={onTransfer} + onDuplicate={onDuplicate} onDelete={onDelete} onUserAdd={onUserAdd} onUserRemove={onUserRemove} @@ -238,6 +240,7 @@ Card.propTypes = { onUpdate: PropTypes.func.isRequired, onMove: PropTypes.func.isRequired, onTransfer: PropTypes.func.isRequired, + onDuplicate: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, onUserAdd: PropTypes.func.isRequired, onUserRemove: PropTypes.func.isRequired, diff --git a/client/src/components/Card/Tasks.jsx b/client/src/components/Card/Tasks.jsx index d76340f..c530474 100644 --- a/client/src/components/Card/Tasks.jsx +++ b/client/src/components/Card/Tasks.jsx @@ -4,6 +4,8 @@ import classNames from 'classnames'; import { Progress } from 'semantic-ui-react'; import { useToggle } from '../../lib/hooks'; +import Linkify from '../Linkify'; + import styles from './Tasks.module.scss'; const Tasks = React.memo(({ items }) => { @@ -48,7 +50,7 @@ const Tasks = React.memo(({ items }) => { key={item.id} className={classNames(styles.task, item.isCompleted && styles.taskCompleted)} > - {item.name} + {item.name} ))} diff --git a/client/src/components/Card/Tasks.module.scss b/client/src/components/Card/Tasks.module.scss index 508ac89..8f8a7ec 100644 --- a/client/src/components/Card/Tasks.module.scss +++ b/client/src/components/Card/Tasks.module.scss @@ -55,8 +55,10 @@ display: block; font-size: 12px; line-height: 14px; + overflow: hidden; padding-bottom: 6px; padding-left: 14px; + text-overflow: ellipsis; &:before { content: "–"; diff --git a/client/src/components/CardModal/AttachmentAddZone/AttachmentAddZone.jsx b/client/src/components/CardModal/AttachmentAddZone/AttachmentAddZone.jsx index 3236d92..66a0e89 100644 --- a/client/src/components/CardModal/AttachmentAddZone/AttachmentAddZone.jsx +++ b/client/src/components/CardModal/AttachmentAddZone/AttachmentAddZone.jsx @@ -94,7 +94,7 @@ const AttachmentAddZone = React.memo(({ children, onCreate }) => { return ( <> {/* eslint-disable-next-line react/jsx-props-no-spreading */} -
+
{isDragActive &&
{t('common.dropFileToUpload')}
} {children} {/* eslint-disable-next-line react/jsx-props-no-spreading */} diff --git a/client/src/components/CardModal/AttachmentAddZone/AttachmentAddZone.module.scss b/client/src/components/CardModal/AttachmentAddZone/AttachmentAddZone.module.scss index b0489dd..782f487 100644 --- a/client/src/components/CardModal/AttachmentAddZone/AttachmentAddZone.module.scss +++ b/client/src/components/CardModal/AttachmentAddZone/AttachmentAddZone.module.scss @@ -12,8 +12,4 @@ width: 100%; z-index: 2001; } - - .wrapper { - overflow: hidden; - } } diff --git a/client/src/components/CardModal/CardModal.jsx b/client/src/components/CardModal/CardModal.jsx index 76fa5aa..ff77aa9 100755 --- a/client/src/components/CardModal/CardModal.jsx +++ b/client/src/components/CardModal/CardModal.jsx @@ -55,6 +55,7 @@ const CardModal = React.memo( onUpdate, onMove, onTransfer, + onDuplicate, onDelete, onUserAdd, onUserRemove, @@ -140,6 +141,11 @@ const CardModal = React.memo( }); }, [isSubscribed, onUpdate]); + const handleDuplicateClick = useCallback(() => { + onDuplicate(); + onClose(); + }, [onDuplicate, onClose]); + const handleGalleryOpen = useCallback(() => { isGalleryOpened.current = true; }, []); @@ -496,6 +502,10 @@ const CardModal = React.memo( {t('action.move')} + - {name} + {name} {isPersisted && canEdit && ( diff --git a/client/src/components/Header/NotificationsStep.module.scss b/client/src/components/Header/NotificationsStep.module.scss index cdad65b..a86a0e9 100644 --- a/client/src/components/Header/NotificationsStep.module.scss +++ b/client/src/components/Header/NotificationsStep.module.scss @@ -49,7 +49,6 @@ max-height: 60vh; overflow-x: hidden; overflow-y: auto; - scrollbar-width: thin; &::-webkit-scrollbar { width: 5px; diff --git a/client/src/components/LabelsStep/LabelsStep.module.scss b/client/src/components/LabelsStep/LabelsStep.module.scss index c5ec023..1be274c 100644 --- a/client/src/components/LabelsStep/LabelsStep.module.scss +++ b/client/src/components/LabelsStep/LabelsStep.module.scss @@ -25,7 +25,6 @@ max-height: 60vh; overflow-x: hidden; overflow-y: auto; - scrollbar-width: thin; &::-webkit-scrollbar { width: 5px; diff --git a/client/src/components/Linkify.jsx b/client/src/components/Linkify.jsx new file mode 100644 index 0000000..71b01fd --- /dev/null +++ b/client/src/components/Linkify.jsx @@ -0,0 +1,68 @@ +import React, { useCallback } from 'react'; +import PropTypes from 'prop-types'; +import LinkifyReact from 'linkify-react'; + +import history from '../history'; + +const Linkify = React.memo(({ children, linkStopPropagation, ...props }) => { + const handleLinkClick = useCallback( + (event) => { + if (linkStopPropagation) { + event.stopPropagation(); + } + + if (!event.target.getAttribute('target')) { + event.preventDefault(); + history.push(event.target.href); + } + }, + [linkStopPropagation], + ); + + const linkRenderer = useCallback( + ({ attributes: { href, ...linkProps }, content }) => { + let url; + try { + url = new URL(href, window.location); + } catch (error) {} // eslint-disable-line no-empty + + const isSameSite = !!url && url.origin === window.location.origin; + + return ( + + {isSameSite ? url.pathname : content} + + ); + }, + [handleLinkClick], + ); + + return ( + + {children} + + ); +}); + +Linkify.propTypes = { + children: PropTypes.string.isRequired, + linkStopPropagation: PropTypes.bool, +}; + +Linkify.defaultProps = { + linkStopPropagation: false, +}; + +export default Linkify; diff --git a/client/src/components/List/List.module.scss b/client/src/components/List/List.module.scss index 7b47c89..b1a7096 100644 --- a/client/src/components/List/List.module.scss +++ b/client/src/components/List/List.module.scss @@ -43,7 +43,6 @@ max-height: calc(100vh - 268px); overflow-x: hidden; overflow-y: auto; - scrollbar-width: thin; width: 290px; &:hover { diff --git a/client/src/components/Login/Login.jsx b/client/src/components/Login/Login.jsx index 84e6a9d..6f547ee 100755 --- a/client/src/components/Login/Login.jsx +++ b/client/src/components/Login/Login.jsx @@ -68,6 +68,7 @@ const Login = React.memo( isSubmittingUsingOidc, error, withOidc, + isOidcEnforced, onAuthenticate, onAuthenticateUsingOidc, onMessageDismiss, @@ -107,8 +108,10 @@ const Login = React.memo( }, [onAuthenticate, data]); useEffect(() => { - emailOrUsernameField.current.focus(); - }, []); + if (!isOidcEnforced) { + emailOrUsernameField.current.focus(); + } + }, [isOidcEnforced]); useEffect(() => { if (wasSubmitting && !isSubmitting && error) { @@ -159,51 +162,57 @@ const Login = React.memo( onDismiss={onMessageDismiss} /> )} -
-
-
{t('common.emailOrUsername')}
- -
-
-
{t('common.password')}
- +
+
{t('common.emailOrUsername')}
+ +
+
+
{t('common.password')}
+ +
+ -
- - + + )} {withOidc && ( + /> )}
@@ -242,6 +251,7 @@ Login.propTypes = { isSubmittingUsingOidc: PropTypes.bool.isRequired, error: PropTypes.object, // eslint-disable-line react/forbid-prop-types withOidc: PropTypes.bool.isRequired, + isOidcEnforced: PropTypes.bool.isRequired, onAuthenticate: PropTypes.func.isRequired, onAuthenticateUsingOidc: PropTypes.func.isRequired, onMessageDismiss: PropTypes.func.isRequired, diff --git a/client/src/components/Memberships/AddStep/AddStep.module.scss b/client/src/components/Memberships/AddStep/AddStep.module.scss index 95be8d0..917142f 100644 --- a/client/src/components/Memberships/AddStep/AddStep.module.scss +++ b/client/src/components/Memberships/AddStep/AddStep.module.scss @@ -4,7 +4,6 @@ max-height: 60vh; overflow-x: hidden; overflow-y: auto; - scrollbar-width: thin; &::-webkit-scrollbar { width: 5px; diff --git a/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx b/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx index 7333cf5..eed1326 100644 --- a/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx +++ b/client/src/components/UserSettingsModal/AccountPane/AccountPane.jsx @@ -24,6 +24,7 @@ const AccountPane = React.memo( organization, language, isLocked, + isUsernameLocked, isAvatarUpdating, usernameUpdateForm, emailUpdateForm, @@ -104,7 +105,7 @@ const AccountPane = React.memo( value={language || 'auto'} onChange={handleLanguageChange} /> - {!isLocked && ( + {(!isLocked || !isUsernameLocked) && ( <>
@@ -113,56 +114,62 @@ const AccountPane = React.memo( })}
-
- - - -
-
- - - -
-
- - - -
+ {!isUsernameLocked && ( +
+ + + +
+ )} + {!isLocked && ( + <> +
+ + + +
+
+ + + +
+ + )} )} @@ -179,6 +186,7 @@ AccountPane.propTypes = { organization: PropTypes.string, language: PropTypes.string, isLocked: PropTypes.bool.isRequired, + isUsernameLocked: PropTypes.bool.isRequired, isAvatarUpdating: PropTypes.bool.isRequired, /* eslint-disable react/forbid-prop-types */ usernameUpdateForm: PropTypes.object.isRequired, diff --git a/client/src/components/UserSettingsModal/UserSettingsModal.jsx b/client/src/components/UserSettingsModal/UserSettingsModal.jsx index fc8ac9c..8767ac4 100644 --- a/client/src/components/UserSettingsModal/UserSettingsModal.jsx +++ b/client/src/components/UserSettingsModal/UserSettingsModal.jsx @@ -18,6 +18,7 @@ const UserSettingsModal = React.memo( language, subscribeToOwnCards, isLocked, + isUsernameLocked, isAvatarUpdating, usernameUpdateForm, emailUpdateForm, @@ -50,6 +51,7 @@ const UserSettingsModal = React.memo( organization={organization} language={language} isLocked={isLocked} + isUsernameLocked={isUsernameLocked} isAvatarUpdating={isAvatarUpdating} usernameUpdateForm={usernameUpdateForm} emailUpdateForm={emailUpdateForm} @@ -108,6 +110,7 @@ UserSettingsModal.propTypes = { language: PropTypes.string, subscribeToOwnCards: PropTypes.bool.isRequired, isLocked: PropTypes.bool.isRequired, + isUsernameLocked: PropTypes.bool.isRequired, isAvatarUpdating: PropTypes.bool.isRequired, /* eslint-disable react/forbid-prop-types */ usernameUpdateForm: PropTypes.object.isRequired, diff --git a/client/src/components/UsersModal/Item/ActionsStep.jsx b/client/src/components/UsersModal/Item/ActionsStep.jsx index f8a2959..7b0eaed 100644 --- a/client/src/components/UsersModal/Item/ActionsStep.jsx +++ b/client/src/components/UsersModal/Item/ActionsStep.jsx @@ -136,13 +136,15 @@ const ActionsStep = React.memo( context: 'title', })}
+ {!user.isUsernameLocked && ( + + {t('action.editUsername', { + context: 'title', + })} + + )} {!user.isLocked && ( <> - - {t('action.editUsername', { - context: 'title', - })} - {t('action.editEmail', { context: 'title', diff --git a/client/src/components/UsersModal/Item/Item.jsx b/client/src/components/UsersModal/Item/Item.jsx index 80db356..ed68da3 100755 --- a/client/src/components/UsersModal/Item/Item.jsx +++ b/client/src/components/UsersModal/Item/Item.jsx @@ -19,6 +19,7 @@ const Item = React.memo( isAdmin, isLocked, isRoleLocked, + isUsernameLocked, isDeletionLocked, emailUpdateForm, passwordUpdateForm, @@ -61,6 +62,7 @@ const Item = React.memo( phone, isAdmin, isLocked, + isUsernameLocked, isDeletionLocked, emailUpdateForm, passwordUpdateForm, @@ -95,6 +97,7 @@ Item.propTypes = { isAdmin: PropTypes.bool.isRequired, isLocked: PropTypes.bool.isRequired, isRoleLocked: PropTypes.bool.isRequired, + isUsernameLocked: PropTypes.bool.isRequired, isDeletionLocked: PropTypes.bool.isRequired, /* eslint-disable react/forbid-prop-types */ emailUpdateForm: PropTypes.object.isRequired, diff --git a/client/src/components/UsersModal/UsersModal.jsx b/client/src/components/UsersModal/UsersModal.jsx index 0b55778..68f0f82 100755 --- a/client/src/components/UsersModal/UsersModal.jsx +++ b/client/src/components/UsersModal/UsersModal.jsx @@ -10,6 +10,7 @@ import Item from './Item'; const UsersModal = React.memo( ({ items, + canAdd, onUpdate, onUsernameUpdate, onUsernameUpdateMessageDismiss, @@ -112,6 +113,7 @@ const UsersModal = React.memo( isAdmin={item.isAdmin} isLocked={item.isLocked} isRoleLocked={item.isRoleLocked} + isUsernameLocked={item.isUsernameLocked} isDeletionLocked={item.isDeletionLocked} emailUpdateForm={item.emailUpdateForm} passwordUpdateForm={item.passwordUpdateForm} @@ -129,11 +131,13 @@ const UsersModal = React.memo( - - -