v1.1; some profile beauty; snowfall
This commit is contained in:
+4
-3
@@ -6,8 +6,8 @@
|
|||||||
"@fortawesome/fontawesome-svg-core": "1.2.32",
|
"@fortawesome/fontawesome-svg-core": "1.2.32",
|
||||||
"@fortawesome/free-brands-svg-icons": "5.15.1",
|
"@fortawesome/free-brands-svg-icons": "5.15.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.15.1",
|
"@fortawesome/free-solid-svg-icons": "5.15.1",
|
||||||
"@fortawesome/react-fontawesome": "0.1.12",
|
"@fortawesome/react-fontawesome": "0.1.13",
|
||||||
"@sentry/react": "5.27.4",
|
"@sentry/react": "5.28.0",
|
||||||
"bbcode-to-react": "0.2.9",
|
"bbcode-to-react": "0.2.9",
|
||||||
"eruda": "2.4.1",
|
"eruda": "2.4.1",
|
||||||
"eruda-dom": "2.0.0",
|
"eruda-dom": "2.0.0",
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
"react-loader-spinner": "3.1.14",
|
"react-loader-spinner": "3.1.14",
|
||||||
"react-redux": "7.2.2",
|
"react-redux": "7.2.2",
|
||||||
"react-router-dom": "5.2.0",
|
"react-router-dom": "5.2.0",
|
||||||
|
"react-snowfall": "^1.0.2",
|
||||||
"redux": "4.0.5"
|
"redux": "4.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -27,9 +28,9 @@
|
|||||||
"@types/react-redux": "7.1.11",
|
"@types/react-redux": "7.1.11",
|
||||||
"@types/react-router-dom": "5.1.6",
|
"@types/react-router-dom": "5.1.6",
|
||||||
"@types/react-transition-group": "4.4.0",
|
"@types/react-transition-group": "4.4.0",
|
||||||
"sass": "1.29.0",
|
|
||||||
"react-scripts": "3.4.4",
|
"react-scripts": "3.4.4",
|
||||||
"redux-devtools-extension": "2.13.8",
|
"redux-devtools-extension": "2.13.8",
|
||||||
|
"sass": "1.29.0",
|
||||||
"typescript": "4.1.2"
|
"typescript": "4.1.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+12
-15
@@ -2,12 +2,17 @@ const baseUrl = process.env.NODE_ENV === "development" ? 'http://localhost:8080/
|
|||||||
|
|
||||||
async function request<T>(url: string, method: string, body?: string | FormData): Promise<T> {
|
async function request<T>(url: string, method: string, body?: string | FormData): Promise<T> {
|
||||||
let bUrl = baseUrl;
|
let bUrl = baseUrl;
|
||||||
if(baseUrl.endsWith('/')) bUrl = bUrl.slice(0, baseUrl.length - 1);
|
if(baseUrl.endsWith('/'))
|
||||||
if(url.startsWith('/')) url = url.slice(1, baseUrl.length);
|
bUrl = bUrl.slice(0, baseUrl.length - 1);
|
||||||
|
if(url.startsWith('/'))
|
||||||
|
url = url.slice(1, baseUrl.length);
|
||||||
|
|
||||||
const res = await fetch(`${bUrl}/${url}`, {
|
const res = await fetch(`${bUrl}/${url}`, {
|
||||||
method, body,
|
method, body,
|
||||||
credentials: "include"
|
credentials: "include",
|
||||||
|
headers: new Headers({
|
||||||
|
'Dev': /dev.(.+)/.test(window.location.href) ? '1' : '0'
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let json: ApiResponse<T>;
|
let json: ApiResponse<T>;
|
||||||
@@ -26,18 +31,10 @@ async function request<T>(url: string, method: string, body?: string | FormData)
|
|||||||
return Promise.resolve(json.data);
|
return Promise.resolve(json.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function get<T>(url: string, data?: object): Promise<T> {
|
const get = <T>(url: string, data?: object): Promise<T> => request<T>(url, 'GET', JSON.stringify(data));
|
||||||
return request<T>(url, 'GET', JSON.stringify(data));
|
const post = <T>(url: string, data?: object | FormData): Promise<T> => request(url, 'POST', data instanceof FormData ? data : JSON.stringify(data));
|
||||||
}
|
const put = <T>(url: string, data?: object): Promise<T> => request<T>(url, 'PUT', JSON.stringify(data));
|
||||||
function post<T>(url: string, data?: object | FormData): Promise<T> {
|
const del = <T>(url: string, data?: object): Promise<T> => request<T>(url, 'DELETE', JSON.stringify(data));
|
||||||
return request(url, 'POST', data instanceof FormData ? data : JSON.stringify(data));
|
|
||||||
}
|
|
||||||
function put<T>(url: string, data?: object): Promise<T> {
|
|
||||||
return request<T>(url, 'PUT', JSON.stringify(data));
|
|
||||||
}
|
|
||||||
function del<T>(url: string, data?: object): Promise<T> {
|
|
||||||
return request<T>(url, 'DELETE', JSON.stringify(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
const TranslateStatus: { [id: string]: string } = {
|
const TranslateStatus: { [id: string]: string } = {
|
||||||
planned: 'Запланировано',
|
planned: 'Запланировано',
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { get } from "./api";
|
||||||
|
|
||||||
|
export const getClub = (id: number) => get<ClubType>(`/clubs/${id}`);
|
||||||
+13
-2
@@ -65,8 +65,17 @@ body[theme='light']
|
|||||||
--block-sec-color: #{$blockSecondaryBackgroundColorLight}
|
--block-sec-color: #{$blockSecondaryBackgroundColorLight}
|
||||||
--nav-bg-color: #{$navbarBackgroundColorLight}
|
--nav-bg-color: #{$navbarBackgroundColorLight}
|
||||||
|
|
||||||
color: var(--main-color)
|
body[theme='win95']
|
||||||
background: var(--main-bg-color)
|
--main-color: #{$mainColorLight}
|
||||||
|
--link-color: #{$linkHoverColorLight}
|
||||||
|
--sec-color: #{$secondaryColorLight}
|
||||||
|
|
||||||
|
--main-bg-color: #008083
|
||||||
|
--sec-bg-color: #c0c0c0
|
||||||
|
--nav-bg-color: #c0c0c0
|
||||||
|
|
||||||
|
.Navbar
|
||||||
|
box-shadow: 0 1px 1px 1px white
|
||||||
|
|
||||||
a
|
a
|
||||||
color: var(--main-color)
|
color: var(--main-color)
|
||||||
@@ -96,7 +105,9 @@ a
|
|||||||
margin-bottom: 1rem
|
margin-bottom: 1rem
|
||||||
|
|
||||||
.Error
|
.Error
|
||||||
|
color: #{mainColor}
|
||||||
background-color: var(--error-bg-color)
|
background-color: var(--error-bg-color)
|
||||||
|
|
||||||
.Successful
|
.Successful
|
||||||
|
color: #{mainColor}
|
||||||
background-color: var(--success-bg-color)
|
background-color: var(--success-bg-color)
|
||||||
|
|||||||
+33
-7
@@ -9,8 +9,6 @@ import NotFoundError from "../NotFoundError";
|
|||||||
import { fetchCurrentUser, fetchUserSettings } from "../../api/users";
|
import { fetchCurrentUser, fetchUserSettings } from "../../api/users";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import Character from "../Character/Character";
|
import Character from "../Character/Character";
|
||||||
// @ts-ignore
|
|
||||||
import parser from 'bbcode-to-react';
|
|
||||||
import SpoilerTag from "../../ui/Spoiler/SpoilerTag";
|
import SpoilerTag from "../../ui/Spoiler/SpoilerTag";
|
||||||
import Novels from "../Novels/Novels";
|
import Novels from "../Novels/Novels";
|
||||||
import Footer from '../Footer/Footer';
|
import Footer from '../Footer/Footer';
|
||||||
@@ -28,21 +26,30 @@ import Settings from "../User/Settings/Settings";
|
|||||||
import { setUser } from "../../store/ducks/currentUser";
|
import { setUser } from "../../store/ducks/currentUser";
|
||||||
import { setSettings } from "../../store/ducks/userSettings";
|
import { setSettings } from "../../store/ducks/userSettings";
|
||||||
import { withProfiler } from "@sentry/react";
|
import { withProfiler } from "@sentry/react";
|
||||||
|
import { hasPermission } from "../../utils";
|
||||||
|
import Club from "../Club/Club";
|
||||||
|
import Snowfall from "react-snowfall";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import eruda from 'eruda';
|
import eruda from 'eruda';
|
||||||
import { hasPermission } from "../../utils";
|
// @ts-ignore
|
||||||
|
import parser from 'bbcode-to-react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
notifications: React.ReactNode[]
|
notifications: React.ReactNode[]
|
||||||
modal: React.ReactNode
|
modal: React.ReactNode
|
||||||
setUser: SetUser
|
setUser: SetUser
|
||||||
setSettings: SetSettings
|
setSettings: SetSettings
|
||||||
|
snowfall: Snowfall
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {}
|
type State = {
|
||||||
|
user: UserType | null
|
||||||
|
};
|
||||||
|
|
||||||
class App extends React.Component<Props, State> {
|
class App extends React.Component<Props, State> {
|
||||||
state: State = {}
|
state: State = {
|
||||||
|
user: null
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -60,6 +67,9 @@ class App extends React.Component<Props, State> {
|
|||||||
|
|
||||||
fetchCurrentUser().then(user => {
|
fetchCurrentUser().then(user => {
|
||||||
setUser(user);
|
setUser(user);
|
||||||
|
this.setState({ user });
|
||||||
|
localStorage.setItem('banned', JSON.stringify(user.is_banned));
|
||||||
|
|
||||||
fetchUserSettings().then(settings => {
|
fetchUserSettings().then(settings => {
|
||||||
setSettings(settings);
|
setSettings(settings);
|
||||||
if(settings.theme !== cachedTheme) {
|
if(settings.theme !== cachedTheme) {
|
||||||
@@ -70,6 +80,7 @@ class App extends React.Component<Props, State> {
|
|||||||
if(hasPermission(user, 'mobile_debug'))
|
if(hasPermission(user, 'mobile_debug'))
|
||||||
eruda.init();
|
eruda.init();
|
||||||
}).catch((err: ApiError) => {
|
}).catch((err: ApiError) => {
|
||||||
|
console.log(err)
|
||||||
if(err.code !== 3 && err.text)
|
if(err.code !== 3 && err.text)
|
||||||
console.error(err.text);
|
console.error(err.text);
|
||||||
});
|
});
|
||||||
@@ -88,10 +99,22 @@ class App extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { modal } = this.props;
|
const { modal, snowfall } = this.props;
|
||||||
|
const { user } = this.state;
|
||||||
|
const isBanned = user?.is_banned || JSON.parse(localStorage.getItem('banned') || 'false');
|
||||||
|
|
||||||
|
if(isBanned) {
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', height: '100%', justifyContent: "center", alignItems: "center", fontSize: '2rem' }}>
|
||||||
|
Вы были забанены!
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{snowfall.snowfallStatus && (
|
||||||
|
<Snowfall color={'white'} snowflakeCount={snowfall.snowflakeCount} style={{ zIndex: 1000 }}/>
|
||||||
|
)}
|
||||||
<Navbar/>
|
<Navbar/>
|
||||||
<div className={'Container'}>
|
<div className={'Container'}>
|
||||||
<div className="Notifications">
|
<div className="Notifications">
|
||||||
@@ -110,6 +133,8 @@ class App extends React.Component<Props, State> {
|
|||||||
<Route exact path={'/novels'} component={Novels}/>
|
<Route exact path={'/novels'} component={Novels}/>
|
||||||
<Route exact path={'/novels/:novelId'} component={Novel}/>
|
<Route exact path={'/novels/:novelId'} component={Novel}/>
|
||||||
|
|
||||||
|
<Route exact path={'/clubs/:clubId'} component={Club}/>
|
||||||
|
|
||||||
<Route exact path={'/users'} component={Users}/>
|
<Route exact path={'/users'} component={Users}/>
|
||||||
<Route exact path={'/user/:userId'} component={User}/>
|
<Route exact path={'/user/:userId'} component={User}/>
|
||||||
<Route exact path={'/user/:userId/novels'} component={UserNovels}/>
|
<Route exact path={'/user/:userId/novels'} component={UserNovels}/>
|
||||||
@@ -138,7 +163,8 @@ class App extends React.Component<Props, State> {
|
|||||||
export default connect(
|
export default connect(
|
||||||
(state: StoreState) => ({
|
(state: StoreState) => ({
|
||||||
notifications: state.notifications,
|
notifications: state.notifications,
|
||||||
modal: state.modal
|
modal: state.modal,
|
||||||
|
snowfall: state.snowfall
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
setUser: (userData: UserType) => dispatch(setUser(userData)),
|
setUser: (userData: UserType) => dispatch(setUser(userData)),
|
||||||
|
|||||||
@@ -15,12 +15,10 @@
|
|||||||
margin-top: 1rem
|
margin-top: 1rem
|
||||||
|
|
||||||
.Character__Image
|
.Character__Image
|
||||||
width: 150px !important
|
width: 150px
|
||||||
height: 250px
|
height: 250px
|
||||||
margin-right: .5rem
|
margin-right: .5rem
|
||||||
border-radius: $borderRadius
|
border-radius: $borderRadius
|
||||||
background-size: cover
|
|
||||||
background-position: center
|
|
||||||
|
|
||||||
.Character__Info
|
.Character__Info
|
||||||
display: flex
|
display: flex
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class Character extends React.Component<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<div className={'Character'}>
|
<div className={'Character'}>
|
||||||
<div className="Character__Main">
|
<div className="Character__Main">
|
||||||
<div style={{ backgroundImage: `url(${char.image})` }} className="Character__Image"/>
|
<img src={char.image} alt={char.original_name} className="Character__Image" />
|
||||||
<div className="Character__Info">
|
<div className="Character__Info">
|
||||||
<div className="Character__Info_Name">
|
<div className="Character__Info_Name">
|
||||||
{char.original_name !== char.localized_name ? (
|
{char.original_name !== char.localized_name ? (
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { getClub } from "../../api/clubs";
|
||||||
|
import Loading from "../../ui/Loading";
|
||||||
|
import Group from "../../ui/Group/Group";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
match: { params: { clubId: number } }
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
club: ClubType | null
|
||||||
|
}
|
||||||
|
|
||||||
|
class Club extends React.Component<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
club: null
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
getClub(this.props.match.params.clubId).then(club => {
|
||||||
|
this.setState({ club });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if(!this.state.club) return <Loading/>;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Group title={this.state.club.name}>
|
||||||
|
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Club;
|
||||||
@@ -43,8 +43,6 @@ class LoginModal extends React.Component<Props, State> {
|
|||||||
const { login: log, password, recaptcha } = this.state;
|
const { login: log, password, recaptcha } = this.state;
|
||||||
|
|
||||||
login(log, password, recaptcha).then(user => {
|
login(log, password, recaptcha).then(user => {
|
||||||
if(!user) return;
|
|
||||||
|
|
||||||
setUser(user);
|
setUser(user);
|
||||||
const successful = (
|
const successful = (
|
||||||
<NotificationMessage level={"success"} position={"center"}>
|
<NotificationMessage level={"success"} position={"center"}>
|
||||||
|
|||||||
@@ -4,19 +4,28 @@ import React, { FormEvent } from "react";
|
|||||||
import { updateUserAppearanceSettings } from "../../../api/users";
|
import { updateUserAppearanceSettings } from "../../../api/users";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { setUser } from "../../../store/ducks/currentUser";
|
import { setUser } from "../../../store/ducks/currentUser";
|
||||||
|
import { setSnowflakeCount, setSnowfallStatus } from "../../../store/ducks/snowfall";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setUser: SetUser
|
setUser: SetUser
|
||||||
settings: UserSettingsType
|
settings: UserSettingsType
|
||||||
|
|
||||||
|
snowfallSettings: Snowfall
|
||||||
|
setSnowflakeCount: SetSnowflakeCount
|
||||||
|
setSnowfallStatus: SetSnowflakeStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
theme: Theme
|
theme: Theme
|
||||||
|
colorName: boolean
|
||||||
|
snowflakeCount: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
class Appearance extends React.Component<Props, State> {
|
class Appearance extends React.Component<Props, State> {
|
||||||
state: State = {
|
state: State = {
|
||||||
theme: this.props.settings.theme
|
theme: this.props.settings.theme,
|
||||||
|
colorName: JSON.parse(localStorage.getItem('color-name') || 'false'),
|
||||||
|
snowflakeCount: JSON.parse(localStorage.getItem('snowflakeCount') ?? '250')
|
||||||
}
|
}
|
||||||
|
|
||||||
saveTheme = (event: FormEvent) => {
|
saveTheme = (event: FormEvent) => {
|
||||||
@@ -25,18 +34,19 @@ class Appearance extends React.Component<Props, State> {
|
|||||||
const { theme } = this.state;
|
const { theme } = this.state;
|
||||||
const { setUser } = this.props;
|
const { setUser } = this.props;
|
||||||
|
|
||||||
updateUserAppearanceSettings(theme).then(user => {
|
|
||||||
setUser(user);
|
|
||||||
|
|
||||||
localStorage.setItem('theme', theme);
|
localStorage.setItem('theme', theme);
|
||||||
document.body.setAttribute('theme', theme);
|
document.body.setAttribute('theme', theme);
|
||||||
})
|
|
||||||
|
updateUserAppearanceSettings(theme).then(user => {
|
||||||
|
setUser(user);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { theme } = this.state;
|
const { snowfallSettings, setSnowflakeCount, setSnowfallStatus } = this.props;
|
||||||
|
const { theme, colorName } = this.state;
|
||||||
|
|
||||||
return (
|
return (<>
|
||||||
<Group title={'Тема'} className={'Settings_Group'}>
|
<Group title={'Тема'} className={'Settings_Group'}>
|
||||||
<form onSubmit={this.saveTheme}>
|
<form onSubmit={this.saveTheme}>
|
||||||
<select onChange={e => this.setState({ theme: e.target.value as Theme })} value={theme}>
|
<select onChange={e => this.setState({ theme: e.target.value as Theme })} value={theme}>
|
||||||
@@ -49,15 +59,64 @@ class Appearance extends React.Component<Props, State> {
|
|||||||
<Button>Сохранить</Button>
|
<Button>Сохранить</Button>
|
||||||
</form>
|
</form>
|
||||||
</Group>
|
</Group>
|
||||||
)
|
<Group title={'Остальное'}>
|
||||||
|
<label>
|
||||||
|
Выделять ник цветом группы
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
onChange={e => {
|
||||||
|
localStorage.setItem('color-name', JSON.stringify(e.target.checked));
|
||||||
|
this.setState({ colorName: e.target.checked });
|
||||||
|
}}
|
||||||
|
checked={colorName}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</Group>
|
||||||
|
<Group title={'Снег'}>
|
||||||
|
<label>
|
||||||
|
Включить снег
|
||||||
|
<input
|
||||||
|
type={'checkbox'}
|
||||||
|
checked={snowfallSettings.snowfallStatus}
|
||||||
|
onChange={e => {
|
||||||
|
setSnowfallStatus(e.target.checked);
|
||||||
|
localStorage.setItem('snowfallStatus', JSON.stringify(e.target.checked));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<div>
|
||||||
|
Количество снежинок {snowfallSettings.snowflakeCount}
|
||||||
|
{snowfallSettings.snowflakeCount > 700 && (
|
||||||
|
<span style={{ color: 'red' }}> (использование большого количества снежинок может негативно сказаться на производительности!)</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={0}
|
||||||
|
max={1000}
|
||||||
|
value={snowfallSettings.snowflakeCount}
|
||||||
|
onChange={e => {
|
||||||
|
const value = JSON.parse(e.target.value);
|
||||||
|
setSnowflakeCount(value);
|
||||||
|
localStorage.setItem('snowflakeCount', value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</Group>
|
||||||
|
</>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
(state: StoreState) => ({
|
(state: StoreState) => ({
|
||||||
settings: state.userSettings
|
settings: state.userSettings,
|
||||||
|
snowfallSettings: state.snowfall
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
setUser: (user: UserType) => dispatch(setUser(user))
|
setUser: (user: UserType) => dispatch(setUser(user)),
|
||||||
|
setSnowflakeCount: (count: number) => dispatch(setSnowflakeCount(count)),
|
||||||
|
setSnowfallStatus: (status: boolean) => dispatch(setSnowfallStatus(status))
|
||||||
})
|
})
|
||||||
)(Appearance);
|
)(Appearance);
|
||||||
|
|||||||
@@ -41,16 +41,30 @@
|
|||||||
font-weight: 600
|
font-weight: 600
|
||||||
|
|
||||||
&_Group
|
&_Group
|
||||||
background-color: #0008
|
|
||||||
border-color: #eeeeee
|
border-color: #eeeeee
|
||||||
border-width: 1px 1px 1px 4px
|
border-width: 1px
|
||||||
border-style: solid
|
border-style: solid
|
||||||
|
color: var(--main-color)
|
||||||
|
|
||||||
border-radius: 4px
|
border-radius: 2px
|
||||||
padding: .2rem
|
padding: .2rem
|
||||||
text-align: center
|
text-align: center
|
||||||
font-size: 1.1rem
|
font-size: 1.1rem
|
||||||
font-weight: 300
|
font-weight: 300
|
||||||
|
position: relative
|
||||||
|
z-index: 1
|
||||||
|
|
||||||
|
&::before
|
||||||
|
content: ''
|
||||||
|
width: 100%
|
||||||
|
border-top-color: inherit
|
||||||
|
border-top-width: 27px
|
||||||
|
border-top-style: solid
|
||||||
|
opacity: 0.5
|
||||||
|
position: absolute
|
||||||
|
z-index: -1
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
|
||||||
&_Avatar
|
&_Avatar
|
||||||
background-size: cover
|
background-size: cover
|
||||||
@@ -80,7 +94,6 @@
|
|||||||
@media (max-width: 460px)
|
@media (max-width: 460px)
|
||||||
width: calc(100% - 1rem)
|
width: calc(100% - 1rem)
|
||||||
|
|
||||||
|
|
||||||
padding: .5rem 0
|
padding: .5rem 0
|
||||||
margin: 0 .5rem
|
margin: 0 .5rem
|
||||||
|
|
||||||
|
|||||||
+20
-12
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React, { CSSProperties } from "react";
|
||||||
import { fetchUser, fetchUserNovels, generateActivateLink } from "../../api/users";
|
import { fetchUser, fetchUserNovels, generateActivateLink } from "../../api/users";
|
||||||
import './User.sass'
|
import './User.sass'
|
||||||
import NotFoundError from "../NotFoundError";
|
import NotFoundError from "../NotFoundError";
|
||||||
@@ -78,7 +78,9 @@ class User extends React.Component<Props, State> {
|
|||||||
if(errorCode === 100) return <NotFoundError/>;
|
if(errorCode === 100) return <NotFoundError/>;
|
||||||
if(!user) return <Loading/>;
|
if(!user) return <Loading/>;
|
||||||
|
|
||||||
const style = !user.is_activated ? { marginTop: '1rem' } : {}
|
const style: CSSProperties = !user.is_activated ? { marginTop: '1rem' } : {};
|
||||||
|
const userNameStyle: CSSProperties = JSON.parse(localStorage.getItem('color-name') || 'false') && user.group.id !== 0 ? { color: user.group.color } : {};
|
||||||
|
console.log(userNameStyle, localStorage.getItem('color-name'));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'User'}>
|
<div className={'User'}>
|
||||||
@@ -88,8 +90,12 @@ class User extends React.Component<Props, State> {
|
|||||||
<p>Активируйте аккаунт, для доступа ко многим функциям. Так же актвация аккаунта позволит
|
<p>Активируйте аккаунт, для доступа ко многим функциям. Так же актвация аккаунта позволит
|
||||||
восстановить пароль.</p>
|
восстановить пароль.</p>
|
||||||
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
||||||
<Button onClick={this.sendActivateLink}
|
<Button
|
||||||
style={{ fontSize: '1.2rem' }}>Активировать!</Button>
|
onClick={this.sendActivateLink}
|
||||||
|
style={{ fontSize: '1.2rem' }}
|
||||||
|
>
|
||||||
|
Активировать!
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -101,22 +107,24 @@ class User extends React.Component<Props, State> {
|
|||||||
<div className="User__Info_Avatar"
|
<div className="User__Info_Avatar"
|
||||||
style={{ backgroundImage: `url(${user.avatar}?s=96&t=${new Date().getTime()})` }}/>
|
style={{ backgroundImage: `url(${user.avatar}?s=96&t=${new Date().getTime()})` }}/>
|
||||||
<div>
|
<div>
|
||||||
<div className='User__Info_Nick'>{user.username}</div>
|
<div className='User__Info_Nick' style={userNameStyle}>{user.username}</div>
|
||||||
<div className='User__Info_Group' style={{
|
<div className='User__Info_Group' style={{
|
||||||
borderColor: user.group.color,
|
borderColor: user.group.color
|
||||||
color: user.group.color
|
}}>
|
||||||
}}>{user.group.name}</div>
|
{user.group.name}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>) : (
|
</div>) : (
|
||||||
<div className={'User__Info_Main'}>
|
<div className={'User__Info_Main'}>
|
||||||
<div className="User__Info_Avatar"
|
<div className="User__Info_Avatar"
|
||||||
style={{ backgroundImage: `url(${user.avatar}?s=96&t=${new Date().getTime()})` }}/>
|
style={{ backgroundImage: `url(${user.avatar}?s=96&t=${new Date().getTime()})` }}/>
|
||||||
<div>
|
<div>
|
||||||
<div className='User__Info_Nick'>{user.username}</div>
|
<div className='User__Info_Nick' style={userNameStyle}>{user.username}</div>
|
||||||
<div className='User__Info_Group' style={{
|
<div className='User__Info_Group' style={{
|
||||||
borderColor: user.group.color,
|
borderColor: user.group.color
|
||||||
color: user.group.color
|
}}>
|
||||||
}}>{user.group.name}</div>
|
{user.group.name}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,5 +3,8 @@ import currentUser from './currentUser'
|
|||||||
import modal from "./modal";
|
import modal from "./modal";
|
||||||
import notifications from './notifications'
|
import notifications from './notifications'
|
||||||
import userSettings from './userSettings';
|
import userSettings from './userSettings';
|
||||||
|
import snowfall from './snowfall';
|
||||||
|
|
||||||
export default combineReducers({ currentUser, modal, notifications, userSettings })
|
export default combineReducers({
|
||||||
|
currentUser, modal, notifications, userSettings, snowfall
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
const SET_SNOWFLAKE_COUNT = 'unmei/snowfall/SET_SNOWFLAKE_COUNT';
|
||||||
|
const SET_SNOWFALL_STATUS = 'unmei/snowfall/SET_SNOWFALL_STATUS';
|
||||||
|
|
||||||
|
const initialState: Snowfall = {
|
||||||
|
snowflakeCount: JSON.parse(localStorage.getItem('snowflakeCount') ?? '250'),
|
||||||
|
snowfallStatus: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const reducer = (state = initialState, action: Action<Snowfall>) => {
|
||||||
|
if(!action.payload) return state;
|
||||||
|
const payload = action.payload;
|
||||||
|
switch(action.type) {
|
||||||
|
case SET_SNOWFLAKE_COUNT:
|
||||||
|
localStorage.setItem('snowflakeCount', JSON.stringify(payload.snowflakeCount));
|
||||||
|
return Object.assign({}, state, { snowflakeCount: payload.snowflakeCount });
|
||||||
|
case SET_SNOWFALL_STATUS:
|
||||||
|
return Object.assign({}, state, { snowfallStatus: payload.snowfallStatus });
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setSnowflakeCount = (snowflakeCount: number) => ({
|
||||||
|
type: SET_SNOWFLAKE_COUNT,
|
||||||
|
payload: {
|
||||||
|
snowflakeCount
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const setSnowfallStatus = (snowfallStatus: boolean) => ({
|
||||||
|
type: SET_SNOWFALL_STATUS,
|
||||||
|
payload: {
|
||||||
|
snowfallStatus
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default reducer;
|
||||||
|
export { setSnowflakeCount, setSnowfallStatus }
|
||||||
Vendored
+15
@@ -1,3 +1,18 @@
|
|||||||
|
type Theme = 'dark' | 'blue' | 'red' | 'green' | 'light' | 'win95';
|
||||||
|
type ExitStatus = 'came_out';
|
||||||
|
type NovelPlatform = 'win';
|
||||||
|
|
||||||
|
type ApiError = {
|
||||||
|
code: number
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApiResponse<T> = {
|
||||||
|
error: boolean
|
||||||
|
error_data?: ApiError | string
|
||||||
|
data?: T
|
||||||
|
}
|
||||||
|
|
||||||
type VersionResponse = {
|
type VersionResponse = {
|
||||||
version: string
|
version: string
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+8
@@ -1,8 +1,14 @@
|
|||||||
|
type Snowfall = {
|
||||||
|
snowflakeCount: number
|
||||||
|
snowfallStatus: boolean
|
||||||
|
};
|
||||||
|
|
||||||
type StoreState = {
|
type StoreState = {
|
||||||
currentUser: UserType
|
currentUser: UserType
|
||||||
userSettings: UserSettingsType
|
userSettings: UserSettingsType
|
||||||
notifications: React.ReactNode[]
|
notifications: React.ReactNode[]
|
||||||
modal: React.ReactNode
|
modal: React.ReactNode
|
||||||
|
snowfall: Snowfall
|
||||||
}
|
}
|
||||||
|
|
||||||
type Action<T> = {
|
type Action<T> = {
|
||||||
@@ -16,3 +22,5 @@ type SetSettings = (settings: UserSettingsType) => void
|
|||||||
type SetModal = (modal: React.ReactNode | null) => void
|
type SetModal = (modal: React.ReactNode | null) => void
|
||||||
type HideModal = () => void
|
type HideModal = () => void
|
||||||
type AddNotification = (notification: React.ReactNode) => void
|
type AddNotification = (notification: React.ReactNode) => void
|
||||||
|
type SetSnowflakeCount = (snowflakeCount: number) => void
|
||||||
|
type SetSnowflakeStatus = (snowfallStatus: boolean) => void
|
||||||
|
|||||||
Vendored
+18
-25
@@ -1,16 +1,14 @@
|
|||||||
type Theme = 'dark' | 'blue' | 'red' | 'green' | 'light'
|
type UserType = {
|
||||||
type ExitStatus = 'came_out'
|
authorized: boolean
|
||||||
type NovelPlatform = 'win'
|
id: number
|
||||||
|
username: string
|
||||||
type ApiError = {
|
email?: string
|
||||||
code: number
|
avatar: string
|
||||||
text: string
|
cover: string
|
||||||
}
|
group: UserGroupType
|
||||||
|
is_superuser: boolean
|
||||||
type ApiResponse<T> = {
|
is_activated: boolean
|
||||||
error: boolean
|
is_banned: boolean
|
||||||
error_data?: ApiError | string
|
|
||||||
data?: T
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NovelType = {
|
type NovelType = {
|
||||||
@@ -36,18 +34,6 @@ type CharacterType = {
|
|||||||
main: boolean
|
main: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserType = {
|
|
||||||
authorized: boolean
|
|
||||||
id: number
|
|
||||||
username: string
|
|
||||||
email?: string
|
|
||||||
avatar: string
|
|
||||||
cover: string
|
|
||||||
group: UserGroupType
|
|
||||||
is_superuser: boolean
|
|
||||||
is_activated: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserSettingsType = {
|
type UserSettingsType = {
|
||||||
avatar: string
|
avatar: string
|
||||||
theme: Theme
|
theme: Theme
|
||||||
@@ -89,3 +75,10 @@ type PostType = {
|
|||||||
author: string
|
author: string
|
||||||
author_id: number
|
author_id: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClubType = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
avatar: string
|
||||||
|
ownerId: number
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import classNames from "../../classNames";
|
|||||||
import './Group.sass'
|
import './Group.sass'
|
||||||
|
|
||||||
interface Props extends HTMLAttributes<HTMLElement> {
|
interface Props extends HTMLAttributes<HTMLElement> {
|
||||||
title: string
|
title?: string
|
||||||
direction?: 'column' | 'row'
|
direction?: 'column' | 'row'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ const Group = (restProps: Props) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className={classNames('Group', className)} {...props}>
|
<div className={classNames('Group', className)} {...props}>
|
||||||
<div className="Group__Title">{title}</div>
|
{title && <div className="Group__Title">{title}</div>}
|
||||||
<div style={style} className={'Group__Content'}>
|
<div style={style} className={'Group__Content'}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user