Skip to content

Commit

Permalink
login module
Browse files Browse the repository at this point in the history
  • Loading branch information
veronikaa-kuznetsova committed Jun 10, 2024
1 parent 653f958 commit 9836eaa
Show file tree
Hide file tree
Showing 58 changed files with 359 additions and 23 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .loki/reference/chrome_iphone7_pages_MainPage_Dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .loki/reference/chrome_iphone7_pages_MainPage_Normal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .loki/reference/chrome_laptop_pages_MainPage_Dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .loki/reference/chrome_laptop_pages_MainPage_Normal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion config/storybook/webpack.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import webpack, { RuleSetRule } from 'webpack';
import webpack, { DefinePlugin, RuleSetRule } from 'webpack';
import path from 'path';
import { buildCssLoader } from '../build/loaders/buildCssLoader';
import { BuildPaths } from '../build/types/config';
Expand Down Expand Up @@ -28,5 +28,9 @@ export default ({ config }: {config: webpack.Configuration}) => {
});
config.module.rules.push(buildCssLoader(true));

config.plugins.push(new DefinePlugin({
__IS_DEV__: true,
}));

return config;
};
4 changes: 3 additions & 1 deletion public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
"update_page": "Update page",
"sign_in": "Sign in",
"sign_up": "Sign up",
"logout": "Logout",
"decrement": "Decrement",
"increment": "Increment",
"enter_username": "Enter username",
"enter_password": "Enter password"
"enter_password": "Enter password",
"incorrect_username_or_password": "Incorrect username or password"
}
4 changes: 3 additions & 1 deletion public/locales/ru/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
"update_page": "Обновить страницу",
"sign_in": "Войти",
"sign_up": "Зарегистрироваться",
"logout": "Выйти",
"decrement": "Уменьшить",
"increment": "Увеличить",
"enter_username": "Введите username",
"enter_password": "Введите пароль"
"enter_password": "Введите пароль",
"incorrect_username_or_password": "Неверное имя пользователя или пароль"
}
34 changes: 22 additions & 12 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import React, { Suspense } from 'react';
import React, { Suspense, useEffect } from 'react';
import { classNames } from 'shared/lib/classNames/classNames';
import { AppRouter } from 'app/providers/router';
import { Navbar } from 'widgets/Navbar';
import { Sidebar } from 'widgets/Sidebar';
import { useDispatch } from 'react-redux';
import { userActions } from 'entities/User';

const App = () => (
<div className={classNames('app', {}, [])}>
<Suspense fallback="">
<Navbar />
<div className="content-page">
<Sidebar />
<AppRouter />
</div>
</Suspense>
</div>
);
const App = () => {
const dispatch = useDispatch();

useEffect(() => {
dispatch(userActions.initAuthData());
}, [dispatch]);

return (
<div className={classNames('app', {}, [])}>
<Suspense fallback="">
<Navbar />
<div className="content-page">
<Sidebar />
<AppRouter />
</div>
</Suspense>
</div>
);
};

export default App;
2 changes: 2 additions & 0 deletions src/app/providers/StoreProvider/config/StateSchema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
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/app/styles/variables/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@

// colors
--overlay-color: rgba(0 0 0 / 60%);
--red-light: #ff0000;
--red-dark: #ce0505;
}
4 changes: 4 additions & 0 deletions src/entities/User/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export {
getUserAuthData,
} from './model/selectors/getUserAuthData/getUserAuthData';

export {
userReducer,
userActions,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { StateSchema } from 'app/providers/StoreProvider';

export const getUserAuthData = (state: StateSchema) => state.user.authData;
21 changes: 18 additions & 3 deletions src/entities/User/model/slice/userSlice.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { createSlice } from '@reduxjs/toolkit';
import { UserSchema } from '../types/user';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { USER_LOCALSTORAGE_KEY } from 'shared/const/localstorage';
import { UserSchema, User } from '../types/user';

const initialState: UserSchema = {};

export const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
reducers: {
setAuthData: (state, action: PayloadAction<User>) => {
state.authData = action.payload;
},
initAuthData: (state) => {
const user = localStorage.getItem(USER_LOCALSTORAGE_KEY);
if (user) {
state.authData = JSON.parse(user);
}
},
logout: (state) => {
state.authData = undefined;
localStorage.removeItem(USER_LOCALSTORAGE_KEY);
},
},
});

