-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SOK-22] User profile page #13
Changes from 21 commits
4110c13
1597380
442c996
ab29ccb
82efa62
c08cc7f
901e7f1
888ba27
a4590ad
0533838
5f8153f
44d145a
01a321d
a61b010
f9a4f97
61ab747
0d46090
7b59c39
dff7926
3cb9e99
b39dbb2
ee6ad2b
bd06de6
eef02e0
c3d32aa
7bfdf3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
@import '../../../scss/vars.scss'; | ||
|
||
.avatar { | ||
width: 160px; | ||
max-width: 100%; | ||
height: 158px; | ||
max-height: 100%; | ||
position: relative; | ||
|
||
&__image, &__image-placeholder { | ||
border-radius: 50%; | ||
border: 5px solid $c_avatar-border-color; | ||
width: inherit; | ||
height: inherit; | ||
} | ||
|
||
&__overlay { | ||
opacity: 0; | ||
position: absolute; | ||
top: 0; | ||
left: 0; | ||
right: 0; | ||
bottom: 0; | ||
background-color: rgba(0, 0, 0, 0.5); | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
transition: opacity 0.3s ease; | ||
border-radius: 50%; | ||
|
||
&:hover { | ||
opacity: 1; | ||
} | ||
} | ||
|
||
&:hover { | ||
.overlay { | ||
opacity: 1; | ||
} | ||
} | ||
|
||
&__change-button { | ||
border: none; | ||
color: white; | ||
font-size: 14px; | ||
font-weight: bold; | ||
text-transform: uppercase; | ||
cursor: pointer; | ||
padding: 8px 16px; | ||
border-radius: 20px; | ||
background-color: rgba(0, 0, 0, 0.5); | ||
transition: background-color 0.3s ease; | ||
|
||
&:hover { | ||
background-color: rgba(0, 0, 0, 0.7); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import './Avatar.scss' | ||
import React, { useState, useRef, useEffect } from 'react' | ||
import { Image } from '@/components/ui/Image/Image' | ||
import AvatarPlaceholder from '@/assets/images/avatar-placeholder.png' | ||
|
||
export const AVATAR_SRC = 'https://ya-praktikum.tech/api/v2/resources' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Эту константу стоит вынести в env файл, обсуди с Георгием. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Перенёс |
||
|
||
export const Avatar = (props: { | ||
src: string | ||
containerClassName?: string | ||
imageClassName?: string | ||
onChange: (file: File) => void | ||
}) => { | ||
const { src, containerClassName, imageClassName, onChange } = props | ||
const [previewUrl, setPreviewUrl] = useState<string | null>(null) | ||
const fileInputRef = useRef<HTMLInputElement>(null) | ||
|
||
useEffect(() => { | ||
return () => { | ||
if (previewUrl) { | ||
URL.revokeObjectURL(previewUrl) | ||
} | ||
} | ||
}, [previewUrl]) | ||
|
||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const file = event.target.files?.[0] | ||
if (file) { | ||
const fileUrl = URL.createObjectURL(file) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Логику с генерацией url я бы вынес в отдельный хук |
||
setPreviewUrl(fileUrl) | ||
onChange(file) | ||
} | ||
} | ||
|
||
const handleButtonClick = () => { | ||
fileInputRef.current?.click() | ||
} | ||
|
||
const displaySrc = previewUrl || src | ||
|
||
return ( | ||
<div className={`avatar ${containerClassName}`}> | ||
{displaySrc ? ( | ||
<Image | ||
src={displaySrc} | ||
className={`avatar__image ${imageClassName}`} | ||
alt="Avatar" | ||
/> | ||
) : ( | ||
<Image | ||
src={AvatarPlaceholder} | ||
className={'avatar__image-placeholder'} | ||
alt="Avatar" | ||
/> | ||
)} | ||
<div className="avatar__overlay"> | ||
<button className={'avatar__change-button'} onClick={handleButtonClick}> | ||
{'Изменить аватар'} | ||
</button> | ||
</div> | ||
<input | ||
ref={fileInputRef} | ||
type="file" | ||
accept="image/png, image/jpeg, image/jpg, image/gif" | ||
onChange={handleFileChange} | ||
style={{ display: 'none' }} | ||
/> | ||
</div> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import './Button.scss' | ||
import React from 'react' | ||
|
||
export const Button = (props: { | ||
text: string | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import './Form.scss' | ||
import React from 'react' | ||
|
||
interface FormProps { | ||
className?: string | ||
children?: React.ReactNode | ||
onSubmit?: () => void | ||
} | ||
|
||
export const Form = (props: FormProps) => { | ||
const { onSubmit, className, children } = props | ||
return ( | ||
<form onSubmit={onSubmit} className={`form-fields ${className}`}> | ||
{children} | ||
</form> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
interface ImageProps { | ||
src: string | ||
className?: string | ||
alt?: string | ||
} | ||
|
||
export const Image = (props: ImageProps) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Честно говоря, не понимаю зачем нужен этот компонент, выглядит как будто проще написать img |
||
const { src, className, alt } = props | ||
return <img {...props} /> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
@import '../../../scss/vars'; | ||
|
||
.input-default { | ||
padding: 12px 15px; | ||
border-radius: 12px; | ||
border: 2px solid $c_input-default-border; | ||
text-align: center; | ||
background-color: $c_default-background; | ||
color: $c_font-default; | ||
font-size: 18px; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Забыл указать размер шрифта There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Исправил |
||
&:focus-visible, | ||
&:hover { | ||
outline: none; | ||
border-color: $c_input-default-border-hover; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import './Input.scss' | ||
import React, { ChangeEventHandler, CSSProperties } from 'react' | ||
|
||
interface InputProps { | ||
placeholder?: string | ||
name?: string | ||
className?: string | ||
disabled?: boolean | ||
value?: string | ||
type?: string | ||
label?: string | ||
onChange?: ChangeEventHandler | undefined | ||
style?: CSSProperties | undefined | ||
} | ||
|
||
export const Input = (props: InputProps) => { | ||
const { value, name, className, label, disabled, onChange, style } = props | ||
return ( | ||
<div className={'input-wrapper'}> | ||
{label && <label htmlFor={name}>{label}</label>} | ||
<input | ||
{...props} | ||
name={name} | ||
value={value} | ||
className={`input-default ${className}`} | ||
disabled={disabled} | ||
onChange={onChange} | ||
style={style} | ||
/> | ||
</div> | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Мне кажется, что такие цвета лучше вынести в константы