Skip to content

Commit f76b1c0

Browse files
authored
Merge pull request #108 from doctorixx/master
Add skeleton loading and other fixes
2 parents 08a314b + 8d25959 commit f76b1c0

19 files changed

Lines changed: 396 additions & 210 deletions

File tree

BACKEND_V2/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ dependencies {
3737
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
3838
implementation("org.springframework.boot:spring-boot-starter-web")
3939
implementation("org.springframework.boot:spring-boot-starter-security")
40-
implementation("org.springframework.boot:spring-boot-starter-actuator")
4140

4241
// Kotlin
4342
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package ru.codebattles.backend.dto
2+
3+
4+
import io.swagger.v3.oas.annotations.media.Schema
5+
import org.springframework.security.core.GrantedAuthority
6+
import org.springframework.security.core.authority.SimpleGrantedAuthority
7+
8+
data class ExtendedUserDto(
9+
@Schema(description = "Unique identifier of the user", example = "1")
10+
val id: Long,
11+
12+
@Schema(description = "Username of the user", example = "john_doe")
13+
val username: String,
14+
15+
@Schema(description = "User display name", example = "John Doe")
16+
val name: String,
17+
18+
val roles: List<String>,
19+
20+
21+
){
22+
fun getIsAdmin(): Boolean {
23+
return roles.contains("ROLE_ADMIN") || roles.contains("ADMIN")
24+
}
25+
}

BACKEND_V2/src/main/kotlin/ru/codebattles/backend/dto/UserDto.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ data class UserDto(
99

1010
@Schema(description = "Username of the user", example = "john_doe")
1111
val username: String,
12+
13+
@Schema(description = "User display name", example = "John Doe")
14+
val name: String,
1215
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package ru.codebattles.backend.dto.mapper
2+
3+
import org.mapstruct.BeanMapping
4+
import org.mapstruct.Mapper
5+
import org.mapstruct.MappingTarget
6+
import org.mapstruct.NullValuePropertyMappingStrategy
7+
import ru.codebattles.backend.dto.ExtendedUserDto
8+
import ru.codebattles.backend.dto.UserDto
9+
import ru.codebattles.backend.dto.UserProfileEditDto
10+
import ru.codebattles.backend.dto.mapper.core.AbstractMapper
11+
import ru.codebattles.backend.entity.User
12+
13+
14+
@Mapper(componentModel = "spring")
15+
interface ExtendedUserMapper : AbstractMapper<User, ExtendedUserDto>

BACKEND_V2/src/main/kotlin/ru/codebattles/backend/web/controllers/UsersController.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal
99
import org.springframework.web.bind.annotation.*
1010
import ru.codebattles.backend.dto.ChangePasswordDto
1111
import ru.codebattles.backend.dto.CreateUserDto
12+
import ru.codebattles.backend.dto.ExtendedUserDto
1213
import ru.codebattles.backend.dto.UserDto
14+
import ru.codebattles.backend.dto.mapper.ExtendedUserMapper
1315
import ru.codebattles.backend.dto.mapper.UserMapper
1416
import ru.codebattles.backend.entity.User
1517
import ru.codebattles.backend.repository.UserRepository
1618
import ru.codebattles.backend.services.CompetitionService
1719
import ru.codebattles.backend.services.UserService
1820
import ru.codebattles.backend.web.entity.LinkUserRequest
1921
import ru.codebattles.backend.web.entity.OkResponse
20-
import java.util.*
2122

2223
@Tag(name = "Users", description = "Endpoints for managing users")
2324
@RestController
@@ -28,14 +29,15 @@ class UsersController(
2829
val userMapper: UserMapper,
2930
private val userService: UserService,
3031
private val competitionService: CompetitionService,
32+
private val extendedUserMapper: ExtendedUserMapper,
3133
) {
3234
@Operation(
3335
summary = "Get current user",
3436
description = "Retrieves current user."
3537
)
3638
@GetMapping("me")
37-
fun getProfile(@AuthenticationPrincipal user: User): Optional<User> {
38-
return userRepository.findById(user.id!!)
39+
fun getProfile(@AuthenticationPrincipal user: User): ExtendedUserDto {
40+
return extendedUserMapper.toDto(user)
3941
}
4042

4143

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
3+
4+
export const Loader = <p className="card-text placeholder-glow">
5+
<span className="placeholder col-7"></span>
6+
<span className="placeholder col-6"></span>
7+
<span className="placeholder col-8"></span>
8+
</p>
9+
10+
export const SingleLineBigLoader = <p className="card-text placeholder-glow">
11+
<span className="placeholder col-12"></span>
12+
</p>
13+
14+
export const SingleLineMediumLoader = <p className="card-text placeholder-glow">
15+
<span className="placeholder col-7"></span>
16+
</p>
17+
18+
export const SingleLineSmallLoader = <p className="card-text placeholder-glow">
19+
<span className="placeholder col-5"></span>
20+
</p>
21+
22+
export const SingleLineLittleLoader = <p className="card-text placeholder-glow">
23+
<span className="placeholder col-3"></span>
24+
</p>
25+
26+
const LoadingWrapper = ({loading, children, loader=Loader}) => {
27+
return (
28+
<>
29+
{/*<p>{JSON.stringify(loading)}</p>*/}
30+
{loading? (loader) : (children)}
31+
</>
32+
);
33+
};
34+
35+
export default LoadingWrapper;

FRONTEND_V2/src/components/ViewSendComponent.jsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import ResponsiveTable from "./bootstrap/ResponsiveTable.jsx";
44
import Card from "./bootstrap/Card.jsx";
55
import PropTypes from "prop-types";
66
import { useTranslation } from 'react-i18next';
7+
import LoadingWrapper from "./LoadingWrapper.jsx";
78

8-
export const ViewSendComponent = ({data}) => {
9+
export const ViewSendComponent = ({data, loading=false}) => {
910
const { t } = useTranslation();
1011

1112
const colorsByResult = {
@@ -22,11 +23,13 @@ export const ViewSendComponent = ({data}) => {
2223
return (
2324
<Card>
2425
<h3>{t('viewSend.analysis')}</h3>
25-
<p><b>{t('viewSend.language')}:</b> {data?.checker?.displayName}</p>
26-
<b>{t('viewSend.sourceCode')}:</b>
27-
<LazySyntaxHighlight lang={data?.checker?.languageHighlightName}>
28-
{data.code}
29-
</LazySyntaxHighlight>
26+
<LoadingWrapper loading={loading}>
27+
<p><b>{t('viewSend.language')}:</b> {data?.checker?.displayName}</p>
28+
<b>{t('viewSend.sourceCode')}:</b>
29+
<LazySyntaxHighlight lang={data?.checker?.languageHighlightName}>
30+
{data.code}
31+
</LazySyntaxHighlight>
32+
</LoadingWrapper>
3033
<div className="my-4"></div>
3134
<ResponsiveTable>
3235
<thead>
@@ -58,4 +61,5 @@ export const ViewSendComponent = ({data}) => {
5861

5962
ViewSendComponent.propTypes = {
6063
data: PropTypes.object,
64+
loading: PropTypes.bool
6165
}

FRONTEND_V2/src/components/form_impl/ProblemsForm.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import { InputFormElement } from "../forms/InputFormElement.jsx";
33
import PropTypes from "prop-types";
44
import { useTranslation } from 'react-i18next';
5+
import {TextareaFormElement} from "../forms/TextareaFormElement.jsx";
56

67
export const ProblemsForm = ({ form, testsArray, examplesArray }) => {
78
const { t } = useTranslation();
@@ -19,13 +20,13 @@ export const ProblemsForm = ({ form, testsArray, examplesArray }) => {
1920
name={"name"}
2021
args={{ required: t('adminProblems.nameRequired') }}
2122
/>
22-
<InputFormElement
23+
<TextareaFormElement
2324
displayName={t('adminProblems.description')}
2425
name={"description"}
2526
args={{ required: t('adminProblems.descriptionRequired') }}
2627
/>
27-
<InputFormElement displayName={t('adminProblems.inData')} name={"inData"} />
28-
<InputFormElement displayName={t('adminProblems.outData')} name={"outData"} />
28+
<TextareaFormElement displayName={t('adminProblems.inData')} name={"inData"} />
29+
<TextareaFormElement displayName={t('adminProblems.outData')} name={"outData"} />
2930

3031
{/* Tests Section */}
3132
<div className="mb-3">
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, {useContext, useId} from 'react';
2+
import {FormContext} from "./FormContext.jsx";
3+
import PropTypes from "prop-types";
4+
5+
export const TextareaFormElement = ({displayName, name, args, helpText, readonly = false, disabled = false}) => {
6+
const formInputId = useId()
7+
8+
const form = useContext(FormContext);
9+
10+
const {
11+
register,
12+
formState: {errors}
13+
} = form
14+
15+
16+
return (
17+
<div className="mb-3">
18+
<label htmlFor={formInputId} className="form-label">{displayName}</label>
19+
<textarea
20+
id={formInputId}
21+
className={`form-control ${errors[name] ? 'is-invalid' : ''}`}
22+
{...register(name, args)}
23+
disabled={disabled}
24+
readOnly={readonly}
25+
/>
26+
{helpText &&
27+
<small className="form-text text-muted">{helpText}</small>
28+
}
29+
30+
{errors[name] && (
31+
<div className="invalid-feedback">{errors[name].message}</div>
32+
)}
33+
</div>
34+
);
35+
};
36+
37+
38+
TextareaFormElement.propTypes = {
39+
displayName: PropTypes.string,
40+
name: PropTypes.string.isRequired,
41+
helpText: PropTypes.string,
42+
args: PropTypes.object,
43+
readonly: PropTypes.bool,
44+
disabled: PropTypes.bool,
45+
}

FRONTEND_V2/src/hooks/useGetAPI.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ function useCachedGetAPI(
1717
const [data, setData] = useState(defaultState);
1818
const navigate = useNavigate()
1919

20+
21+
const [hasData, updateHasData] = useState(false)
22+
23+
useEffect(() => {
24+
return () => {
25+
updateHasData(data === defaultState)
26+
};
27+
}, [data, defaultState]);
28+
29+
2030
const apiGetData = () => {
2131
axiosInstance
2232
.get(url)
@@ -51,7 +61,7 @@ function useCachedGetAPI(
5161
getData();
5262
}, []);
5363

54-
return [data, updateCallback];
64+
return [data, updateCallback, hasData];
5565
}
5666

5767
export default useCachedGetAPI;

0 commit comments

Comments
 (0)