Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save Username in Local Storage only at Permission #228

Merged
merged 10 commits into from
Jan 15, 2024
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ module.exports = {
{
blankLine: "always",
prev: "*",
next: ["interface", "type", "function"],
next: ["interface", "type", "function", "export"],
},
],
"prettier/prettier": "warn",
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/common/components/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TextField } from "@mui/material";
import { StandardTextFieldProps } from "@mui/material/TextField/TextField";
import { ErrorHelperText } from "./ErrorHelperText";

interface TextInputProps extends StandardTextFieldProps {
export interface TextInputProps extends StandardTextFieldProps {
onSubmit: () => void;
errorHelperText?: string;
}
Expand Down
32 changes: 14 additions & 18 deletions packages/frontend/src/common/dialogs/JoinSessionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
useMediaQuery,
useTheme,
Expand All @@ -17,14 +16,13 @@ import { generateId } from "../utils/generateId";

import { useBackendAdapter } from "../adapter/backendAdapter";
import { useNamespace } from "../hooks/useNamespace";
import { TextInput } from "../components/TextInput";
import { useErrorContext } from "../context/ErrorContext";
import { LocalStorage } from "../utils/localStorage";
import { useLocalStorage } from "../hooks/useLocalStorage";
import { CallToActionButton } from "../components/buttons/CallToActionButton";
import { useDialog } from "../hooks/useDialog";
import { useRedirect } from "../hooks/useRedirect";
import { useRoomIdFromPath } from "../hooks/useRoomIdFromPath";
import UserNameInputField from "../../retro/components/dialogs/UsernameInputField";
import useLocalStorageName from "../../retro/hooks/useLocalStorageName";

interface JoinSessionDialogProps {
onAddToWaitingList: ({ userId, userName }: { userId: string; userName: string }) => void;
Expand All @@ -40,6 +38,9 @@ export function JoinSessionDialog({ onAddToWaitingList }: JoinSessionDialogProps
handleChange,
isValid,
} = useValidatedTextInput({ minLength: 1, maxLength: 40 });
const { isStorageAllowed, trySavingNameLocally, handleAllowanceChange } = useLocalStorageName({
setName,
});
const { redirectBackToHome, redirectToRoom } = useRedirect();
const { setError } = useErrorContext();
const { setRoomId } = useRoomContext();
Expand All @@ -50,10 +51,6 @@ export function JoinSessionDialog({ onAddToWaitingList }: JoinSessionDialogProps
const namespace = useNamespace();
const { roomIdExists } = useBackendAdapter();

useLocalStorage(() => {
setName(LocalStorage.getUserName());
});

function handleClose() {
setName("");
closeDialog();
Expand All @@ -79,7 +76,7 @@ export function JoinSessionDialog({ onAddToWaitingList }: JoinSessionDialogProps
};
setRoomId(roomId);
setUser(newUser);
LocalStorage.setUserName(name);
trySavingNameLocally(name);
onAddToWaitingList({ userId: newUser.id, userName: name });
redirectToRoom(roomId);
handleClose();
Expand All @@ -95,18 +92,17 @@ export function JoinSessionDialog({ onAddToWaitingList }: JoinSessionDialogProps
>
<DialogTitle id="join-poker-dialog-title">Join Session</DialogTitle>
<DialogContent>
<DialogContentText>Please provide your name for this session</DialogContentText>
<TextInput
<UserNameInputField
userName={name}
id="user-name"
onSubmit={handleSubmit}
required
autoFocus
fullWidth
value={name}
onChange={handleChange}
error={isError}
id="user-name"
label="Name"
type="text"
isStorageAllowed={isStorageAllowed}
onStorageAllowanceChange={(event) => {
handleAllowanceChange(event.target.checked);
}}
autoFocus
/>
</DialogContent>
<DialogActions>
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/common/dialogs/Participant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface ParticipantProps {
handleKickUser: (userId: string) => void;
handleTransferModeratorRole: (userId: string) => void;
}

export function Participant({
participant,
handleKickUser,
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/common/dialogs/WaitingUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface WaitingUserProps {
handleRejectJoinUser: (userId: string) => void;
handleAcceptJoinUser: (userId: string) => void;
}

export function WaitingUser({
waitingUser,
handleRejectJoinUser,
Expand Down
22 changes: 21 additions & 1 deletion packages/frontend/src/common/utils/localStorage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const userNameKey = "userName";
const usernameStorePermissionKey = "userAllowsNameStorage";

function setUserName(userName: string) {
localStorage.setItem(userNameKey, userName);
Expand All @@ -8,4 +9,23 @@ function getUserName() {
return localStorage.getItem(userNameKey) ?? "";
}

export const LocalStorage = { setUserName, getUserName };
function removeUserName() {
localStorage.removeItem(userNameKey);
}

function setNameStorePermission(permission: boolean) {
localStorage.setItem(usernameStorePermissionKey, String(permission));
}

function getNameStorePermission(): boolean {
const value = localStorage.getItem(usernameStorePermissionKey);
return value === "true";
}

export const LocalStorage = {
setUserName,
getUserName,
removeUserName,
setNameStorePermission,
getNameStorePermission,
};
1 change: 1 addition & 0 deletions packages/frontend/src/poker/context/PokerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const initialState: PokerState = {
};

export const PokerContext = React.createContext<PokerContextValues>(undefined!);

export function PokerContextProvider(props: PokerContextProviderProps) {
const [state, dispatch] = useReducer(pokerReducer, initialState);
const { user } = useUserContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface CircularProgressWithLabelProps {
maxValue: number;
currentValue: number;
}

export function CircularProgressWithLabel({
maxValue,
currentValue,
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/retro/components/TimePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface TimePickerProps {
onSubmit: () => void;
onTimerIncrement: (increment: number) => void;
}

export function TimePicker({
minutes,
seconds,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface IncrementTimerButtonProps {
onTimerIncrement: (amount: number) => void;
minutesToIncrement: number;
}

export default function IncrementTimerButton({
onTimerIncrement,
minutesToIncrement,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import { generateId } from "../../../common/utils/generateId";
import { TextInput } from "../../../common/components/TextInput";
import { useValidatedTextInput } from "../../../common/hooks/useValidatedTextInput";
import { useRoomContext } from "../../../common/context/RoomContext";
import { LocalStorage } from "../../../common/utils/localStorage";
import { useLocalStorage } from "../../../common/hooks/useLocalStorage";
import { CallToActionButton } from "../../../common/components/buttons/CallToActionButton";
import { useDialog } from "../../../common/hooks/useDialog";
import { useRedirect } from "../../../common/hooks/useRedirect";
import UserNameInputField from "./UsernameInputField";
import useLocalStorageName from "../../hooks/useLocalStorageName";

export function CreateRetroSessionDialog() {
const { isOpen, closeDialog } = useDialog(true);
Expand All @@ -50,14 +50,13 @@ export function CreateRetroSessionDialog() {
const { retroState, handleChangeRetroFormat, handleSetRetroState, handleJoinSession } =
useRetroContext();
const { user, setUser } = useUserContext();
const { isStorageAllowed, trySavingNameLocally, handleAllowanceChange } = useLocalStorageName({
setName,
});
const { setRoomId } = useRoomContext();
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));

useLocalStorage(() => {
setName(LocalStorage.getUserName());
});

function handleClose() {
setName("");
setTitle("");
Expand All @@ -82,7 +81,7 @@ export function CreateRetroSessionDialog() {
setRoomId(roomId);
handleSetRetroState({ ...retroState, title });
setUser(newUser);
LocalStorage.setUserName(name);
trySavingNameLocally(name);
handleJoinSession(newUser);
handleChangeRetroFormat(format);
redirectToRoom(roomId);
Expand All @@ -100,19 +99,21 @@ export function CreateRetroSessionDialog() {
<DialogTitle id="form-dialog-create-retro">Create Retro Session</DialogTitle>
<DialogContent sx={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
<Box>
<DialogContentText>Please enter your name</DialogContentText>
<TextInput
value={name}
<UserNameInputField
userName={name}
id="user-name"
onSubmit={handleSubmit}
onChange={handleNameChange}
error={isNameError}
id="user-name"
label="Username"
isStorageAllowed={isStorageAllowed}
onStorageAllowanceChange={(event) => {
handleAllowanceChange(event.target.checked);
}}
NearW marked this conversation as resolved.
Show resolved Hide resolved
autoFocus
NearW marked this conversation as resolved.
Show resolved Hide resolved
/>
</Box>
<Box>
<DialogContentText>Please provide your name for this session</DialogContentText>
<DialogContentText>Please provide a name for this session</DialogContentText>
<TextInput
value={title}
onSubmit={handleSubmit}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface TimerDialogProps extends DialogProps {
remainingMinutes: number;
remainingSeconds: number;
}

export function TimerDialog({
isOpen,
close,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Checkbox, DialogContentText, FormControlLabel, Stack, Tooltip } from "@mui/material";
import InfoIcon from "@mui/icons-material/Info";
import { TextInput, TextInputProps } from "../../../common/components/TextInput";
import React from "react";

interface UserNameInputFieldProps extends TextInputProps {
userName: string;
label?: string;
textFieldLabel?: string;
storagePermissionLabel?: string;
isStorageAllowed: boolean;
onStorageAllowanceChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}

export default function UserNameInputField({
userName,
label = "Please enter your name",
storagePermissionLabel = "Remember me",
textFieldLabel = "Username",
onSubmit,
onChange,
error,
isStorageAllowed,
onStorageAllowanceChange,
...props
}: UserNameInputFieldProps) {
return (
<>
<DialogContentText>{label}</DialogContentText>
<TextInput
value={userName}
onSubmit={onSubmit}
onChange={onChange}
error={error}
label={textFieldLabel}
{...props}
/>
<Stack direction="row" alignItems="center" gap={1}>
<FormControlLabel
control={<Checkbox checked={isStorageAllowed} onChange={onStorageAllowanceChange} />}
label={storagePermissionLabel}
sx={{ marginRight: 0 }}
/>
<Tooltip
arrow
title="If checked, the username will be persisted in the local storage of your browser for future sessions. Unchecking the box will remove this information from your browser again. Your username will only be shared while collaborating and will not be persisted on any server or transmitted to any external service."
>
<InfoIcon />
</Tooltip>
</Stack>
</>
);
}
32 changes: 32 additions & 0 deletions packages/frontend/src/retro/hooks/useLocalStorageName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useState } from "react";
import { LocalStorage } from "../../common/utils/localStorage";
import { useLocalStorage } from "../../common/hooks/useLocalStorage";

interface useLocalStorageNameProps {
setName: (name: string) => void;
}

export default function useLocalStorageName({ setName }: useLocalStorageNameProps) {
const [isStorageAllowed, setIsStorageAllowed] = useState(false);

useLocalStorage(() => {
setName(LocalStorage.getUserName());
setIsStorageAllowed(LocalStorage.getNameStorePermission());
});

function trySavingNameLocally(name: string) {
if (isStorageAllowed) {
LocalStorage.setUserName(name);
}
}

function handleAllowanceChange(isAllowed: boolean) {
if (!isAllowed) {
LocalStorage.removeUserName();
}
LocalStorage.setNameStorePermission(isAllowed);
setIsStorageAllowed(isAllowed);
}

return { isStorageAllowed, trySavingNameLocally, handleAllowanceChange };
}
1 change: 1 addition & 0 deletions packages/frontend/src/retro/hooks/useTimedEffect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
interface useTimedEffectProps {
effectLength: number;
}

export default function useTimedEffect({ effectLength }: useTimedEffectProps) {
const [isEffectActive, setIsEffectActive] = useState(false);

Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/retro/types/retroActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export interface IsVotingEnabledChangedAction extends BaseAction {
type: "IS_VOTING_ENABLED_CHANGED";
isEnabled: boolean;
}

export interface CardVotingLimitChangedAction extends BaseAction {
type: "CARD_VOTING_LIMIT_CHANGED";
limit: number;
Expand Down
3 changes: 3 additions & 0 deletions packages/frontend/src/retro/types/retroTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ export interface RetroColumn {
cards: RetroCard[];
isBlurred: boolean;
}

export enum TimerStatus {
RUNNING,
PAUSED,
STOPPED,
}

export interface RetroState {
title: string;
format: string;
Expand All @@ -37,4 +39,5 @@ export interface RetroState {
}

export type VotesByUserId = Record<string, number>;

export type UserByUserId = Record<string, User>;