export const { actions: userActions } = userSlice;
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;
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
import { User, userActions } from 'entities/User';
import { USER_LOCALSTORAGE_KEY } from 'shared/const/localstorage';

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();
}

localStorage.setItem(USER_LOCALSTORAGE_KEY, JSON.stringify(response.data));
thunkAPI.dispatch(userActions.setAuthData(response.data));

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

Check warning on line 26 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');
}
},
);
39 changes: 39 additions & 0 deletions src/features/AuthByUsername/model/slice/loginSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { loginByUsername } from '../services/loginByUsername/loginByUsername';
import { LoginSchema } from '../types/loginSchema';

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, action) => {

Check warning on line 24 in src/features/AuthByUsername/model/slice/loginSlice.ts

View workflow job for this annotation

GitHub Actions / pipeline (17.x)

'action' is defined but never used
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;
}
16 changes: 16 additions & 0 deletions src/features/AuthByUsername/ui/LoginForm/LoginForm.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { StoreDecorator } from 'shared/config/storybook/StoreDecorator/StoreDecorator';
import { LoginForm } from './LoginForm';

export default {
Expand All @@ -14,3 +15,18 @@ const Template: ComponentStory<typeof LoginForm> = (args) => <LoginForm {...args

export const Primary = Template.bind({});
Primary.args = {};
Primary.decorators = [StoreDecorator({
loginForm: { username: '123', password: 'asd' },
})];

export const withError = Template.bind({});
withError.args = {};
withError.decorators = [StoreDecorator({
loginForm: { username: '123', password: 'asd', error: 'ERROR' },
})];

export const Loading = Template.bind({});
Loading.args = {};
Loading.decorators = [StoreDecorator({
loginForm: { isLoading: true },
})];
41 changes: 37 additions & 4 deletions src/features/AuthByUsername/ui/LoginForm/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,63 @@
import { classNames } from 'shared/lib/classNames/classNames';
import { useTranslation } from 'react-i18next';
import { Button } from 'shared/ui/Button/Button';
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 { Text, TextTheme } from 'shared/ui/Text/Text';
import { loginByUsername } from '../../model/services/loginByUsername/loginByUsername';
import { loginActions } from '../../model/slice/loginSlice';
import cls from './LoginForm.module.scss';
import { getLoginState } from '../../model/selectors/getLoginState/getLoginState';

interface LoginFormProps {
className?: string;
}

export const LoginForm = ({ className }: LoginFormProps) => {
export const LoginForm = memo(({ className }: LoginFormProps) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const {
username, password, isLoading, error,
} = 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, username, password]);

return (
<div className={classNames(cls.LoginForm, {}, [className])}>
<Text title={t('sign_in')} />
{error && <Text text={t('incorrect_username_or_password')} theme={TextTheme.ERROR} />}
<Input
autofocus
className={cls.input}
placeholder={t('enter_username')}
onChange={onChangeUsername}
value={username}
/>
<Input
className={cls.input}
placeholder={t('enter_password')}
onChange={onChangePassword}
value={password}
/>
<Button className={cls.loginBtn}>
<Button
theme={ButtonTheme.OUTLINE}
className={cls.loginBtn}
onClick={onLoginClick}
disabled={isLoading}
>
{t('sign_in')}
</Button>
</div>
);
};
});
9 changes: 9 additions & 0 deletions src/shared/config/storybook/StoreDecorator/StoreDecorator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Story } from '@storybook/react';
import { StateSchema, StoreProvider } from 'app/providers/StoreProvider';
import { DeepPartial } from '@reduxjs/toolkit';

export const StoreDecorator = (state: DeepPartial<StateSchema>) => (StoryComponent: Story) => (
<StoreProvider initialState={state}>
<StoryComponent />
</StoreProvider>
);
1 change: 1 addition & 0 deletions src/shared/const/localstorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const USER_LOCALSTORAGE_KEY = 'user';
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 @@ -64,3 +64,7 @@
.size_xl {
font: var(--font-xl);
}

.disabled {
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 @@ -92,3 +92,10 @@ SquareSizeXl.args = {
square: true,
size: ButtonSize.XL,
};

export const Disabled = Template.bind({});
Disabled.args = {
children: '>',
theme: ButtonTheme.OUTLINE,
disabled: true,
};
Loading

0 comments on commit 9836eaa

Please sign in to comment.