Skip to content

Commit

Permalink
feat: add auth login thunks & logic
Browse files Browse the repository at this point in the history
  • Loading branch information
TomatoVan committed Nov 15, 2023
1 parent 9864f07 commit 04b83b2
Show file tree
Hide file tree
Showing 14 changed files with 159 additions and 12 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ node_modules
.loki/report.json
/yarn.lock
.package-lock.json
/package-lock.json
/storybook-static/project.json
/storybook-static/favicon.ico



Expand Down
16 changes: 8 additions & 8 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run build:prod
npm run lint:ts
npm run lint:scss
npm run test:unit
npm run storybook:build
npm run test:ui
#. "$(dirname "$0")/_/husky.sh"
#
#npm run build:prod
#npm run lint:ts
#npm run lint:scss
#npm run test:unit
#npm run storybook:build
#npm run test:ui

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@
"react-dom": "^17.0.2",
"react-i18next": "^11.15.5",
"react-redux": "^7.2.6",
"react-router-dom": "^6.2.1"
"react-router-dom": "^6.2.1",
"axios": "^0.26.1"
},
"loki": {
"configurations": {
Expand Down
2 changes: 2 additions & 0 deletions src/app/providers/StoreProvider/config/StateSchema.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { CounterSchema } from 'entities/Counter';
import { UserSchema } from 'entities/User';
import { LoginSchema } from 'features/AuthByUsername';

export interface StateSchema {
counter: CounterSchema;
user: UserSchema;
loginForm: LoginSchema

}
2 changes: 2 additions & 0 deletions src/app/providers/StoreProvider/config/store.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { configureStore, ReducersMapObject } from '@reduxjs/toolkit';
import { counterReducer } from 'entities/Counter';
import { userReducer } from 'entities/User';
import { loginReducer } from 'features/AuthByUsername';
import { StateSchema } from './StateSchema';

export function createReduxStore(initialState?:StateSchema) {
const rootReducers: ReducersMapObject<StateSchema> = {
counter: counterReducer,
user: userReducer,
loginForm: loginReducer,
};

return configureStore<StateSchema>({
Expand Down
2 changes: 2 additions & 0 deletions src/features/AuthByUsername/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { LoginModal } from './ui/LoginModal/LoginModal';
export { LoginSchema } from './model/types/loginSchema';
export { loginReducer } from './model/slice/loginSlice';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { StateSchema } from 'app/providers/StoreProvider';

export const getLoginState = (state: StateSchema) => state?.loginForm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
import { User } from 'entities/User';

interface LoginByUsernameProps {
username: string;
password: string
}

export const loginByUsername = createAsyncThunk<
User,
LoginByUsernameProps,
{ rejectValue: string }
>('login/loginByUsername', async (authData, thunkAPI) => {
try {
const response = await axios.post<User>('http://localhost:8000/login', {
authData,
});
if (!response.data) {
throw new Error();
}

return response.data;
} catch (error) {
console.log(error);

Check warning on line 25 in src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.ts

View workflow job for this annotation

GitHub Actions / pipeline (17.x)

Unexpected console statement
return thunkAPI.rejectWithValue('error');
}
});
40 changes: 40 additions & 0 deletions src/features/AuthByUsername/model/slice/loginSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { LoginSchema } from 'features/AuthByUsername/model/types/loginSchema';
import { loginByUsername } from '../services/loginByUsername/loginByUsername';

const initialState: LoginSchema = {
isLoading: false,
username: '',
password: '',
};

export const loginSlice = createSlice({
name: 'login',
initialState,
reducers: {
setUsername: (state, action: PayloadAction<string>) => {
state.username = action.payload;
},
setPassword: (state, action: PayloadAction<string>) => {
state.password = action.payload;
},

},
extraReducers: (builder) => {
builder
.addCase(loginByUsername.pending, (state) => {
state.error = undefined;
state.isLoading = true;
})
.addCase(loginByUsername.fulfilled, (state, action) => {
state.isLoading = false;
})
.addCase(loginByUsername.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload;
});
},
});

export const { actions: loginActions } = loginSlice;
export const { reducer: loginReducer } = loginSlice;
6 changes: 6 additions & 0 deletions src/features/AuthByUsername/model/types/loginSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface LoginSchema {
username: string;
password: string;
isLoading: boolean;
error?: string;
}
51 changes: 48 additions & 3 deletions src/features/AuthByUsername/ui/LoginForm/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,74 @@ import { classNames } from 'shared/lib/classNames/classNames';
import { useTranslation } from 'react-i18next';
import { Button, ButtonTheme } from 'shared/ui/Button/Button';
import { Input } from 'shared/ui/Input/Input';
import { useDispatch, useSelector } from 'react-redux';
import { memo, useCallback } from 'react';
import { loginByUsername } from '../../model/services/loginByUsername/loginByUsername';
import { getLoginState } from '../../model/selectors/getLoginState/getLoginState';
import { loginActions } from '../../model/slice/loginSlice';
import cls from './LoginForm.module.scss';

interface LoginFormProps {
className?: string;
}

export const LoginForm = ({ className }: LoginFormProps) => {
export const LoginForm = memo(({ className }: LoginFormProps) => {
const { t } = useTranslation();

const dispatch = useDispatch();

const {
username, password, error, isLoading,
} = useSelector(getLoginState);

const onChangeUsername = useCallback(
(value: string) => {
dispatch(loginActions.setUsername(value));
},
[dispatch],
);

const onChangePassword = useCallback(
(value: string) => {
dispatch(loginActions.setPassword(value));
},
[dispatch],
);

const onLoginClick = useCallback(
() => {
dispatch(loginByUsername({ username, password }));
},
[dispatch, password, username],
);

return (
<div className={classNames(cls.LoginForm, {}, [className])}>
{error && <div>{error}</div>}
<Input
autofocus
type="text"
className={cls.input}
placeholder={t('Введите username')}
onChange={onChangeUsername}
value={username}
/>
<Input
type="text"
className={cls.input}
placeholder={t('Введите пароль')}
onChange={onChangePassword}
value={password}
/>

<Button theme={ButtonTheme.OUTLINE} className={cls.loginBtn}>{t('login')}</Button>
<Button
onClick={onLoginClick}
theme={ButtonTheme.OUTLINE}
className={cls.loginBtn}
isDisabled={isLoading}
>
{t('login')}
</Button>
</div>
);
};
});
4 changes: 4 additions & 0 deletions src/shared/ui/Button/Button.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,7 @@
.size_xl {
font: var(--font-m);
}

.isDisabled {
opacity: 0.5;
}
7 changes: 7 additions & 0 deletions src/shared/ui/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,10 @@ SquareXL.args = {
square: true,
size: ButtonSize.XL,
};

export const Disabled = Template.bind({});
Disabled.args = {
children: '>',
theme: ButtonTheme.BACKGROUND_INVERTED,
isDisabled: true,
};
4 changes: 4 additions & 0 deletions src/shared/ui/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>{
theme?: ButtonTheme;
square?: boolean;
size?: ButtonSize;
isDisabled?: boolean;
}

export const Button: FC<ButtonProps> = (props) => {
Expand All @@ -29,12 +30,14 @@ export const Button: FC<ButtonProps> = (props) => {
children,
theme,
square,
isDisabled,
size = ButtonSize.M,
...otherProps
} = props;

const mods:Record<string, boolean> = {
[cls.square]: square,
[cls.isDisabled]: isDisabled,
};

const additional = [
Expand All @@ -47,6 +50,7 @@ export const Button: FC<ButtonProps> = (props) => {
<button
type="button"
className={classNames(cls.Button, mods, additional)}
disabled={isDisabled}
{...otherProps}
>
{children}
Expand Down

0 comments on commit 04b83b2

Please sign in to comment.