Skip to content

Commit 72cb8a4

Browse files
committed
signup by type
1 parent 9dc5f72 commit 72cb8a4

13 files changed

Lines changed: 197 additions & 55 deletions

File tree

frontend/src/components/areas/public/features/session/pages/session-page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const SessionPage = () => {
1919
<Route exact path="/reset-password/:token" component={ResetPasswordPageContainer} />
2020
<Route exact path="/token/:token" component={Session} />
2121
<Route exact path="/activate/user/:userId/token/:token" component={AccountActivation} />
22-
<Route exact path="/signup/:status" component={RegisterPageContainer} />
22+
<Route exact path="/signup/:type" component={RegisterPageContainer} />
2323
<Route exact path="/forgot" component={ForgotPasswordPageContainer} />
2424
</Switch>
2525
</HashRouter>

frontend/src/components/design-library/atoms/inputs/checkboxes/checkboxes.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const Checkboxes = ({
8484

8585
const columnCount = useMemo(() => {
8686
const count = checkboxesToRender.length || 1
87-
return count > 4 ? 4 : count
87+
return count > 5 ? 5 : count
8888
}, [checkboxesToRender.length])
8989

9090
const mdSize = (12 / columnCount) as 3 | 4 | 6 | 12

frontend/src/components/design-library/atoms/inputs/fields/user-role-field/user-role-field.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,16 @@ const UserRoleField = ({ roles, onChange }) => {
1818
)
1919

2020
return (
21-
<Grid container spacing={2} alignContent="center" alignItems="center">
22-
<Grid size={{ xs: 12, md: 2 }}>
21+
<Grid container spacing={2} alignContent="center" alignItems="center" style={{ marginTop: 20 }}>
22+
<Grid size={{ xs: 12, md: 12 }}>
2323
<Typography
2424
variant="caption"
2525
color="textSecondary"
26-
style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
26+
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}
2727
>
2828
<FormattedMessage id="user.types.roles.select.label" defaultMessage="Signup as: " />
2929
<Tooltip
30+
style={{ marginLeft: 5 }}
3031
placement="right"
3132
title={
3233
<FormattedMessage
@@ -39,7 +40,7 @@ const UserRoleField = ({ roles, onChange }) => {
3940
</Tooltip>
4041
</Typography>
4142
</Grid>
42-
<Grid size={{ xs: 12, md: 10 }}>
43+
<Grid size={{ xs: 12, md: 12 }}>
4344
<Checkboxes
4445
checkboxes={checkBoxes}
4546
includeSelectAll={true}

frontend/src/components/design-library/molecules/cards/spot-card/spot-card.styles.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,36 @@
11
import { Card, CardContent } from '@mui/material'
2-
import { styled } from '@mui/material/styles'
2+
import { alpha, styled } from '@mui/material/styles'
33

4-
export const StyledCard = styled(Card)(() => ({
5-
minWidth: 420,
6-
marginTop: 40,
7-
opacity: 0.8,
8-
overflow: 'visible'
9-
}))
4+
const getThemePaletteByVariant = (theme, variant = 'default') => {
5+
const variants = {
6+
primary: theme.palette.primary,
7+
secondary: theme.palette.secondary,
8+
success: theme.palette.success,
9+
warning: theme.palette.warning,
10+
info: theme.palette.info,
11+
default: theme.palette.primary
12+
}
13+
14+
return variants[variant] || variants.default
15+
}
16+
17+
export const StyledCard = styled(Card, {
18+
shouldForwardProp: (prop) => prop !== '$themeVariant'
19+
})<{ $themeVariant?: string }>(({ theme, $themeVariant = 'default' }) => {
20+
const palette = getThemePaletteByVariant(theme, $themeVariant)
21+
22+
return {
23+
minWidth: 420,
24+
marginTop: 40,
25+
opacity: 0.8,
26+
overflow: 'visible',
27+
borderTop: `4px solid ${palette.main}`,
28+
background: `linear-gradient(180deg, ${alpha(palette.light || palette.main, 0.14)} 0%, ${alpha(
29+
theme.palette.background.paper,
30+
0.95
31+
)} 55%)`
32+
}
33+
})
1034

1135
export const StyledCardContent = styled(CardContent)(() => ({
1236
textAlign: 'center',

frontend/src/components/design-library/molecules/cards/spot-card/spot-card.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { StyledCard, StyledCardContent, Content } from './spot-card.styles'
99

1010
import logo from 'images/logo-complete.png'
1111

12-
const SpotCard = ({ title, description, children }) => {
12+
const SpotCard = ({ title, description, children, themeVariant = 'default' }) => {
1313
const [openDialog, setOpenDialog] = useState(false)
1414
const [dialogType, setDialogType] = useState(null)
1515

@@ -38,7 +38,7 @@ const SpotCard = ({ title, description, children }) => {
3838

3939
return (
4040
<>
41-
<StyledCard>
41+
<StyledCard $themeVariant={themeVariant}>
4242
<StyledCardContent>
4343
<Link to="/">
4444
<img src={logo} width={140} alt="Logo" />

frontend/src/components/design-library/molecules/form-section/login-form/login-form-main/login-form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const LoginForm: React.FC<LoginButtonProps> = ({
5959
return (
6060
<Wrapper contrast={contrast}>
6161
<Content>
62-
<div>
62+
<div style={{ textAlign: 'center' }}>
6363
{mode !== 'reset' ? (
6464
<>
6565
<Typography

frontend/src/components/design-library/molecules/form-section/login-form/login-form-signup/login-form-signup.tsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ type LoginFormSignupProps = {
2727
onSignin?: () => void
2828
roles?: any
2929
fetchRoles?: () => void
30+
hideRoleSelection?: boolean
31+
presetRoleId?: string | number
3032
}
3133

3234
const LoginFormSignup = ({
@@ -37,7 +39,9 @@ const LoginFormSignup = ({
3739
agreeTermsCheckError,
3840
onSignin,
3941
roles,
40-
fetchRoles
42+
fetchRoles,
43+
hideRoleSelection,
44+
presetRoleId
4145
}: LoginFormSignupProps) => {
4246
const [openTermsDialog, setOpenTermsDialog] = useState(false)
4347
const [openPrivacyDialog, setOpenPrivacyDialog] = useState(false)
@@ -227,6 +231,7 @@ const LoginFormSignup = ({
227231
e.preventDefault()
228232
const { captchaChecked, agreeTermsCheck, name, username, password, confirmPassword, Types } =
229233
state
234+
const selectedTypes = hideRoleSelection && presetRoleId ? [presetRoleId] : Types
230235
const termsAgreed = termsChecked(agreeTermsCheck)
231236
const validName = validateName(name)
232237
const validEmail = validateEmail(username)
@@ -245,7 +250,7 @@ const LoginFormSignup = ({
245250
name: name,
246251
email: username,
247252
password: password,
248-
Types: Types
253+
Types: selectedTypes
249254
})
250255
const errorType = response?.error && response?.error?.response?.data.message
251256
if (errorType === 'user.exist') {
@@ -279,11 +284,20 @@ const LoginFormSignup = ({
279284
}
280285

281286
useEffect(() => {
282-
process.env.NODE_ENV === 'development' && setState({ ...state, captchaChecked: true })
283-
process.env.NODE_ENV === 'test' && setState({ ...state, captchaChecked: true })
284-
fetchRoles()
287+
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
288+
setState((prev) => ({ ...prev, captchaChecked: true }))
289+
}
290+
291+
fetchRoles?.()
285292
}, [])
286293

294+
useEffect(() => {
295+
if (!hideRoleSelection) return
296+
if (presetRoleId === undefined || presetRoleId === null) return
297+
298+
setState((prev) => ({ ...prev, Types: [presetRoleId] }))
299+
}, [hideRoleSelection, presetRoleId])
300+
287301
const { error, password, confirmPassword } = state
288302

289303
return (
@@ -359,9 +373,11 @@ const LoginFormSignup = ({
359373
defaultValue={state.confirmPassword}
360374
/>
361375
</Margins>
362-
<Margins>
363-
<UserRoleField roles={roles} onChange={handleTypesChange} />
364-
</Margins>
376+
{!hideRoleSelection && (
377+
<Margins>
378+
<UserRoleField roles={roles} onChange={handleTypesChange} />
379+
</Margins>
380+
)}
365381
<div style={{ display: 'flex', justifyContent: 'flex-start' }}>
366382
<div style={{ display: 'flex', alignItems: 'center' }}>
367383
{state.agreeTermsCheck ? (

frontend/src/components/design-library/pages/public-pages/session-public-pages/signup-page/signup-page.tsx

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,93 @@ import SpotCard from 'design-library/molecules/cards/spot-card/spot-card'
22
import LoginFormSignup from 'design-library/molecules/form-section/login-form/login-form-signup/login-form-signup'
33
import React from 'react'
44
import { FormattedMessage } from 'react-intl'
5-
import { useHistory } from 'react-router-dom'
5+
import { useHistory, useParams } from 'react-router-dom'
6+
7+
const normalizeTypeValue = (value = '') =>
8+
value
9+
.toString()
10+
.trim()
11+
.toLowerCase()
12+
.replace(/[_\s]+/g, '-')
13+
14+
const singularizeTypeValue = (value = '') => normalizeTypeValue(value).replace(/s$/, '')
15+
16+
const formatTypeLabel = (value = '') =>
17+
value
18+
.toString()
19+
.replace(/[-_]+/g, ' ')
20+
.replace(/\b\w/g, (char) => char.toUpperCase())
21+
22+
const getThemeVariant = (type = '') => {
23+
const normalizedType = normalizeTypeValue(type)
24+
25+
if (normalizedType.includes('maintainer')) return 'primary'
26+
if (normalizedType.includes('sponsor')) return 'secondary'
27+
if (normalizedType.includes('contributor')) return 'success'
28+
if (normalizedType.includes('manager')) return 'warning'
29+
return 'info'
30+
}
31+
32+
const resolveRoleByType = (rawType, rolesData = []) => {
33+
const normalizedType = normalizeTypeValue(rawType)
34+
const singularType = singularizeTypeValue(rawType)
35+
36+
if (!normalizedType || !Array.isArray(rolesData) || rolesData.length === 0) {
37+
return null
38+
}
39+
40+
return (
41+
rolesData.find((role) => {
42+
const candidates = [role?.name, role?.label, role?.id].filter(Boolean)
43+
return candidates.some((candidate) => {
44+
const normalizedCandidate = normalizeTypeValue(candidate)
45+
const singularCandidate = singularizeTypeValue(candidate)
46+
47+
return (
48+
normalizedCandidate === normalizedType ||
49+
singularCandidate === singularType ||
50+
normalizedCandidate.includes(normalizedType) ||
51+
normalizedType.includes(normalizedCandidate) ||
52+
singularCandidate.includes(singularType) ||
53+
singularType.includes(singularCandidate)
54+
)
55+
})
56+
}) || null
57+
)
58+
}
659

760
const SignupPage = ({ handleSignup, roles, fetchRoles }) => {
861
const history = useHistory()
62+
const { type, status } = useParams<{ type?: string; status?: string }>()
63+
const routeType = type || status
64+
const resolvedRole = resolveRoleByType(routeType, roles?.data)
65+
const selectedTypeLabel = resolvedRole?.label || resolvedRole?.name || formatTypeLabel(routeType)
66+
const isTypedSignup = !!resolvedRole
67+
968
return (
1069
<SpotCard
1170
title={<FormattedMessage id="signup.welcome" defaultMessage="Welcome!" />}
1271
description={
13-
<FormattedMessage id="signup.description" defaultMessage="Create your account" />
72+
isTypedSignup ? (
73+
<FormattedMessage
74+
id="signup.asType"
75+
defaultMessage="Signup as a {type}"
76+
values={{ type: selectedTypeLabel }}
77+
/>
78+
) : (
79+
<FormattedMessage id="signup.description" defaultMessage="Create your account" />
80+
)
1481
}
82+
themeVariant={isTypedSignup ? getThemeVariant(resolvedRole?.name || routeType) : 'default'}
1583
>
1684
<LoginFormSignup
1785
onSignin={() => history.push('/signin')}
1886
onSubmit={handleSignup}
1987
roles={roles}
2088
fetchRoles={fetchRoles}
2189
noCancelButton
90+
hideRoleSelection={isTypedSignup}
91+
presetRoleId={resolvedRole?.id}
2292
/>
2393
</SpotCard>
2494
)

frontend/src/main/routes.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default (props) => (
2424
path={[
2525
'/reset-password',
2626
'/signup',
27+
'/signup/:type',
2728
'/signin',
2829
'/forgot',
2930
'/activate/user/:userId/token/:token',

0 commit comments

Comments
 (0)