изменений опять просто ёбнешься. когда я успел?
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
const baseUrl = process.env.NODE_ENV === "development" ? 'http://localhost:8080/v1' : 'https://api.unmei.space/v1';
|
||||
const baseUrl = process.env.NODE_ENV === "development" ? 'http://192.168.0.103:8080/v1' : 'https://api.unmei.space/v1';
|
||||
|
||||
const request: ApiRequest = async (url: string, method: string, body?: string | FormData) => {
|
||||
let bUrl = baseUrl;
|
||||
|
||||
@@ -45,7 +45,7 @@ class App extends React.Component<Props, State> {
|
||||
if(cachedTheme)
|
||||
document.body.setAttribute('theme', cachedTheme);
|
||||
|
||||
const { setUser } = this.props;
|
||||
const { setUser } = props;
|
||||
fetchCurrentUser().then((user: UserType) => {
|
||||
setUser(user);
|
||||
fetchUserSettings().then((settings: UserSettingsType) => {
|
||||
@@ -55,7 +55,8 @@ class App extends React.Component<Props, State> {
|
||||
}
|
||||
});
|
||||
}).catch((err: ApiError) => {
|
||||
if(err.code !== 3 && err.text) console.error(err.text);
|
||||
if(err.code !== 3 && err.text)
|
||||
console.error(err.text);
|
||||
});
|
||||
|
||||
parser.registerTag('spoiler', SpoilerTag);
|
||||
@@ -67,6 +68,8 @@ class App extends React.Component<Props, State> {
|
||||
|
||||
caches.keys().then(names => {
|
||||
names.forEach(name => caches.delete(name).catch(console.error))
|
||||
}).then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,29 +5,13 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faDiscord, faVk, faTelegramPlane } from '@fortawesome/free-brands-svg-icons';
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
const secretChangeTheme = () => {
|
||||
const theme = localStorage.getItem('theme');
|
||||
if(theme == null) {
|
||||
localStorage.setItem('theme', 'light');
|
||||
document.body.setAttribute('theme', 'light')
|
||||
} else if(theme === 'light') {
|
||||
localStorage.setItem('theme', 'dark');
|
||||
document.body.setAttribute('theme', 'dark')
|
||||
} else if(theme === 'dark') {
|
||||
localStorage.setItem('theme', 'light');
|
||||
document.body.setAttribute('theme', 'light')
|
||||
}
|
||||
if(theme !== 'light') {
|
||||
console.log('Светлая тема находится в разработке! Не все может корректно отображаться.\nК тому же, эта кнопка секретная. Ты знал, на что шел ;)');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='Footer'>
|
||||
<footer className='Footer__Tabs'>
|
||||
<div className="Footer__Tab">
|
||||
<div className="Footer__Tab_Title" onClick={secretChangeTheme}>Команда</div>
|
||||
<div className="Footer__Tab_Title">Команда</div>
|
||||
<Link to={'/user/1'} className={'Link'}>Nix13</Link>
|
||||
<Link to={'/user/6'} className={'Link'}>RonFall</Link>
|
||||
</div>
|
||||
<div className="Footer__Tab">
|
||||
<div className="Footer__Tab_Title">Соцсети</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import NovelItem from '../../../ui/NovelItem/NovelItem';
|
||||
import {capitalize, generateClassName, getRandomInt} from "../../../utils";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faAlignJustify, faThLarge} from "@fortawesome/free-solid-svg-icons";
|
||||
import Button from "../../../ui/Button/Button";
|
||||
|
||||
type Props = {
|
||||
match: { params: { userId: number } }
|
||||
@@ -73,7 +74,7 @@ class UserNovels extends React.Component<Props, State> {
|
||||
{planned.length > 0 && <div className={'UserNovels__List'}>
|
||||
<div className="UserNovels__List_Title">
|
||||
<div style={{ marginRight: '1rem' }}>Запланированные</div>
|
||||
<button onClick={this.getRandomNovel}>Рандомная новелла</button>
|
||||
<Button onClick={this.getRandomNovel}>Рандомная новелла</Button>
|
||||
</div>
|
||||
<div className={generateClassName("UserNovels__List_Novels", capitalize(viewType) || 'Grid')}>
|
||||
{planned.map((novel, id) => <NovelItem {...novel} key={novel.id} novelId={id+1} viewType={viewType}/>)}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import Button from "../../../ui/Button/Button";
|
||||
import Group from "../../../ui/Group/Group";
|
||||
import React, {FormEvent} from "react";
|
||||
import {fetchUserSettings, updateUserAppearanceSettings} from "../../../api/users";
|
||||
import {setUser} from "../../../store/actions";
|
||||
|
||||
type State = {
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
class Appearance extends React.Component<{}, State> {
|
||||
state: State = {
|
||||
theme: 'dark'
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
fetchUserSettings().then(settings => {
|
||||
this.setState({ theme: settings.theme })
|
||||
})
|
||||
}
|
||||
|
||||
saveTheme = (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
const { theme } = this.state;
|
||||
updateUserAppearanceSettings(theme).then(user => {
|
||||
setUser(user);
|
||||
|
||||
localStorage.setItem('theme', theme);
|
||||
document.body.setAttribute('theme', theme);
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { theme } = this.state;
|
||||
|
||||
return (
|
||||
<Group title={'Тема'} className={'Settings_Group'}>
|
||||
<form onSubmit={this.saveTheme}>
|
||||
<select onChange={e => this.setState({ theme: e.target.value as Theme })} value={theme}>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="blue">Blue</option>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="light">Light</option>
|
||||
</select>
|
||||
|
||||
<Button>Сохранить</Button>
|
||||
</form>
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Appearance;
|
||||
@@ -0,0 +1,65 @@
|
||||
import React, {ChangeEvent, FormEvent} from "react";
|
||||
import Button from "../../../ui/Button/Button";
|
||||
import Group from "../../../ui/Group/Group";
|
||||
import {fetchUserSettings, updateUserGeneralSettings, uploadAvatar} from "../../../api/users";
|
||||
|
||||
type Props = {
|
||||
setUser: (user: UserType) => void
|
||||
}
|
||||
|
||||
type State = {
|
||||
// Avatar
|
||||
useGravatar: boolean
|
||||
avatar: File | null
|
||||
}
|
||||
|
||||
class General extends React.Component<Props, State> {
|
||||
state: State = {
|
||||
useGravatar: false, avatar: null
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
fetchUserSettings().then(settings => {
|
||||
this.setState({ useGravatar: settings.use_gravatar })
|
||||
})
|
||||
}
|
||||
|
||||
onChangeAvatar = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const files = event.target.files;
|
||||
if(!files) return;
|
||||
this.setState({ avatar: files[0] });
|
||||
}
|
||||
|
||||
saveAvatar = (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
const { avatar, useGravatar} = this.state;
|
||||
const { setUser } = this.props;
|
||||
|
||||
if(avatar) {
|
||||
const formData = new FormData();
|
||||
formData.append('avatar', avatar);
|
||||
|
||||
uploadAvatar(formData).then(avatarUrl => {
|
||||
updateUserGeneralSettings(useGravatar, avatarUrl).then(setUser)
|
||||
})
|
||||
} else {
|
||||
updateUserGeneralSettings(useGravatar, '').then(setUser)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { useGravatar } = this.state;
|
||||
|
||||
return (
|
||||
<Group title={'Аватар'} className={'Settings_Group'}>
|
||||
<form onSubmit={this.saveAvatar}>
|
||||
<label>Использовать Gravatar <input type="checkbox" onChange={event => this.setState({ useGravatar: event.target.checked })} checked={useGravatar}/></label>
|
||||
<label>Загрузить аватар <input type="file" onChange={this.onChangeAvatar} disabled={useGravatar}/></label>
|
||||
<Button>Сохранить</Button>
|
||||
</form>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default General;
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
import Group from "../../../ui/Group/Group";
|
||||
import Input from "../../../ui/Input/Input";
|
||||
import Button from "../../../ui/Button/Button";
|
||||
|
||||
type State = {
|
||||
oldEmail: string
|
||||
email1: string
|
||||
email2: string
|
||||
error: string
|
||||
}
|
||||
|
||||
class Security extends React.Component<{}, State> {
|
||||
state: State = {
|
||||
oldEmail: '', email1: '', email2: '',
|
||||
error: ''
|
||||
}
|
||||
|
||||
changeEmail = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const { oldEmail, email1, email2 } = this.state;
|
||||
let error = '';
|
||||
if(!oldEmail) error += 'Введите старый E-mail\n';
|
||||
if(!email1) error += 'Введите новый E-mail\n';
|
||||
if(!email2) error += 'Введите повтор нового E-mail\n';
|
||||
if(email1 !== email2) error += 'E-mail не совпадают\n';
|
||||
if(oldEmail === email1) error += 'Старый и новый E-mail не должны совпадать';
|
||||
|
||||
if(error) {
|
||||
this.setState({ error });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { oldEmail, email1, email2, error } = this.state;
|
||||
|
||||
return (
|
||||
<Group title={'Смена E-mail'} className={'Settings_Group'}>
|
||||
{error && <pre className={'Error'}>{error}</pre>}
|
||||
<form onSubmit={this.changeEmail}>
|
||||
<Input
|
||||
placeholder={'Старый E-mail'}
|
||||
type={'email'}
|
||||
value={oldEmail}
|
||||
onChange={e => this.setState({ oldEmail: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder={'Новый E-mail'}
|
||||
type={'email'}
|
||||
value={email1}
|
||||
onChange={e => this.setState({ email1: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
placeholder={'Новый E-mail ещё раз'}
|
||||
type={'email'}
|
||||
value={email2}
|
||||
onChange={e => this.setState({ email1: e.target.value })}
|
||||
/>
|
||||
<Button>Сохранить</Button>
|
||||
</form>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Security;
|
||||
@@ -1,5 +1,7 @@
|
||||
.Group
|
||||
.Settings_Avatar
|
||||
.Settings_Group
|
||||
font-size: 1.1rem
|
||||
|
||||
form
|
||||
display: flex
|
||||
flex-direction: column
|
||||
@@ -8,5 +10,5 @@
|
||||
input
|
||||
margin: .5rem 0
|
||||
|
||||
.Button
|
||||
flex-grow: 0
|
||||
input, .Button
|
||||
font-size: 1.1rem
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import React, {ChangeEvent, FormEvent} from "react";
|
||||
import React from "react";
|
||||
import Group from "../../../ui/Group/Group";
|
||||
import {connect} from "react-redux";
|
||||
import NotFoundError from "../../NotFoundError";
|
||||
import Button from "../../../ui/Button/Button";
|
||||
import './Settings.sass';
|
||||
import {
|
||||
fetchUserSettings,
|
||||
updateUserAppearanceSettings,
|
||||
updateUserGeneralSettings,
|
||||
uploadAvatar
|
||||
} from "../../../api/users";
|
||||
import {setUser} from "../../../store/actions";
|
||||
import Tabs from "../../../ui/Tabs/Tabs";
|
||||
import TabItem from "../../../ui/Tabs/TabItem";
|
||||
import Appearance from "./Appearance";
|
||||
import General from "./General";
|
||||
import Security from "./Security";
|
||||
|
||||
enum TabsNames {
|
||||
General,
|
||||
Security,
|
||||
Appearance
|
||||
}
|
||||
|
||||
@@ -26,69 +23,22 @@ type Props = {
|
||||
}
|
||||
|
||||
type State = {
|
||||
// Avatar
|
||||
useGravatar: boolean
|
||||
avatar: File | null
|
||||
theme: Theme
|
||||
|
||||
currentTab: TabsNames
|
||||
}
|
||||
|
||||
class Settings extends React.Component<Props, State> {
|
||||
state: State = {
|
||||
useGravatar: false, avatar: null, theme: "dark",
|
||||
currentTab: TabsNames.General
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
fetchUserSettings().then(settings => {
|
||||
this.setState({ useGravatar: settings.use_gravatar })
|
||||
})
|
||||
}
|
||||
|
||||
onChangeAvatar = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const files = event.target.files;
|
||||
if(!files) return;
|
||||
this.setState({ avatar: files[0] });
|
||||
}
|
||||
|
||||
saveAvatar = (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
const { avatar, useGravatar} = this.state;
|
||||
const { setUser } = this.props;
|
||||
|
||||
if(avatar) {
|
||||
const formData = new FormData();
|
||||
formData.append('avatar', avatar);
|
||||
|
||||
uploadAvatar(formData).then(avatarUrl => {
|
||||
updateUserGeneralSettings(useGravatar, avatarUrl).then(setUser)
|
||||
})
|
||||
} else {
|
||||
updateUserGeneralSettings(useGravatar, '').then(setUser)
|
||||
}
|
||||
}
|
||||
|
||||
saveTheme = (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
const { theme } = this.state;
|
||||
updateUserAppearanceSettings(theme).then(user => {
|
||||
setUser(user);
|
||||
|
||||
localStorage.setItem('theme', theme);
|
||||
document.body.setAttribute('theme', theme);
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { currentUser, match: { params: { userId } } } = this.props;
|
||||
const { useGravatar, currentTab, theme } = this.state
|
||||
const { currentTab } = this.state
|
||||
|
||||
if(parseInt(userId) !== currentUser.id) return <NotFoundError/>;
|
||||
|
||||
return (
|
||||
<Group title={'Настройки'}>
|
||||
<Group title={''}>
|
||||
<Tabs>
|
||||
<TabItem
|
||||
selected={currentTab === TabsNames.General}
|
||||
@@ -96,6 +46,12 @@ class Settings extends React.Component<Props, State> {
|
||||
>
|
||||
Главное
|
||||
</TabItem>
|
||||
<TabItem
|
||||
selected={currentTab === TabsNames.Security}
|
||||
onClick={() => this.setState({ currentTab: TabsNames.Security })}
|
||||
>
|
||||
Безопастность
|
||||
</TabItem>
|
||||
<TabItem
|
||||
selected={currentTab === TabsNames.Appearance}
|
||||
onClick={() => this.setState({ currentTab: TabsNames.Appearance })}
|
||||
@@ -104,28 +60,13 @@ class Settings extends React.Component<Props, State> {
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
{currentTab === TabsNames.General && (
|
||||
<Group title={'Аватар'} className={'Settings_Avatar'}>
|
||||
<form onSubmit={this.saveAvatar}>
|
||||
<label>Использовать Gravatar <input type="checkbox" onChange={event => this.setState({ useGravatar: event.target.checked })} checked={useGravatar}/></label>
|
||||
<label>Загрузить аватар <input type="file" onChange={this.onChangeAvatar} disabled={useGravatar}/></label>
|
||||
<Button>Сохранить</Button>
|
||||
</form>
|
||||
</Group>
|
||||
<General setUser={setUser}/>
|
||||
)}
|
||||
{currentTab === TabsNames.Security && (
|
||||
<Security/>
|
||||
)}
|
||||
{currentTab === TabsNames.Appearance && (
|
||||
<Group title={'Тема'} className={'Settings_Avatar'}>
|
||||
<form onSubmit={this.saveTheme}>
|
||||
<select onChange={e => this.setState({ theme: e.target.value as Theme })} value={theme}>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="blue">Blue</option>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="light">Light</option>
|
||||
</select>
|
||||
|
||||
<Button>Сохранить</Button>
|
||||
</form>
|
||||
</Group>
|
||||
<Appearance/>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
|
||||
+22
-8
@@ -27,19 +27,32 @@ class User extends React.Component<Props, State> {
|
||||
user: null, novels: [], errorCode: null
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const params = this.props.match.params;
|
||||
|
||||
fetchUser(parseInt(params.userId)).then((user: UserType) => {
|
||||
getAndSetUser = (id: number) => {
|
||||
fetchUser(id).then(user => {
|
||||
this.setState({ user });
|
||||
document.title = `${user.username} / Unmei`;
|
||||
|
||||
fetchUserNovels(parseInt(params.userId)).then((novels: NovelType[]) => {
|
||||
fetchUserNovels(id).then(novels => {
|
||||
this.setState({ novels });
|
||||
}).catch((err: ApiError) => console.error(err));
|
||||
}).catch((err: ApiError) => this.setState({ errorCode: err.code }));
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>, _?: any) {
|
||||
const newUserId = parseInt(this.props.match.params.userId);
|
||||
const prevUserId = parseInt(prevProps.match.params.userId);
|
||||
|
||||
if(newUserId !== prevUserId) {
|
||||
this.getAndSetUser(newUserId);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const params = this.props.match.params;
|
||||
|
||||
this.getAndSetUser(parseInt(params.userId))
|
||||
}
|
||||
|
||||
sendActivateLink = () => {
|
||||
generateActivateLink().then(() => {
|
||||
const notification = (
|
||||
@@ -48,10 +61,11 @@ class User extends React.Component<Props, State> {
|
||||
</NotificationMessage>
|
||||
);
|
||||
this.props.addNotification(notification);
|
||||
}).catch(err => {
|
||||
}).catch((err: ApiError) => {
|
||||
const notification = (
|
||||
<NotificationMessage level={"success"}>
|
||||
Сообщение успешно отправлено!
|
||||
<NotificationMessage level={"error"}>
|
||||
Произошла ошибка! №{err.code} <br/>
|
||||
{err.text}
|
||||
</NotificationMessage>
|
||||
);
|
||||
this.props.addNotification(notification);
|
||||
|
||||
@@ -9,3 +9,6 @@
|
||||
font-size: 1.2rem
|
||||
font-weight: 600
|
||||
margin-bottom: .5rem
|
||||
|
||||
&__Content
|
||||
margin-left: .5rem
|
||||
|
||||
@@ -19,7 +19,7 @@ const Group = (restProps: Props) => {
|
||||
return (
|
||||
<div className={classNames('Group', className)} {...props}>
|
||||
<div className="Group__Title">{title}</div>
|
||||
<div style={style}>
|
||||
<div style={style} className={'Group__Content'}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, {InputHTMLAttributes} from "react";
|
||||
import classNames from "../../classNames";
|
||||
import './Input.sass';
|
||||
|
||||
const Input = (restProps: InputHTMLAttributes<HTMLElement>) => {
|
||||
const Input = (restProps: InputHTMLAttributes<HTMLInputElement>) => {
|
||||
const { className, ...props } = restProps;
|
||||
return (
|
||||
<input className={classNames('Input', className)} {...props} />
|
||||
|
||||
+9
-1
@@ -20,4 +20,12 @@ const generateClassName = (baseClassName: string, ...classNames: string[]) => {
|
||||
else return `${baseClassName} ${classNames.join(" ")}`
|
||||
}
|
||||
|
||||
export { capitalize, getRandomInt, hasPermission, hasAccessToAdminPanel, generateClassName };
|
||||
const getImage: (path: string) => string = (path: string) => {
|
||||
let cdnUrl = process.env['CDN_URL'];
|
||||
if(!cdnUrl) return path;
|
||||
if(cdnUrl.endsWith('/')) cdnUrl = cdnUrl.slice(cdnUrl.length-1);
|
||||
if(path.startsWith('/')) path = path.slice(1, path.length);
|
||||
return `${cdnUrl}/${path}`;
|
||||
}
|
||||
|
||||
export { capitalize, getRandomInt, hasPermission, hasAccessToAdminPanel, generateClassName, getImage };
|
||||
|
||||
Reference in New Issue
Block a user