Repository: inovex/scrumlr.io
Pull request #1658 (2022-05-10 13:03:26)

Naming of password protection settings
## Changelog

- Entered passwords are saved + the board is protected upon focus loss or when pressing "Enter"
- A toast is shown and the password is copied to the clipboard when saving
- Removing the password via button makes the board public
- When a password was set and the settings panel was closed, the already-set-password cannot be viewed again when re-opening the settings --> password input and "Show Password"-button are disabled

New:
- Added password edit button
- A toast is shown when a protected board is made public

TBD:
- [x] Prevent save + protect in edit mode when the "Show Password" / Visibility button is toggled during editing
- [x] Fix Manual Approval mode


## Checklist

- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] The light- and dark-theme are both supported and tested
- [x] The design was implemented and is responsive for all devices and screen sizes
- [x] The application was tested in the most commonly used browsers (e.g. Chrome, Firefox, Safari)

Resaki1: Wenn man ein Passwort gesetzt hat, kann man es nicht mehr ändern. Man muss dann das Passwort ganz entfernen und ein neues setzten. Ist das gewollt?
Resaki1: Nur der Admin, der das Passwort gesetzt hat, kann es sich in den Settings anschauen. Absicht?
timengel: > Wenn man ein Passwort gesetzt hat, kann man es nicht mehr ändern. Man muss dann das Passwort ganz entfernen und ein neues setzten. Ist das gewollt?

Ja, leider. Das war vorher auch schon so und liegt u.a. daran dass wir das PW ja nicht im Klartext verwalten. Und clientseitig speichern ist 1. kein good practice und 2. macht Probleme bei Wiederaufruf des Boards nachdem es geschlossen wurde. Habe das mit Johann abgesprochen.
Resaki1: > > Wenn man ein Passwort gesetzt hat, kann man es nicht mehr ändern. Man muss dann das Passwort ganz entfernen und ein neues setzten. Ist das gewollt?
> 
> Ja, leider. Das war vorher auch schon so und liegt u.a. daran dass wir das PW ja nicht im Klartext verwalten. Und clientseitig speichern ist 1. kein good practice und 2. macht Probleme bei Wiederaufruf des Boards nachdem es geschlossen wurde. Habe das mit Johann abgesprochen.

Ok, dann passt das für den PR. Aber sollten wir vielleicht trotzdem beheben. Müssen das PW ja dafür nicht speichern sondern dann einfach überschreiben
timengel: > > > Wenn man ein Passwort gesetzt hat, kann man es nicht mehr ändern. Man muss dann das Passwort ganz entfernen und ein neues setzten. Ist das gewollt?
> > 
> > 
> > Ja, leider. Das war vorher auch schon so und liegt u.a. daran dass wir das PW ja nicht im Klartext verwalten. Und clientseitig speichern ist 1. kein good practice und 2. macht Probleme bei Wiederaufruf des Boards nachdem es geschlossen wurde. Habe das mit Johann abgesprochen.
> 
> Ok, dann passt das für den PR. Aber sollten wir vielleicht trotzdem beheben. Müssen das PW ja dafür nicht speichern sondern dann einfach überschreiben

Ja guter Punkt, dann spart man sich einen Klick und den Request für die Umstellung von Protected -> Public -> Protected.  Ich setze das noch um.
brandstetterm: Also, on a board with manual approval the access policy is shown as open
<img width="419" alt="Screenshot 2022-05-18 at 13 25 28" src="https://user-images.githubusercontent.com/36969812/169028151-ce04a610-8ed2-4b56-a0f5-8d5fc1c9f8b5.png">
 
brandstetterm: An info toast is displayed only for the change from Protected to Public ("The board is public now!") but not for Public to Protected (e.g. "The board is protected now!")
brandstetterm: Also, the toasts "The board is public now!" and "Password copied!" are displayed even when the API request to change the access policy fails
brandstetterm: Also, if you're on a board with manual approval, it seems like you cannot change the access policy to public. I get a 500 internal server error as response to the request
timengel: > An info toast is displayed only for the change from Protected to Public ("The board is public now!") but not for Public to Protected (e.g. "The board is protected now!")

When submitting a password a Toast "Password copied!" is shown. I didnt want to show two Toasts at once and thought this was indication enough. How about changing it to "Board protected and password copied!" / "Board geschützt und Passwort kopiert!" @brandstetterm?

