Skip to content

Commit b9307ec

Browse files
nbitonfranck-boullier
authored andcommitted
feat: allow signup with promo codes (#888)
* feat: allow signup with promo codes * feat: case insensitive promo code match
1 parent 97a58ff commit b9307ec

File tree

6 files changed

+61
-11
lines changed

6 files changed

+61
-11
lines changed

imports/api/hooks/on-create-user.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,24 @@ import randToken from 'rand-token'
55
import bugzillaApi from '../../util/bugzilla-api'
66
import { baseUserSchema } from '../custom-users'
77
import { logger } from '../../util/logger'
8+
import PromoCodes from '../promo-codes'
89

910
// Exported for testing purposes
1011
export function onCreateUser (options, user) {
1112
const { callAPI } = bugzillaApi
12-
const { bzLogin, bzPass } = options.profile
13+
const { bzLogin, bzPass, promoCode } = options.profile
1314
delete options.profile.bzLogin
1415
delete options.profile.bzPass
1516

17+
if (promoCode) {
18+
const promoCodeRecord = PromoCodes.findOne({ code: new RegExp(promoCode.toLowerCase(), 'i') })
19+
if (!promoCodeRecord) {
20+
throw new Meteor.Error('Promo code does not exist')
21+
} else if (promoCodeRecord.expiresOn && promoCodeRecord.expiresOn.getTime() < Date.now()) {
22+
throw new Meteor.Error('This promo code has expired')
23+
}
24+
}
25+
1626
// Creating a random bz pass if one was not provided
1727
const password = bzPass || 'a' + randToken.generate(10) + '!'
1828
const { email } = options

imports/api/hooks/on-login-failure.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Accounts } from 'meteor/accounts-base'
22
import { logger } from '../../util/logger'
33

44
Accounts.onLoginFailure(({ user, type, error }) => {
5-
const userIdentifier = user.emails ? `User with email ${user.emails[0].address}` : `User ${user._id}`
6-
logger.warn(`${userIdentifier} has tried to login with the '${type}' method unsuccessfully. Reason: "${error.reason}"`)
5+
if (user) {
6+
const userIdentifier = user.emails ? `User with email ${user.emails[0].address}` : `User ${user._id}`
7+
logger.warn(`${userIdentifier} has tried to login with the '${type}' method unsuccessfully. Reason: "${error.reason}"`)
8+
}
79
})

imports/api/promo-codes.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Mongo } from 'meteor/mongo'
2+
3+
export const collectionName = 'promoCodes'
4+
5+
/* Contains docs of format:
6+
{
7+
code: string
8+
createdAt: Date
9+
expiresOn?: Date
10+
}
11+
*/
12+
export default new Mongo.Collection(collectionName)

imports/ui/signup/signup.actions.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ type Action = {
1010
type: string
1111
}
1212

13-
type Info = {
14-
password: string,
15-
emailAddress: string
16-
}
13+
type Info = {
14+
password: string,
15+
emailAddress: string,
16+
promoCode: ?string
17+
}
1718

1819
type Dispatch = (action: Action) => any;
1920
type ThunkAction = (dispatch: Dispatch) => any
@@ -28,6 +29,7 @@ export function submitSignupInfo (info: Info): ThunkAction {
2829
email: info.emailAddress,
2930
password: info.password,
3031
profile: {
32+
promoCode: info.promoCode
3133
}
3234
}, (err) => {
3335
if (err) {

imports/ui/signup/signup.jsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ type State = {
2525
termsAgreement: boolean,
2626
info: {
2727
password: string,
28-
emailAddress: string
28+
emailAddress: string,
29+
promoCode: string
2930
},
3031
errorTexts: {}
3132
}
@@ -35,6 +36,7 @@ type Inputs = Array<{
3536
identifier: string,
3637
placeholder: string,
3738
type: string,
39+
optional?: boolean,
3840
onChange: (evt: SyntheticInputEvent<HTMLInputElement>) => void
3941
}>
4042

@@ -47,7 +49,8 @@ export class SignupPage extends React.Component<Props, State> {
4749
termsAgreement: false,
4850
info: {
4951
password: '',
50-
emailAddress: ''
52+
emailAddress: '',
53+
promoCode: ''
5154
},
5255
errorTexts: {}
5356
}
@@ -68,6 +71,20 @@ export class SignupPage extends React.Component<Props, State> {
6871
})
6972
})
7073
}
74+
},
75+
{
76+
label: 'Promo code',
77+
identifier: 'promoCode',
78+
optional: true,
79+
placeholder: 'Signing up for a promo?',
80+
type: 'text',
81+
onChange: (evt) => {
82+
const { value } = evt.target
83+
const { info } = this.state
84+
this.setState({
85+
info: Object.assign({}, info, { promoCode: value })
86+
})
87+
}
7188
}
7289
]
7390

@@ -97,7 +114,7 @@ export class SignupPage extends React.Component<Props, State> {
97114
isFormValid = () => {
98115
const { info, errorTexts, termsAgreement } = this.state
99116
return (
100-
this.inputs.filter(({ identifier }) => !!info[identifier]).length === this.inputs.length && // All have a value
117+
this.inputs.filter(({ identifier, optional }) => optional || !!info[identifier]).length === this.inputs.length && // All have a value
101118
Object.keys(errorTexts).filter(key => !!errorTexts[key]).length === 0 && // No error messages
102119
!!info.password && // Password has a value
103120
!!termsAgreement
@@ -121,7 +138,13 @@ export class SignupPage extends React.Component<Props, State> {
121138
{this.inputs.map(({ label, identifier, placeholder, type, onChange }, i) => (
122139
<InputRow key={i} label={label} placeholder={placeholder} inpType={type} value={info[identifier]}
123140
onChange={evt => onChange ? onChange(evt) : this.makeInfoChange({ [identifier]: evt.target.value })}
124-
errorText={errorTexts[identifier] || userCreationState.error}
141+
errorText={
142+
errorTexts[identifier] || (
143+
userCreationState.error &&
144+
userCreationState.error.match(new RegExp(label.toLowerCase(), 'i')) &&
145+
userCreationState.error
146+
)
147+
}
125148
/>
126149
))}
127150
{userCreationState.error && userCreationState.error.includes('Email') &&

server/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import '../imports/api/notification-settings-overrides'
1515
import '../imports/api/failed-unit-creations'
1616
import '../imports/api/increment-counters'
1717
import '../imports/api/internal-api-payloads'
18+
import '../imports/api/promo-codes'
1819
import '../imports/api/hooks/on-create-user'
1920
import '../imports/api/hooks/on-login'
2021
import '../imports/api/hooks/on-login-failure'

0 commit comments

Comments
 (0)