v1.1; some profile beauty; snowfall

This commit is contained in:
2020-12-22 01:01:47 +03:00
parent 3b54084212
commit a8f62159c7
19 changed files with 710 additions and 631 deletions
+4 -3
View File
@@ -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
View File
@@ -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: 'Запланировано',
+3
View File
@@ -0,0 +1,3 @@
import { get } from "./api";
export const getClub = (id: number) => get<ClubType>(`/clubs/${id}`);
+13 -2
View File
@@ -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
View File
@@ -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)),
+1 -3
View File
@@ -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
+1 -1
View File
@@ -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 ? (
+37
View File
@@ -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;
-2
View File
@@ -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"}>
+69 -10
View File
@@ -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;
localStorage.setItem('theme', theme);
document.body.setAttribute('theme', theme);
updateUserAppearanceSettings(theme).then(user => { updateUserAppearanceSettings(theme).then(user => {
setUser(user); setUser(user);
});
localStorage.setItem('theme', theme);
document.body.setAttribute('theme', theme);
})
} }
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);
+17 -4
View File
@@ -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
View File
@@ -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>
)} )}
+4 -1
View File
@@ -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
});
+37
View File
@@ -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 }
+15
View File
@@ -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
} }
+8
View File
@@ -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
+18 -25
View File
@@ -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
}
+2 -2
View File
@@ -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>
+416 -544
View File
File diff suppressed because it is too large Load Diff