Patch:
diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json
index 126f30cc..379c1dfd 100644
--- a/public/locales/de/translation.json
+++ b/public/locales/de/translation.json
@@ -190,7 +190,9 @@
     "timerStarted": "Ein Alarm wurde gesetzt!",
     "timerEnded": "Der Alarm ist beendet!",
     "timerCancelled": "Der Alarm wurde abgebrochen!",
-    "hiddenColumnsVisible": "Ausgeblendete Spalten werden dir nun angezeigt!"
+    "hiddenColumnsVisible": "Ausgeblendete Spalten werden dir nun angezeigt!",
+    "passwordCopied": "Board geschützt und Passwort kopiert!",
+    "boardMadePublic": "Das Board ist nun öffentlich!"
   },
   "Language": {
     "german": "Deutsch",
@@ -230,13 +232,13 @@
     "BoardName": "Sitzungsname",
     "AccessPolicy": "Zugangsrichtlinien",
     "Password": "Passwort",
-    "SetAccessPolicyOpen": "Sitzung veröffentlichen",
-    "SetAccessPolicyPasswordProtected": "Sitzung passwort sichern",
+    "SetAccessPolicyOpen": "Passwortschutz entfernen",
+    "SecurePasswordHint": "Bitte sicheres Passwort eingeben",
     "DeleteBoard": "Sitzung löschen",
     "ShowAuthorOption": "Kartenautoren anzeigen",
     "ShowHiddenColumnsOption": "Ausgeblendete Spalten für mich anzeigen",
     "ShowOtherUsersNotesOption": "Karten anderer Teilnehmer anzeigen",
-    "generatePassword": "Erstelle zufälliges Passwort",
+    "generatePassword": "Passwort erstellen und absichern",
     "save": "speichern",
     "edit": "bearbeiten"
   },
diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json
index c02f6b72..6beaad94 100644
--- a/public/locales/en/translation.json
+++ b/public/locales/en/translation.json
@@ -34,7 +34,7 @@
   "AccessPolicySelection": {
     "title": "Access policy",
     "publicTitle": "Open",
-    "byPassphraseTitle": "Password protected",
+    "byPassphraseTitle": "Protected",
     "manualVerificationTitle": "Manual approval",
     "passphrase": "Passphrase",
     "public": "Access by URL without further verification. The URLs are hard to guess, so this should be sufficient for most sessions.",
@@ -189,7 +189,9 @@
     "timerStarted": "Timer started!",
     "timerEnded": "Timer ended!",
     "timerCancelled": "Timer cancelled!",
-    "hiddenColumnsVisible": "Hidden columns are now visible to you!"
+    "hiddenColumnsVisible": "Hidden columns are now visible to you!",
+    "passwordCopied": "Board protected and password copied!",
+    "boardMadePublic": "The board is now public!"
   },
   "Language": {
     "german": "German",
@@ -229,13 +231,13 @@
     "BoardName": "Board Name",
     "AccessPolicy": "Access Policy",
     "Password": "Password",
-    "SetAccessPolicyOpen": "Set to open",
-    "SetAccessPolicyPasswordProtected": "Set to password protected",
+    "SetAccessPolicyOpen": "Remove password protection",
+    "SecurePasswordHint": "Please enter a secure password",
     "DeleteBoard": "Delete Board",
     "ShowAuthorOption": "Show authors of cards",
     "ShowHiddenColumnsOption": "Show hidden columns for me",
     "ShowOtherUsersNotesOption": "Show cards of other users",
-    "generatePassword": "Generate random password",
+    "generatePassword": "Generate password and protect",
     "save": "save",
     "edit": "edit"
   },
diff --git a/src/components/SettingsDialog/BoardSettings/BoardSettings.scss b/src/components/SettingsDialog/BoardSettings/BoardSettings.scss
index 372aa022..44240c57 100644
--- a/src/components/SettingsDialog/BoardSettings/BoardSettings.scss
+++ b/src/components/SettingsDialog/BoardSettings/BoardSettings.scss
@@ -92,6 +92,7 @@
 
 .board-settings__policy-button span {
   cursor: inherit;
+  flex: 1;
 }
 
 .board-settings__policy-button_value {
@@ -106,6 +107,7 @@
   font-weight: bold;
   letter-spacing: 0.5px;
   font-size: 14px;
+  color: $color-placeholder;
 }
 
 @media screen and (max-width: 920px) {
@@ -122,8 +124,8 @@
 }
 
 .board-settings__policy-button_value svg {
-  width: 22px;
-  height: 22px;
+  width: $icon--medium;
+  height: $icon--medium;
   color: var(--accent-color);
 }
 
