Commit 3736c5a2 authored by Болатов Ален's avatar Болатов Ален

Merge branch '23' into 'dev'

some minor changes

See merge request !24
parents db7d5af1 a7f19dc5
This diff is collapsed.
......@@ -26,6 +26,9 @@
"@types/react-redux": "^7.1.25",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^3.1.0",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"tailwindcss": "^3.3.1",
"typescript": "^4.9.3",
"vite": "^4.2.0"
}
......
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
.container {
max-width: 980px;
display: flex;
}
\ No newline at end of file
import { Container } from "@mui/material"
import { Route, Routes } from "react-router-dom"
import Layout from "./components/Layout/Layout"
import './App.css'
import HomePage from "./containers/HomePage/HomePage"
import React from 'react';
import {BrowserRouter, Routes, Route} from 'react-router-dom';
import Layout from './components/Layout';
import HomePage from './containers/HomePage';
import Register from './containers/Register';
const App = ()=> {
return (
//@ts-ignore
<Layout>
<Routes>
<Route path={'/'} element={<HomePage />} />
</Routes>
</Layout>
)
}
export default App
const App: React.FunctionComponent = (): React.ReactElement => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<HomePage />} />
<Route path="register" element={<Register />} />
</Route>
</Routes>
</BrowserRouter>
);
};
export default App;
import React, { FunctionComponent, ReactElement } from "react";
import Logo from "../Logo/Logo";
import Navbar from "../Navbar/Navbar";
// import './Header.css'
const Header: FunctionComponent = (): ReactElement => {
return (
<header className="header">
<div className="container header_container">
<div className="logo_block">
<Logo />
</div>
<div className="nav_block">
<Navbar />
</div>
</div>
</header>
)
}
export default Header
\ No newline at end of file
import React, {FunctionComponent, ReactElement} from 'react';
import {NavLink, Outlet} from 'react-router-dom';
const Layout: FunctionComponent = (): ReactElement => {
return (
<div className="container mx-auto ">
<div className="flex justify-between">
<a href="#">Add Post</a>
<div>
<NavLink to={'register'}>Register</NavLink>
<NavLink to={'login'}>Login</NavLink>
</div>
</div>
<Outlet />
</div>
);
};
export default Layout;
.sidebar {
margin: 80px 30px;
margin-right: 150px;
}
\ No newline at end of file
import React, { FunctionComponent, ReactElement } from "react";
import Navbar from "../Navbar/Navbar";
import Logo from "../Logo/Logo";
import './Layout.css'
//@ts-ignore
const Layout: FunctionComponent = ({children}): ReactElement => {
return (
<div className="container aside_container">
<div className="sidebar">
<Logo />
<Navbar />
</div>
<main>{children}</main>
</div>
)
}
export default Layout
\ No newline at end of file
.logo_img {
width: 150px;
height: auto
}
\ No newline at end of file
import React, { FunctionComponent, ReactElement } from "react";
import { Link } from "react-router-dom";
import './Logo.css'
import logo from '../../assets/logo.png'
const Logo: FunctionComponent = (): ReactElement => {
return (
<Link className="logo" to={'/'}>
<img className="logo_img" src={logo} alt="logo" />
</Link>
)
}
export default Logo
\ No newline at end of file
.nav_item{
list-style-type: none;
margin-bottom: 20px;
}
.nav_link {
text-decoration: none;
font-size: 25px;
color: black;
}
\ No newline at end of file
import React, { FunctionComponent, ReactElement } from "react";
import { NavLink } from "react-router-dom";
import './Navbar.css'
const Navbar: FunctionComponent = (): ReactElement => {
return (
<div className="container">
<ul className="nav_items">
<li className="nav_item">
<NavLink to={'/'} className={'nav_link'}>Home</NavLink>
</li>
<li className="nav_item">
<NavLink to={'/'} className={'nav_link'}>News</NavLink>
</li>
<li className="nav_item">
<NavLink to={'/add-news'} className={'nav_link'}>Add-Post</NavLink>
</li>
<li className="nav_item">
<NavLink to={'/'} className={'nav_link'}>Blog</NavLink>
</li>
<li className="nav_item">
<NavLink to={'/'} className={'nav_link'}>Messages</NavLink>
</li>
</ul>
</div>
)
}
export default Navbar
\ No newline at end of file
import IPost from "../../interfaces/IPost";
export default interface IPostProps {
post: IPost
}
\ No newline at end of file
.post_imageframe {
margin-right: 100px;
width: 100%;
border-radius: 10px;
overflow: hidden;
height: 300px;
}
.post_image {
width: 100%;
height: 100%;
object-fit: cover;
}
\ No newline at end of file
import { FunctionComponent, ReactElement } from "react";
import IPostProps from "./IPostProps";
import defaultImage from '../../assets/default-image.jpg'
import './Post.css'
const Post: FunctionComponent<IPostProps> = (props): ReactElement => {
return (
<div className="post">
<h3>user</h3>
<div className="post_imageframe">
<img
className="post_image"
src={`${import.meta.env.VITE_BASE_URL}/uploads/${props.post.image}`}
alt={props.post.title}
onError={(e) => {
e.currentTarget.src = defaultImage
}} />
</div>
<p>{props.post.datetime}</p>
</div>
)
}
export default Post
\ No newline at end of file
import IPost from "../../interfaces/IPost";
export default interface IPostListProps {
posts: IPost[]
}
\ No newline at end of file
import React, { FunctionComponent, ReactElement } from "react";
import IPostListProps from "./IPostListProps";
import Post from "../Post/Post";
const PostList: FunctionComponent<IPostListProps> = (props): ReactElement => {
return (
<div className="PostList">
{props.posts.length ?
props.posts.map(p => {
return <Post
key={p._id}
post={p}
/>
}): <h1>No posts</h1>}
</div>
)
}
export default PostList
\ No newline at end of file
import {FunctionComponent, ReactElement, useEffect} from 'react';
import {shallowEqual, useDispatch} from 'react-redux';
const HomePage: FunctionComponent = (): ReactElement => {
return <div>homepage</div>;
};
export default HomePage;
import React, { FunctionComponent, ReactElement, useEffect } from "react";
import { AppDispatch, AppState } from "../../store/store";
import { shallowEqual, useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { getPosts } from "../../store/posts/posts.slice";
import PostList from "../../components/PostList/PostList";
const HomePage: FunctionComponent = (): ReactElement => {
const dispatch:AppDispatch = useDispatch()
const {posts} = useSelector((state: AppState) => state.posts, shallowEqual)
useEffect(() => {
dispatch(getPosts())
}, [])
return (
<div className="homepage">
<div className="container">
<PostList
posts={posts}
/>
</div>
</div>
)
}
export default HomePage
\ No newline at end of file
import React, {useEffect, useState} from 'react';
import {Navigate, useNavigate} from 'react-router-dom';
import {useSelector} from 'react-redux';
import IUser from '../interfaces/IUser';
import {useAppDispatch, useAppSelector} from '../store/hooks';
import {selectUser} from '../store/users/users.slice';
const Register = () => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
// const {userLoggedIn} = useSelector((state) => state.user);
const handleRegister = async () => {
// await dispatch(createUser(user));
};
const handleUserInput = (e: React.ChangeEvent<HTMLInputElement>) => {
// setUser((prevState) => ({...prevState, [e.target.name]: e.target.value}));
};
const handleSubmit = async (e: React.ChangeEvent<HTMLFormElement>) => {
e.preventDefault();
// await dispatch(loginUser(user));
};
// if (userLoggedIn) {
// return <Navigate to={'/'} />;
// }
return (
<div className="w-full max-w-xs m-auto h-screen mt-[200px]">
<form
onSubmit={handleSubmit}
className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"
>
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="username"
>
Username
</label>
<input
onChange={handleUserInput}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
id="username"
type="text"
placeholder="Username"
name="username"
/>
</div>
<div className="mb-6">
<label
className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="password"
>
Password
</label>
<input
onChange={handleUserInput}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
id="password"
type="password"
placeholder="Password"
name="password"
/>
</div>
<div className="flex items-center justify-between">
<button
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
type="button"
onClick={handleRegister}
>
Register
</button>
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
type="submit"
>
Sign In
</button>
</div>
</form>
<p className="text-center text-gray-500 text-xs">
&copy;2020 Acme Corp. All rights reserved.
</p>
</div>
);
};
export default Register;
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from './store/store'
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
import {Provider} from 'react-redux';
import {store} from './store/store';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
)
<Provider store={store}>
<App />
</Provider>
);
import { createAsyncThunk } from "@reduxjs/toolkit"
import { AppDispatch, AppState } from "./store"
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
state: AppState
dispatch: AppDispatch
rejectValue: string
}>()
\ No newline at end of file
import {TypedUseSelectorHook, useDispatch, useSelector} from 'react-redux';
import type {RootState, AppDispatch} from './store';
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"
import { useDispatch } from 'react-redux'
import { postsSlice } from "./posts/posts.slice"
import { commentsSlice } from "./comments/comments.slice"
import { usersSlice } from "./users/users.slice"
import {configureStore} from '@reduxjs/toolkit';
import userSlice from './users/users.slice';
export const store = configureStore({
reducer: {
user: userSlice,
},
});
const makeStore = () => {
return configureStore({
reducer: {
posts: postsSlice.reducer,
comments: commentsSlice.reducer,
users: usersSlice.reducer
}
})
}
const store = makeStore()
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export type AppStore = ReturnType<typeof makeStore>;
export type AppState = ReturnType<AppStore["getState"]>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
AppState,
unknown,
Action
>;
export const useAppDispatch: () => AppDispatch = useDispatch;
export default store
\ No newline at end of file
import IUserGetDto from "../../interfaces/IUserGetDto";
export default interface IUsersState {
user: IUserGetDto | undefined
isAuth: boolean
loadingUser: boolean
messageUser: string
}
\ No newline at end of file
import { createSlice } from "@reduxjs/toolkit";
import { userApi } from "../../api/userApi";
import IUser from "../../interfaces/IUser";
import IUserCreateDto from "../../interfaces/IUserCreateDto";
import { createAppAsyncThunk } from "../createAppAsyncThunk";
import IUsersState from './IUserState'
import {createSlice, PayloadAction} from '@reduxjs/toolkit';
import {RootState} from '../store';
interface UserState {
username: string;
}
const namespace = 'users'
const initialState: UserState = {
username: '',
};
export const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
extraReducers: {},
});
export const login = createAppAsyncThunk(
`${namespace}/login`,
async (user: IUserCreateDto) => {
return userApi.login(user)
}
)
export const {} = userSlice.actions;
export const register = createAppAsyncThunk(
`${namespace}/register`,
async (user: IUserCreateDto) => {
return userApi.register(user)
}
)
export const selectUser = (state: RootState) => state.user.username;
export const checkToken = createAppAsyncThunk(
`${namespace}/checkToken`,
async () => {
return userApi.checkToken()
}
)
export const usersSlice = createSlice({
name: namespace,
initialState: {
user: {} as IUser,
isAuth: false,
loadingUser: false,
messageUser: ''
} as IUsersState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(login.rejected, (state) => {
state.loadingUser = false
})
.addCase(login.pending, (state) => {
state.loadingUser = true
})
.addCase(login.fulfilled, (state, action) => {
state.loadingUser = false
const user = action.payload.result
state.user = user
state.messageUser = action.payload.message
if (user) {
localStorage.setItem('token', user.token)
state.isAuth = true
}
})
.addCase(register.rejected, (state) => {
state.loadingUser = false
})
.addCase(register.pending, (state) => {
state.loadingUser = true
})
.addCase(register.fulfilled, (state, action) => {
state.loadingUser = false
const user = action.payload.result
state.user = user
state.messageUser = action.payload.message
if (user) {
localStorage.setItem('token', user.token)
state.isAuth = true
}
})
.addCase(checkToken.rejected, (state) => {
state.loadingUser = false
})
.addCase(checkToken.pending, (state) => {
state.loadingUser = true
})
.addCase(checkToken.fulfilled, (state, action) => {
state.loadingUser = false
const user = action.payload.result
state.user = user
state.messageUser = action.payload.message
if (user) {
state.isAuth = true
} else {
state.isAuth = false
}
})
}
})
export const {} = usersSlice.actions
\ No newline at end of file
export default userSlice.reducer;
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src", "src/store/store.ts"],
"references": [{"path": "./tsconfig.node.json"}]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment