Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
moodlenet
MoodleNet Frontend
Commits
447640de
Commit
447640de
authored
Nov 06, 2018
by
Sam Gluck
Browse files
- Split out user profile component into own file;
- Add logic in for displaying the selected interests as tags in user profile;
parent
7bd5d7a1
Changes
5
Show whitespace changes
Inline
Side-by-side
src/components/elements/Tag/Tag.tsx
View file @
447640de
import
*
as
React
from
'
react
'
;
import
{
Tag
as
ZenTag
,
Close
}
from
'
@zendeskgarden/react-tags
'
;
import
styled
from
'
../../../themes/styled
'
;
export
type
TagProps
=
{
onClick
?:
Function
;
closeable
?:
boolean
;
...
...
@@ -26,4 +28,10 @@ const Tag: React.SFC<TagProps> = ({
);
};
export
const
TagContainer
=
styled
.
div
`
${
ZenTag
}
{
margin: 0 10px 10px 0;
}
`
;
export
default
Tag
;
src/pages/sign-up/SignUp.tsx
View file @
447640de
import
*
as
React
from
'
react
'
;
import
{
Redirect
,
RouteComponentProps
}
from
'
react-router
'
;
import
{
FontAwesomeIcon
}
from
'
@fortawesome/react-fontawesome
'
;
import
{
faImage
,
faUser
}
from
'
@fortawesome/free-solid-svg-icons
'
;
import
{
Grid
,
Row
,
Col
}
from
'
@zendeskgarden/react-grid
'
;
import
styled
,
{
StyledThemeInterface
}
from
'
../../themes/styled
'
;
import
Logo
from
'
../../components/brand/Logo/Logo
'
;
import
Body
from
'
../../components/chrome/Body/Body
'
;
import
H6
from
'
../../components/typography/H6/H6
'
;
import
PreviousStep
from
'
./PreviousStep
'
;
import
Tag
,
{
TagContainer
}
from
'
../../components/elements/Tag/Tag
'
;
import
Step1
from
'
./Step1
'
;
import
Step2
from
'
./Step2
'
;
import
P
from
'
../../components/typography/P/P
'
;
import
H4
from
'
../../components/typography/H4/H4
'
;
import
UserProfile
from
'
../user/UserProfile
'
;
import
User
from
'
../../types/User
'
;
import
H4
from
'
../../components/typography/H4/H4
'
;
import
Button
from
'
../../components/elements/Button/Button
'
;
const
SignUpBody
=
styled
(
Body
)
`
display: flex;
...
...
@@ -30,120 +29,6 @@ const Sidebar = styled.div`
props
.
theme
.
styles
.
colour
.
base5
}
;
`
;
//TODO media queries/responsivity
const
UserProfile
=
styled
.
div
`
overflow: auto;
width: 75%;
height: 100%;
margin-left: 1%;
background-color:
${(
props
:
StyledThemeInterface
)
=>
props
.
theme
.
styles
.
colour
.
base5
}
;
`
;
//TODO don't use any props type
const
UserProfileImage
=
styled
.
div
<
any
>
`
position: relative;
width: 100%;
height: 400px;
background-color:
${
props
=>
props
.
theme
.
styles
.
colour
.
base4
}
;
background-image:
${
props
=>
props
.
backgroundImage
?
`linear-gradient(transparent, rgba(0, 0, 0, .75)), url(
${
props
.
backgroundImage
}
)`
:
''
}
;
background-position: center;
background-size: 100% auto;
color:
${
props
=>
props
.
theme
.
styles
.
colour
.
base3
}
;
display: flex;
align-items: center;
justify-content: center;
font-size: 6rem;
`
;
const
liftUserAvatar
=
-
75
;
const
AvatarPlaceholder
=
styled
.
div
`
font-size: 12px;
position: absolute;
top: 150px;
text-align: center;
text-shadow: 1px 1px 0 #00000069;
transition: all 0.2s linear;
`
;
//TODO don't use any props type
const
UserAvatar
=
styled
.
div
<
any
>
`
overflow: hidden;
height: 150px;
width: 150px;
border-radius: 75px;
background-color:
${
props
=>
props
.
theme
.
styles
.
colour
.
base3
}
;
background-image:
${
props
=>
`url(
${
props
.
backgroundImage
}
)`
}
;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
color:
${
props
=>
props
.
theme
.
styles
.
colour
.
base5
}
;
position: relative;
top:
${
liftUserAvatar
}
px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
font-size: 4rem;
// avatar placeholder
> div {
font-size: 6px;
pointer-events: none;
}
&:hover > div {
top: 50%;
font-size: 12px;
}
// end avatar placeholder
// avatar font awesome icon
svg {
top: 0;
position: relative;
transition: all 0.2s linear;
pointer-events: none;
}
&:hover svg {
font-size: 30px;
top: -25px;
}
// end avatar font awesome icon
`
;
const
UserDetails
=
styled
.
div
`
position: relative;
top:
${
liftUserAvatar
}
px;
text-align: center;
`
;
const
SignUpProfileSection
=
styled
.
div
`
position: relative;
top:
${
liftUserAvatar
}
px;
padding: 50px;
`
;
const
FileInput
=
styled
.
input
`
cursor: pointer;
height: calc(100% + 100px);
width: 100%;
position: absolute;
top: -100px;
left: 0;
&:focus {
outline: 0;
}
`
;
const
stepScrollTo
=
{
1
:
0
,
2
:
650
...
...
@@ -195,12 +80,9 @@ interface SignUpState {
interface
SignUpProps
extends
RouteComponentProps
<
SignUpMatchParams
>
{}
const
FadedFallbackText
=
({
value
,
fallback
})
=>
value
?
(
<
span
>
{
value
}
</
span
>
)
:
(
<
span
style
=
{
{
color
:
'
lightgrey
'
}
}
>
{
fallback
}
</
span
>
);
const
SignUpProfileSection
=
styled
.
div
<
any
>
`
padding: 50px;
`
;
const
Step2Section
=
styled
.
div
<
any
>
`
opacity:
${
props
=>
(
props
.
active
?
1
:
0.1
)}
;
...
...
@@ -208,9 +90,21 @@ const Step2Section = styled.div<any>`
transition: opacity 1.75s linear;
`
;
const
Interests
=
({
active
,
interests
})
=>
(
const
Interests
=
({
active
,
interests
,
onTagClick
})
=>
(
<
Step2Section
active
=
{
active
}
>
<
H4
>
Interests
</
H4
>
<
TagContainer
>
{
interests
.
map
(
interest
=>
(
<
Tag
focused
closeable
key
=
{
interest
}
onClick
=
{
()
=>
onTagClick
(
interest
)
}
>
{
interest
}
</
Tag
>
))
}
</
TagContainer
>
</
Step2Section
>
);
...
...
@@ -265,20 +159,23 @@ export default class SignUp extends React.Component<SignUpProps, SignUpState> {
}
getStepComponent
()
{
const
stepProps
=
{
const
stepIdx
=
this
.
state
.
currentStep
-
1
;
let
Step
=
SignUp
.
stepComponents
[
stepIdx
];
if
(
!
Step
)
{
return
null
;
}
return
(
<
Step
{
...{
user
:
this
.
state
.
user
,
goToNextStep
:
this
.
goToNextStep
,
goToPreviousStep
:
this
.
goToPreviousStep
,
randomizeEmojiId
:
this
.
randomizeEmojiId
,
linkUserState
:
this
.
linkUserState
,
toggleInterest
:
this
.
toggleUserInterest
};
const
stepIdx
=
this
.
state
.
currentStep
-
1
;
let
Step
=
SignUp
.
stepComponents
[
stepIdx
];
if
(
!
Step
)
{
return
null
;
}
return
<
Step
{
...
stepProps
}
/>;
}
}
/>
);
}
randomizeEmojiId
()
{
...
...
@@ -369,7 +266,14 @@ export default class SignUp extends React.Component<SignUpProps, SignUpState> {
});
}
setUserImage
(
name
:
string
):
(
evt
:
React
.
SyntheticEvent
)
=>
void
{
/**
* Load an image from the user file system and set as `imageTypeName` on the
* state so it can be displayed without uploading.
* @param imageTypeName the type of image being set
*/
setUserImage
(
imageTypeName
:
'
profileImage
'
|
'
avatarImage
'
):
(
evt
:
React
.
SyntheticEvent
)
=>
void
{
return
(
evt
:
any
)
=>
{
const
reader
=
new
FileReader
();
...
...
@@ -379,7 +283,7 @@ export default class SignUp extends React.Component<SignUpProps, SignUpState> {
this
.
setState
({
user
:
{
...
this
.
state
.
user
,
[
n
ame
]:
reader
.
result
as
string
[
imageTypeN
ame
]:
reader
.
result
as
string
}
});
},
...
...
@@ -392,6 +296,10 @@ export default class SignUp extends React.Component<SignUpProps, SignUpState> {
};
}
private
getNextStepName
()
{
return
[
'
your interests
'
,
'
discover
'
][
this
.
state
.
currentStep
-
1
];
}
render
()
{
if
(
!
(
'
step
'
in
this
.
props
.
match
.
params
))
{
return
<
Redirect
to
=
"/sign-up/1"
/>;
...
...
@@ -416,81 +324,42 @@ export default class SignUp extends React.Component<SignUpProps, SignUpState> {
</
Col
>
</
Row
>
{
this
.
getStepComponent
()
}
</
Grid
>
</
Sidebar
>
<
UserProfile
innerRef
=
{
e
=>
(
this
.
_profileElem
=
e
)
}
>
<
UserProfileImage
title
=
"Click to select a profile background"
backgroundImage
=
{
this
.
state
.
user
.
profileImage
}
>
<
FileInput
onChange
=
{
this
.
setUserImage
(
'
profileImage
'
)
}
type
=
"file"
/>
{
!
this
.
state
.
user
.
profileImage
?
(
<
div
style
=
{
{
pointerEvents
:
'
none
'
,
position
:
'
relative
'
,
top
:
'
-20px
'
,
textAlign
:
'
center
'
}
}
>
<
FontAwesomeIcon
icon
=
{
faImage
}
/>
<
P
<
Row
style
=
{
{
flexGrow
:
1
}
}
/>
<
Row
>
{
this
.
getNextStepName
()
?
(
<
Col
style
=
{
{
pointerEvents
:
'
none
'
,
marginTop
:
'
-12px
'
,
fontSize
:
'
.8rem
'
,
textShadow
:
'
1px 1px 0 lightgrey
'
display
:
'
flex
'
,
alignItems
:
'
center
'
,
color
:
'
grey
'
}
}
>
Click to select
<
br
/>
a profile background
</
P
>
</
div
>
Next:
{
this
.
getNextStepName
()
}
</
Col
>
)
:
null
}
</
UserProfileImage
>
<
UserAvatar
title
=
"Click to select your profile image"
backgroundImage
=
{
this
.
state
.
user
.
avatarImage
}
>
<
FileInput
onChange
=
{
this
.
setUserImage
(
'
avatarImage
'
)
}
type
=
"file"
/>
{
!
this
.
state
.
user
.
avatarImage
?
(
<
Col
style
=
{
{
display
:
'
flex
'
,
justifyContent
:
'
flex-end
'
}
}
>
{
this
.
state
.
currentStep
>
1
?
(
<>
<
FontAwesomeIcon
icon
=
{
faUser
}
/>
<
AvatarPlaceholder
>
Click to select
<
br
/>
a profile picture
</
AvatarPlaceholder
>
<
Button
secondary
onClick
=
{
this
.
goToNextStep
}
>
Skip
</
Button
>
<
div
style
=
{
{
height
:
'
10px
'
,
width
:
'
10px
'
}
}
/>
</>
)
:
null
}
</
UserAvatar
>
<
UserDetails
>
<
H6
>
{
this
.
state
.
user
.
emojiId
}
</
H6
>
<
H6
>
<
FadedFallbackText
value
=
{
this
.
state
.
user
.
username
}
fallback
=
"joebloggs84"
/>
</
H6
>
<
H6
>
<
FadedFallbackText
value
=
{
this
.
state
.
user
.
role
}
fallback
=
"Head Teacher"
/>
<
span
style
=
{
{
color
:
'
lightgrey
'
}
}
>
—
</
span
>
<
FadedFallbackText
value
=
{
this
.
state
.
user
.
location
}
fallback
=
"London, UK"
/>
</
H6
>
</
UserDetails
>
<
SignUpProfileSection
>
<
Button
onClick
=
{
this
.
goToNextStep
}
>
Continue
</
Button
>
</
Col
>
</
Row
>
</
Grid
>
</
Sidebar
>
<
UserProfile
innerRef
=
{
e
=>
(
this
.
_profileElem
=
e
)
}
user
=
{
this
.
state
.
user
}
setUserImage
=
{
this
.
setUserImage
}
body
=
{
({
containerProps
})
=>
{
return
(
<
SignUpProfileSection
{
...
containerProps
}
>
<
Interests
onTagClick
=
{
this
.
toggleUserInterest
}
active
=
{
this
.
state
.
currentStep
>
1
}
interests
=
{
this
.
state
.
user
.
interests
}
/>
...
...
@@ -499,9 +368,9 @@ export default class SignUp extends React.Component<SignUpProps, SignUpState> {
languages
=
{
this
.
state
.
user
.
languages
}
/>
</
SignUpProfileSection
>
{
/*padding forces profile to be scrollable*/
}
<
div
style
=
{
{
height
:
'
75%
'
,
width
:
'
100%
'
}
}
/>
</
UserProfile
>
);
}
}
/
>
</
SignUpBody
>
);
}
...
...
src/pages/sign-up/Step1.tsx
View file @
447640de
...
...
@@ -18,7 +18,7 @@ const OverflowCol = styled(Col)`
overflow: auto;
`
;
export
default
({
user
,
goToNextStep
,
randomizeEmojiId
,
linkUserState
})
=>
{
export
default
({
user
,
randomizeEmojiId
,
linkUserState
})
=>
{
return
(
<>
<
Row
>
...
...
@@ -97,15 +97,6 @@ export default ({ user, goToNextStep, randomizeEmojiId, linkUserState }) => {
</
TextField
>
</
OverflowCol
>
</
Row
>
<
Row
style
=
{
{
flexGrow
:
1
}
}
/>
<
Row
>
<
Col
style
=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
color
:
'
grey
'
}
}
>
Next: your interests
</
Col
>
<
Col
style
=
{
{
display
:
'
flex
'
,
justifyContent
:
'
flex-end
'
}
}
>
<
Button
onClick
=
{
goToNextStep
}
>
Continue
</
Button
>
</
Col
>
</
Row
>
</>
);
};
src/pages/sign-up/Step2.tsx
View file @
447640de
import
*
as
React
from
'
react
'
;
import
{
Col
,
Row
}
from
'
@zendeskgarden/react-grid
'
;
import
{
Tag
as
ZenTag
}
from
'
@zendeskgarden/react-tags
'
;
import
H6
from
'
../../components/typography/H6/H6
'
;
import
P
from
'
../../components/typography/P/P
'
;
import
Button
from
'
../../components/elements/Button/Button
'
;
import
styled
from
'
../../themes/styled
'
;
import
Tag
from
'
../../components/elements/Tag/Tag
'
;
const
Spacer
=
styled
.
div
`
width: 10px;
height: 10px;
`
;
import
Tag
,
{
TagContainer
}
from
'
../../components/elements/Tag/Tag
'
;
//TODO get tags from the API
const
words
=
`offer
...
...
@@ -32,12 +24,6 @@ support
shell
neck`
;
const
TagContainer
=
styled
.
div
`
${
ZenTag
}
{
margin: 0 5px 5px 0;
}
`
;
// https://stackoverflow.com/a/6274381/2039244
function
shuffle
(
a
)
{
for
(
let
i
=
a
.
length
-
1
;
i
>
0
;
i
--
)
{
...
...
@@ -65,7 +51,14 @@ export default ({ user, goToNextStep, toggleInterest }) => {
</
P
>
<
TagContainer
>
{
words2
.
map
(
word
=>
(
<
Tag
key
=
{
word
}
>
{
word
}
</
Tag
>
<
Tag
closeable
=
{
user
.
interests
.
includes
(
word
)
}
focused
=
{
user
.
interests
.
includes
(
word
)
}
key
=
{
word
}
onClick
=
{
()
=>
toggleInterest
(
word
)
}
>
{
word
}
</
Tag
>
))
}
</
TagContainer
>
<
P
style
=
{
{
fontWeight
:
'
bold
'
}
}
>
Popular on MoodleNet
</
P
>
...
...
@@ -73,8 +66,8 @@ export default ({ user, goToNextStep, toggleInterest }) => {
<
TagContainer
>
{
words1
.
map
(
word
=>
(
<
Tag
focused
=
{
user
.
interests
.
includes
(
word
)
}
key
=
{
word
}
type
=
{
user
.
interests
.
includes
(
word
)
?
'
green
'
:
undefined
}
onClick
=
{
()
=>
toggleInterest
(
word
)
}
>
{
word
}
...
...
@@ -83,19 +76,6 @@ export default ({ user, goToNextStep, toggleInterest }) => {
</
TagContainer
>
</
Col
>
</
Row
>
<
Row
style
=
{
{
flexGrow
:
1
}
}
/>
<
Row
>
<
Col
style
=
{
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
color
:
'
grey
'
}
}
>
Next: discover
</
Col
>
<
Col
style
=
{
{
display
:
'
flex
'
,
justifyContent
:
'
flex-end
'
}
}
>
<
Button
secondary
onClick
=
{
goToNextStep
}
>
Skip
</
Button
>
<
Spacer
/>
<
Button
onClick
=
{
goToNextStep
}
>
Continue
</
Button
>
</
Col
>
</
Row
>
</>
);
};
src/pages/user/UserProfile.tsx
0 → 100644
View file @
447640de
import
*
as
React
from
'
react
'
;
import
{
FontAwesomeIcon
}
from
'
@fortawesome/react-fontawesome
'
;
import
{
faImage
,
faUser
}
from
'
@fortawesome/free-solid-svg-icons
'
;
import
P
from
'
../../components/typography/P/P
'
;
import
H6
from
'
../../components/typography/H6/H6
'
;
import
styled
,
{
StyledThemeInterface
}
from
'
../../themes/styled
'
;
import
User
from
'
../../types/User
'
;
const
FileInput
=
styled
.
input
`
cursor: pointer;
height: calc(100% + 100px);
width: 100%;
position: absolute;
top: -100px;
left: 0;
&:focus {
outline: 0;
}
`
;
//TODO media queries/responsivity
export
const
UserProfile
=
styled
.
div
`
overflow: auto;
width: 75%;
height: 100%;
margin-left: 1%;
background-color:
${(
props
:
StyledThemeInterface
)
=>
props
.
theme
.
styles
.
colour
.
base5
}
;
`
;
//TODO don't use any props type
const
UserProfileImage
=
styled
.
div
<
any
>
`
position: relative;
width: 100%;
height: 400px;
background-color:
${
props
=>
props
.
theme
.
styles
.
colour
.
base4
}
;
background-image:
${
props
=>
props
.
backgroundImage
?
`linear-gradient(transparent, rgba(0, 0, 0, .75)), url(
${
props
.
backgroundImage
}
)`
:
''
}
;
background-position: center;
background-size: 100% auto;
color:
${
props
=>
props
.
theme
.
styles
.
colour
.
base3
}
;
display: flex;
align-items: center;
justify-content: center;
font-size: 6rem;
`
;
const
liftUserAvatar
=
-
75
;
const
AvatarPlaceholder
=
styled
.
div
`
font-size: 12px;
position: absolute;
top: 150px;
text-align: center;
text-shadow: 1px 1px 0 #00000069;
transition: all 0.2s linear;
`
;
//TODO don't use the `any` props type
const
UserAvatar
=
styled
.
div
<
any
>
`
overflow: hidden;
height: 150px;
width: 150px;
border-radius: 75px;
background-color:
${
props
=>
props
.
theme
.
styles
.
colour
.
base3
}
;
background-image:
${
props
=>
`url(
${
props
.
backgroundImage
}
)`
}
;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
color:
${
props
=>
props
.
theme
.
styles
.
colour
.
base5
}
;
position: relative;
top:
${
liftUserAvatar
}
px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
font-size: 4rem;
// avatar placeholder
> div {
font-size: 6px;
pointer-events: none;
}
&:hover > div {
top: 50%;
font-size: 12px;
}
// end avatar placeholder
// avatar font awesome icon
svg {
top: 0;
position: relative;
transition: all 0.2s linear;
pointer-events: none;
}
&:hover svg {
font-size: 30px;
top: -25px;
}
// end avatar font awesome icon
`
;
const
UserDetails
=
styled
.
div
`
position: relative;
top:
${
liftUserAvatar
}
px;
text-align: center;
`
;
const
FadedFallbackText
=
({
value
,
fallback
})
=>
value
?
(
<
span
>
{
value
}
</
span
>
)
:
(
<
span
style
=
{
{
color
:
'
lightgrey
'
}
}
>
{
fallback
}
</
span
>
);
// the body of UserProfile is customisable through a render prop
// the `body` render prop will be passed this style object which
// should be passed to the container returned from the render prop
const
bodyProps
=
{
style
:
{
position
:
'
relative
'
,