@@ -142,13 +144,38 @@
   display: flex;
   align-items: center;
   justify-content: center;
+  cursor: pointer;
 }
 
 .board-settings__password-button_value svg {
-  width: 30px;
-  height: 30px;
+  width: $icon--medium;
+  height: $icon--medium;
+}
+
+.board-settings__edit-password-button {
   color: var(--accent-color);
   cursor: pointer;
+  margin-left: 4px;
+}
+
+.board-settings__edit-password-button svg {
+  width: $icon--medium;
+  height: $icon--medium;
+}
+
+.board-settings__show-password-button--enabled {
+  color: var(--accent-color);
+}
+
+.board-settings__show-password-button--disabled {
+  color: $color-placeholder;
+  cursor: default;
+}
+
+[theme="dark"] {
+  .board-settings__show-password-button--disabled {
+    color: $color-placeholder-dark;
+  }
 }
 
 .board-settings__password-button_value-input {
@@ -172,10 +199,11 @@
   outline: none;
 }
 
-.board-settings__set-policy-button {
+.board-settings__password-management-button {
   display: flex;
   align-items: center;
   padding: 0 24px;
+  height: 30px;
   border: none;
   background-color: inherit;
   color: var(--accent-color);
@@ -183,40 +211,25 @@
   align-self: center;
 }
 
-.board-settings__set-policy-button svg {
-  width: 22px;
-  height: 22px;
+.board-settings__remove-protection-button svg {
+  width: $icon--medium;
+  height: $icon--medium;
   color: var(--accent-color);
 }
 
-.board-settings__set-policy-button span {
-  padding-left: 4px;
-  font-weight: bold;
-  font-size: 13px;
-  letter-spacing: 0.5px;
-}
-
-.board-settings__generate-password-button {
-  display: flex;
-  align-items: center;
-  padding: 0 24px;
-  border: none;
-  background-color: inherit;
+.board-settings__generate-password-button svg {
+  width: $icon--small;
+  height: $icon--small;
+  margin: 0 4px 2px 3px;
   color: var(--accent-color);
-  cursor: pointer;
-  align-self: center;
-  margin-top: 3px;
 }
 
-.board-settings__generate-password-button svg {
-  width: 15px;
-  height: 15px;
-  margin-right: 4px;
-  margin-left: 3px;
-  color: var(--accent-color);
+.board-settings__password-input-hint {
+  cursor: default;
+  align-self: center;
 }
 
-.board-settings__generate-password-button span {
+.board-settings__password-management-text {
   padding-left: 4px;
   font-weight: bold;
   font-size: 13px;
@@ -256,8 +269,8 @@
 
 .board-settings__delete-button svg {
   color: $color-retro-red;
-  width: 30px;
-  height: 25px;
+  width: $icon--medium;
+  height: $icon--medium;
 }
 
 .board-settings__confirmation-dialog {
diff --git a/src/components/SettingsDialog/BoardSettings/BoardSettings.tsx b/src/components/SettingsDialog/BoardSettings/BoardSettings.tsx
index 97b0c8a5..d9f5ba5c 100644
--- a/src/components/SettingsDialog/BoardSettings/BoardSettings.tsx
+++ b/src/components/SettingsDialog/BoardSettings/BoardSettings.tsx
@@ -1,20 +1,23 @@
 import classNames from "classnames";
 import {useTranslation} from "react-i18next";
-import {useRef, useState} from "react";
+import {useEffect, useRef, useState} from "react";
 import {Actions} from "store/action";
 import store, {useAppSelector} from "store";
 import {ReactComponent as SetPolicyIcon} from "assets/icon-lock.svg";
 import {ReactComponent as DeleteIcon} from "assets/icon-delete.svg";
-import {ReactComponent as VisableIcon} from "assets/icon-visible.svg";
+import {ReactComponent as VisibleIcon} from "assets/icon-visible.svg";
 import {ReactComponent as HiddenIcon} from "assets/icon-hidden.svg";
 import {ReactComponent as RefreshIcon} from "assets/icon-refresh.svg";
+import {ReactComponent as EditIcon} from "assets/icon-edit.svg";
+import {ReactComponent as CheckIcon} from "assets/icon-check.svg";
+import {DEFAULT_BOARD_NAME, MIN_PASSWORD_LENGTH, PLACEHOLDER_PASSWORD} from "constants/misc";
+import {Toast} from "utils/Toast";
 import {generateRandomString} from "utils/random";
 import {ConfirmationDialog} from "components/ConfirmationDialog";
 import {SettingsButton} from "../Components/SettingsButton";
 import {SettingsToggle} from "../Components/SettingsToggle";
 import "./BoardSettings.scss";
 import "../SettingsDialog.scss";
-import {DEFAULT_BOARD_NAME} from "../../../constants/misc";
 
 export const BoardSettings = () => {
   const {t} = useTranslation();
@@ -29,20 +32,146 @@ export const BoardSettings = () => {
   const [password, setPassword] = useState<string>("");
   const [showPassword, setShowPassword] = useState<boolean>(false);
   const [showConfirmationDialog, setShowConfirmationDialog] = useState<boolean>(false);
+  const [isProtectedOnInitialSettingsOpen, setIsProtectedOnInitialSettingsOpen] = useState(state.board.accessPolicy === "BY_PASSPHRASE");
+  const [isProtected, setIsProtected] = useState(state.board.accessPolicy === "BY_PASSPHRASE");
+  const [activeEditMode, setActiveEditMode] = useState(false);
 
   const boardInputRef = useRef<HTMLInputElement>(null);
   const passwordInputRef = useRef<HTMLInputElement>(null);
 
-  const handleSetPolicy = (AccessType: string) => {
-    if (AccessType === "ByPassphrase") {
-      if (password.length > 0) {
-        store.dispatch(Actions.editBoard({accessPolicy: "BY_PASSPHRASE", passphrase: password}));
+  const isByInvite = state.board.accessPolicy === "BY_INVITE";
+
+  const handleSetPassword = (newPassword: string) => {
+    if (newPassword.length >= MIN_PASSWORD_LENGTH) {
+      if (!isProtected || activeEditMode) {
+        store.dispatch(Actions.editBoard({accessPolicy: "BY_PASSPHRASE", passphrase: newPassword}));
+        navigator.clipboard.writeText(newPassword).then(() =>
+          Toast.success(
+            <div>
+              <div>{t("Toast.passwordCopied")}</div>
+            </div>,
+            1500
+          )
+        );
+        if (activeEditMode) {
+          setIsProtectedOnInitialSettingsOpen(false);
+          setActiveEditMode(false);
+        }
+        if (!isProtected) {
+          setIsProtected(true);
+        }
+      }
+    } else if (isProtected || isProtectedOnInitialSettingsOpen) {
+      if (!activeEditMode) {
+        store.dispatch(Actions.editBoard({accessPolicy: "PUBLIC"}));
+        setIsProtectedOnInitialSettingsOpen(false);
+        setIsProtected(false);
+        Toast.info(
+          <div>
+            <div>{t("Toast.boardMadePublic")}</div>
+          </div>
+        );
       } else {
-        passwordInputRef.current?.focus();
+        setActiveEditMode(false);
+        if (passwordInputRef.current) passwordInputRef.current.placeholder = PLACEHOLDER_PASSWORD;
+      }
+    }
+  };
+
+  useEffect(() => {
+    if (activeEditMode) {
+      passwordInputRef.current?.focus();
+      passwordInputRef.current?.select();
+    }
+  }, [activeEditMode]);
+
+  const getPasswordManagementButton = () => {
+    if (!activeEditMode) {
+      if (isProtected) {
+        return (
+          <button
+            className="board-settings__password-management-button board-settings__remove-protection-button button--centered"
+            onClick={() => {
+              setPassword("");
+              handleSetPassword("");
+            }}
+          >
+            <SetPolicyIcon />
+            <span className="board-settings__password-management-text">{t("BoardSettings.SetAccessPolicyOpen")}</span>
+          </button>
+        );
+      }
+      if (!password) {
+        return (
+          <button
+            className="board-settings__password-management-button board-settings__generate-password-button"
+            onClick={() => {
+              const pw = generateRandomString();
+              setPassword(pw);
+              handleSetPassword(pw);
+            }}
+          >
+            <RefreshIcon />
+            <span className="board-settings__password-management-text">{t("BoardSettings.generatePassword")}</span>
+          </button>
+        );
       }
-    } else {
-      store.dispatch(Actions.editBoard({accessPolicy: "PUBLIC"}));
     }
+    return (
+      <span className="board-settings__password-management-button board-settings__password-input-hint board-settings__password-management-text">
+        {t("BoardSettings.SecurePasswordHint")}
+      </span>
+    );
+  };
+
+  const getPasswordVisibilityButton = () =>
+    showPassword ? (
+      <VisibleIcon className="board-settings__show-password-button--enabled" onClick={() => setShowPassword(false)} />
+    ) : (
+      <HiddenIcon
+        className={!isProtectedOnInitialSettingsOpen || activeEditMode ? "board-settings__show-password-button--enabled" : "board-settings__show-password-button--disabled"}
+        onClick={() => !isProtectedOnInitialSettingsOpen && setShowPassword(true)}
+      />
+    );
+
+  const submitPasswordButton = (
+    <CheckIcon
+      className="board-settings__edit-password-button"
+      onClick={() => {
+        setActiveEditMode(false);
+        handleSetPassword(passwordInputRef.current?.value ?? password ?? "");
+      }}
+    />
+  );
+
+  const getPasswordStatusButton = () =>
+    activeEditMode ? (
+      submitPasswordButton
+    ) : (
+      <EditIcon
+        className="board-settings__edit-password-button"
+        onClick={() => {
+          setActiveEditMode(true);
+        }}
+      />
+    );
+
+  const getAccessPolicyTitle = () => {
+    if (isByInvite)
+      return (
+        <>
+          <span>{t("AccessPolicySelection.manualVerificationTitle")}</span>
+          <SetPolicyIcon />
+        </>
+      );
+    return !isProtected ? (
+      <span>{t("AccessPolicySelection.publicTitle")}</span>
+    ) : (
+      <>
+        <span>{t("AccessPolicySelection.byPassphraseTitle")}</span>
+        <SetPolicyIcon />
+      </>
+    );
   };
 
   return (
@@ -63,6 +192,7 @@ export const BoardSettings = () => {
               className="board-settings__board-name-button_input"
               value={boardName}
               placeholder={DEFAULT_BOARD_NAME}
+              autoComplete="off"
               onChange={(e) => setBoardName(e.target.value)}
               onKeyDown={(e) => e.key === "Enter" && boardName && store.dispatch(Actions.editBoard({name: boardName}))}
               onBlur={(e) => {
@@ -82,49 +212,48 @@ export const BoardSettings = () => {
           <div className="board-settings__group-and-button">
             <div className="settings-dialog__group">
               <SettingsButton className="board-settings__policy-button" label={t("BoardSettings.AccessPolicy")} disabled>
-                <div className="board-settings__policy-button_value">
-                  <span>{state.board.accessPolicy === "PUBLIC" ? t("AccessPolicySelection.publicTitle") : t("AccessPolicySelection.byPassphraseTitle")}</span>
-                  <SetPolicyIcon />
-                </div>
+                <div className="board-settings__policy-button_value">{getAccessPolicyTitle()}</div>
               </SettingsButton>
 
-              {state.currentUserIsModerator && state.board.accessPolicy === "PUBLIC" && (
+              {!isByInvite && state.currentUserIsModerator && (
                 <>
-                  <hr className="settings-dialog__seperator" />
-                  <SettingsButton className="board-settings__password-button" label={t("BoardSettings.Password")} onClick={() => passwordInputRef.current?.focus()}>
+                  <hr className="settings-dialog__separator" />
+                  <SettingsButton
+                    id="password-input-section"
+                    className="board-settings__password-button"
+                    label={t("BoardSettings.Password")}
+                    onClick={() => passwordInputRef.current?.focus()}
+                  >
                     <div className="board-settings__password-button_value">
                       <input
                         ref={passwordInputRef}
                         type={showPassword ? "text" : "password"}
                         className="board-settings__password-button_value-input"
                         value={password}
-                        onChange={(e) => setPassword(e.target.value)}
+                        readOnly={isProtected && !activeEditMode}
+                        disabled={isProtectedOnInitialSettingsOpen && !activeEditMode}
+                        placeholder={!isProtected || (isProtected && activeEditMode) ? "" : PLACEHOLDER_PASSWORD}
+                        autoComplete="off"
+                        onChange={(e) => {
+                          setPassword(e.target.value);
+                        }}
+                        onKeyDown={(e) => e.key === "Enter" && handleSetPassword(passwordInputRef.current?.value ?? password ?? "")}
+                        onBlur={(e) => {
+                          if (e.relatedTarget?.id !== "password-input-section") {
+                            handleSetPassword(passwordInputRef.current?.value ?? password ?? "");
+                          }
+                        }}
                       />
-                      {showPassword ? <VisableIcon onClick={() => setShowPassword(!showPassword)} /> : <HiddenIcon onClick={() => setShowPassword(!showPassword)} />}
+                      {!isProtected && !activeEditMode && password !== "" && submitPasswordButton}
+                      {isProtected && getPasswordVisibilityButton()}
+                      {isProtected && getPasswordStatusButton()}
                     </div>
                   </SettingsButton>
                 </>
               )}
             </div>
 
-            {state.currentUserIsModerator &&
-              (state.board.accessPolicy === "PUBLIC" ? (
-                <>
-                  <button className="board-settings__generate-password-button" onClick={() => setPassword(generateRandomString())}>
-                    <RefreshIcon />
-                    <span>{t("BoardSettings.generatePassword")}</span>
-                  </button>
-                  <button className="board-settings__set-policy-button" onClick={() => handleSetPolicy("ByPassphrase")}>
-                    <SetPolicyIcon />
-                    <span>{t("BoardSettings.SetAccessPolicyPasswordProtected")}</span>
-                  </button>
-                </>
-              ) : (
-                <button className="board-settings__set-policy-button button--centered" onClick={() => handleSetPolicy("Public")}>
-                  <SetPolicyIcon />
-                  <span>{t("BoardSettings.SetAccessPolicyOpen")}</span>
-                </button>
-              ))}
+            {!isByInvite && state.currentUserIsModerator && getPasswordManagementButton()}
           </div>
 
           {state.currentUserIsModerator && (
@@ -140,7 +269,7 @@ export const BoardSettings = () => {
                 >
                   <SettingsToggle active={state.board.showAuthors} />
                 </SettingsButton>
-                <hr className="settings-dialog__seperator" />
+                <hr className="settings-dialog__separator" />
                 <SettingsButton
                   data-testid="notes"
                   className="board-settings__show-notes-button"
@@ -149,7 +278,7 @@ export const BoardSettings = () => {
                 >
                   <SettingsToggle active={state.board.showNotesOfOtherUsers} />
                 </SettingsButton>
-                <hr className="settings-dialog__seperator" />
+                <hr className="settings-dialog__separator" />
                 <SettingsButton
                   data-testid="columns"
                   className="board-settings__show-columns-button"
diff --git a/src/components/SettingsDialog/ExportBoard/ExportBoard.tsx b/src/components/SettingsDialog/ExportBoard/ExportBoard.tsx
index d35c561f..acd8f053 100644
--- a/src/components/SettingsDialog/ExportBoard/ExportBoard.tsx
+++ b/src/components/SettingsDialog/ExportBoard/ExportBoard.tsx
@@ -30,7 +30,7 @@ export const ExportBoard: VFC = () => {
           }}
           data-testid="export-json"
         />
-        <hr className="settings-dialog__seperator" />
+        <hr className="settings-dialog__separator" />
         <SettingsButton
           label={t("ExportBoardOption.exportAsCSV")}
           icon={ExportCSV}
diff --git a/src/components/SettingsDialog/Participants/Participants.tsx b/src/components/SettingsDialog/Participants/Participants.tsx
index 0be1a8f8..216c5ff9 100644
--- a/src/components/SettingsDialog/Participants/Participants.tsx
+++ b/src/components/SettingsDialog/Participants/Participants.tsx
@@ -35,7 +35,7 @@ export const Participants = () => {
               </div>
               {(state.me.role === "MODERATOR" || state.me.role === "OWNER") && <SettingsToggle active={state.me.role === "MODERATOR" || state.me.role === "OWNER"} />}
             </SettingsButton>
-            {state.others.length > 0 && <hr className="settings-dialog__seperator" />}
+            {state.others.length > 0 && <hr className="settings-dialog__separator" />}
             {state.others.length > 0 &&
               state.others.map((participant, index) => (
                 <>
@@ -53,7 +53,7 @@ export const Participants = () => {
                     </div>
                     {(state.me.role === "MODERATOR" || state.me.role === "OWNER") && <SettingsToggle active={participant.role === "MODERATOR" || participant.role === "OWNER"} />}
                   </SettingsButton>
-                  {state.others[index + 1] && <hr className="settings-dialog__seperator" />}
+                  {state.others[index + 1] && <hr className="settings-dialog__separator" />}
                 </>
               ))}
           </div>
diff --git a/src/components/SettingsDialog/SettingsDialog.scss b/src/components/SettingsDialog/SettingsDialog.scss
index 5f574152..404b9cb9 100644
--- a/src/components/SettingsDialog/SettingsDialog.scss
+++ b/src/components/SettingsDialog/SettingsDialog.scss
@@ -58,7 +58,7 @@ $settings-dialog-container--bottom: 62px;
     display: block;
     width: 544px;
     height: 100%;
-    border-radius: 0px 8px 8px 0px;
+    border-radius: 0 8px 8px 0;
     padding-top: 0;
   }
 }
@@ -128,7 +128,7 @@ $settings-dialog-container--bottom: 62px;
     top: -22px;
     right: -22px;
     background-color: $color-white;
-    box-shadow: 0px 16px 32px #0057ff3d;
+    box-shadow: 0 16px 32px #0057ff3d;
   }
   [theme="dark"] .settings-dialog__close-button {
     box-shadow: 0 16px 32px 0 rgba(0, 0, 0, 0.2);
@@ -249,7 +249,7 @@ $settings-dialog-container--bottom: 62px;
   margin: 0;
 }
 
-.settings-dialog__seperator {
+.settings-dialog__separator {
   margin: 0;
   margin-left: 5%;
   width: 90%;
@@ -269,7 +269,7 @@ $settings-dialog-container--bottom: 62px;
     color: $color-white;
   }
 
-  .settings-dialog__seperator {
+  .settings-dialog__separator {
     border-top: 0.5px solid $color-dark-one;
   }
 
diff --git a/src/constants/misc.ts b/src/constants/misc.ts
index 416d2f16..7c0fcdc5 100644
--- a/src/constants/misc.ts
+++ b/src/constants/misc.ts
@@ -1,1 +1,3 @@
 export const DEFAULT_BOARD_NAME = "scrumlr.io";
+export const MIN_PASSWORD_LENGTH = 1;
+export const PLACEHOLDER_PASSWORD = "••••••••••";
diff --git a/src/constants/style.scss b/src/constants/style.scss
index 06f719a0..d1e86280 100644
--- a/src/constants/style.scss
+++ b/src/constants/style.scss
@@ -78,6 +78,11 @@ $column__min-width: 300px;
 $column__max-width: 960px;
 $column__border-width: 12px;
 
+// icons
+$icon--small: 16px;
+$icon--medium: 22px;
+$icon--large: 32px;
+
 // margin and padding
 $margin--small: 8px;
 $margin--default: 16px;
diff --git a/src/utils/Toast.tsx b/src/utils/Toast.tsx
index 445b33a2..41cae8c1 100644
--- a/src/utils/Toast.tsx
+++ b/src/utils/Toast.tsx
@@ -26,7 +26,17 @@ function error(content: ReactNode, autoClose: number | false = 3000) {
   toast.error(content, {...toastConfig, autoClose});
 }
 
+/**
+ * Display info message via toast.
+ *
+ * @param content Info message.
+ */
+function info(content: ReactNode, autoClose: number | false = 3000) {
+  toast.info(content, {...toastConfig, autoClose});
+}
+
 export const Toast = {
   success,
   error,
+  info,
 };


Test patch:
diff --git a/src/components/SettingsDialog/BoardSettings/__tests__/__snapshots__/BoardSettings.test.tsx.snap b/src/components/SettingsDialog/BoardSettings/__tests__/__snapshots__/BoardSettings.test.tsx.snap
index 33cb7310..8b932f69 100644
--- a/src/components/SettingsDialog/BoardSettings/__tests__/__snapshots__/BoardSettings.test.tsx.snap
+++ b/src/components/SettingsDialog/BoardSettings/__tests__/__snapshots__/BoardSettings.test.tsx.snap
@@ -29,6 +29,7 @@ exports[`BoardSettings should match snapshot 1`] = `
           BoardSettings.BoardName
         </span>
         <input
+          autocomplete="off"
           class="board-settings__board-name-button_input"
           placeholder="scrumlr.io"
           value="test-board-name"
@@ -55,16 +56,14 @@ exports[`BoardSettings should match snapshot 1`] = `
               <span>
                 AccessPolicySelection.publicTitle
               </span>
-              <svg>
-                icon-lock.svg
-              </svg>
             </div>
           </button>
           <hr
-            class="settings-dialog__seperator"
+            class="settings-dialog__separator"
           />
           <button
             class="settings-option-button board-settings__password-button"
+            id="password-input-section"
           >
             <span
               class="settings-option-button__label"
@@ -75,36 +74,27 @@ exports[`BoardSettings should match snapshot 1`] = `
               class="board-settings__password-button_value"
             >
               <input
+                autocomplete="off"
                 class="board-settings__password-button_value-input"
+                placeholder=""
                 type="password"
                 value=""
               />
-              <svg>
-                icon-hidden.svg
-              </svg>
             </div>
           </button>
         </div>
         <button
-          class="board-settings__generate-password-button"
+          class="board-settings__password-management-button board-settings__generate-password-button"
         >
           <svg>
             icon-refresh.svg
           </svg>
-          <span>
+          <span
+            class="board-settings__password-management-text"
+          >
             BoardSettings.generatePassword
           </span>
         </button>
-        <button
-          class="board-settings__set-policy-button"
-        >
-          <svg>
-            icon-lock.svg
-          </svg>
-          <span>
-            BoardSettings.SetAccessPolicyPasswordProtected
-          </span>
-        </button>
       </div>
       <div
         class="settings-dialog__group"
@@ -127,7 +117,7 @@ exports[`BoardSettings should match snapshot 1`] = `
           </div>
         </button>
         <hr
-          class="settings-dialog__seperator"
+          class="settings-dialog__separator"
         />
         <button
           class="settings-option-button board-settings__show-notes-button"
@@ -147,7 +137,7 @@ exports[`BoardSettings should match snapshot 1`] = `
           </div>
         </button>
         <hr
-          class="settings-dialog__seperator"
+          class="settings-dialog__separator"
         />
         <button
           class="settings-option-button board-settings__show-columns-button"
diff --git a/src/components/SettingsDialog/ExportBoard/__tests__/__snapshots__/ExportBoard.test.tsx.snap b/src/components/SettingsDialog/ExportBoard/__tests__/__snapshots__/ExportBoard.test.tsx.snap
index 3154a134..ae30373d 100644
--- a/src/components/SettingsDialog/ExportBoard/__tests__/__snapshots__/ExportBoard.test.tsx.snap
+++ b/src/components/SettingsDialog/ExportBoard/__tests__/__snapshots__/ExportBoard.test.tsx.snap
@@ -38,7 +38,7 @@ Object {
             </svg>
           </button>
           <hr
-            class="settings-dialog__seperator"
+            class="settings-dialog__separator"
           />
           <button
             class="settings-option-button export-board__button-reverse-order"
@@ -93,7 +93,7 @@ Object {
           </svg>
         </button>
         <hr
-          class="settings-dialog__seperator"
+          class="settings-dialog__separator"
         />
         <button
           class="settings-option-button export-board__button-reverse-order"
diff --git a/src/components/SettingsDialog/Participants/__tests__/__snapshots__/Participants.test.tsx.snap b/src/components/SettingsDialog/Participants/__tests__/__snapshots__/Participants.test.tsx.snap
index 299ddee2..95a05dbe 100644
--- a/src/components/SettingsDialog/Participants/__tests__/__snapshots__/Participants.test.tsx.snap
+++ b/src/components/SettingsDialog/Participants/__tests__/__snapshots__/Participants.test.tsx.snap
@@ -522,7 +522,7 @@ exports[`Participants should render filter and participants correctly 1`] = `
           </div>
         </button>
         <hr
-          class="settings-dialog__seperator"
+          class="settings-dialog__separator"
         />
         <button
           class="settings-option-button participants__user"
@@ -970,7 +970,7 @@ exports[`Participants should render filter and participants correctly 1`] = `
           </div>
         </button>
         <hr
-          class="settings-dialog__seperator"
+          class="settings-dialog__separator"
         />
         <button
           class="settings-option-button participants__user"