Skip to content

Commit

Permalink
feat: toggle phone number registeration
Browse files Browse the repository at this point in the history
  • Loading branch information
jackdishman committed Aug 30, 2022
1 parent 0882251 commit 86f929c
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 38 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@
"@tailwindcss/typography": "^0.4.1",
"@toruslabs/customauth": "^10.1.1",
"@toruslabs/openlogin-ed25519": "^2.0.0",
"@types/intl-tel-input": "^17.0.5",
"axios": "^0.27.2",
"browser-image-compression": "^2.0.0",
"dompurify": "^2.4.0",
"highlight.js": "^11.6.0",
"intl-tel-input": "^17.0.18",
"libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21",
"marked": "^4.0.19",
Expand Down
16 changes: 16 additions & 0 deletions src/backend/funder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import axios from 'axios'
import { checkAccountStatus, getIsAccountIdOnboarded } from './near'
import { capsuleServer, sufficientFunds } from './utilities/config'

export async function requestOTP(phoneNumber: string) {
const response = await axios.post(`${capsuleServer}/sendOtp`, {
phoneNumber,
})
return response.data.data
}

export async function getFundTransferStatus(accountId: string): Promise<`PROCESSING` | `SENT` | `FAILED`> {
const response = await axios.get(`${capsuleServer}/onboard/sponsor/status?accountId=${accountId}`)
return response.data.data
Expand Down Expand Up @@ -55,3 +62,12 @@ export async function requestOnboard(captchaRes: string, accountId: string) {
})
return response.data.data
}

export async function requestPhoneOnboard(phoneNumber: string, code: string, accountId: string) {
const response = await axios.post(`${capsuleServer}/onboard`, {
phoneNumber,
code,
accountId,
})
return response.data.data
}
2 changes: 2 additions & 0 deletions src/backend/utilities/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const sufficientFunds = process.env.SUFFICIENT_ACCOUNT_FUNDS || `81800000
export const sigValidity = 5 * 60000
export const bootstrapNodes = process.env.BOOTSTRAP_NODES ? JSON.parse(process.env.BOOTSTRAP_NODES) : defaultBootstraps

export const isPhoneEnabled: boolean = process.env.PHONE_ENABLED === `true`

export const stripePublishableKey =
process.env.STRIPE_PUBLISHABLE_KEY ||
`pk_test_51I81pBCPCJ3FaYLGnUrPUMxipudV7gWWA7qAiqIVMAqnULA4a2uluUgBQxX8yKzAe2iGYOoSMX2rSbF45wtKlhXI00Olk8hJmc`
Expand Down
19 changes: 15 additions & 4 deletions src/components/register/SelectID.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,21 @@
</template>

<script lang="ts">
import Vue from 'vue'
import Vue, { PropType } from 'vue'
import { mapMutations } from 'vuex'
import { MutationType, namespace as sessionStoreNamespace } from '~/store/session'
import { ValidationError } from '@/errors'
import { requestOnboard, waitForFunds } from '@/backend/funder'
import { requestOnboard, waitForFunds, hasSufficientFunds } from '@/backend/funder'
import { validateUsernameNEAR } from '@/backend/near'
import { hcaptchaSiteKey } from '@/backend/utilities/config'
import ChevronLeft from '@/components/icons/ChevronLeft.vue'
import { IWalletStatus } from '@/backend/auth'
interface IData {
id: string
siteKey: string
loadingState: `checking_id` | `hcaptcha_loading` | `smart_contract` | `transfer_funds` | null
captchaID: string | null
isLoading: boolean
}
export default Vue.extend({
Expand All @@ -70,13 +70,18 @@ export default Vue.extend({
type: String,
required: true,
},
userInfo: {
type: Object as PropType<IWalletStatus>,
required: true,
},
},
data(): IData {
return {
id: ``,
siteKey: hcaptchaSiteKey,
loadingState: null,
captchaID: null,
isLoading: false,
}
},
async mounted() {
Expand Down Expand Up @@ -110,6 +115,7 @@ export default Vue.extend({
location.reload()
},
async handleRegisterID() {
this.isLoading = true
try {
if (!this.captchaID) {
return
Expand All @@ -119,6 +125,7 @@ export default Vue.extend({
const idValidity = await validateUsernameNEAR(this.id)
if (idValidity.error) {
this.loadingState = null
this.isLoading = false
throw new ValidationError(idValidity.error)
}
this.loadingState = `hcaptcha_loading`
Expand Down Expand Up @@ -150,8 +157,12 @@ export default Vue.extend({
this.$handleError(error)
} finally {
this.loadingState = null
this.isLoading = false
}
},
hasEnoughFunds(): boolean {
return hasSufficientFunds(this.accountId)
},
},
})
</script>
51 changes: 47 additions & 4 deletions src/components/register/SignUp.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
<template>
<div class="flex flex-row w-full items-center justify-center">
<article v-if="!downloadKey" class="flex flex-row w-full items-center justify-center">
<VerifyPhone
v-if="phoneEnabled && (!hasEnoughFunds() || !onboarded)"
:accountId="userInfo.accountId"
class="w-full h-full xl:w-1/2"
@updateFunds="updateFunds"
@setIsOnboarded="setIsOnboarded"
/>
<!-- Step 2: Choose ID -->
<SelectID :funds="funds" :accountId="userInfo.accountId" class="w-full h-full xl:w-1/2" @verify="verify" />
<SelectID v-else :funds="funds" :accountId="userInfo.accountId" class="w-full h-full xl:w-1/2" @verify="verify" />
</article>
<!-- Step 4: Download key -->
<DownloadKey
Expand All @@ -21,25 +28,35 @@ import { mapMutations } from 'vuex'
import SelectID from './SelectID.vue'
import DownloadKey from './DownloadKey.vue'
import VerifyPhone from './VerifyPhone.vue'
import { getUsernameNEAR, removeNearPrivateKey, walletLogout } from '@/backend/near'
import {
checkAccountStatus,
getIsAccountIdOnboarded,
getUsernameNEAR,
removeNearPrivateKey,
walletLogout,
} from '@/backend/near'
import { hasSufficientFunds } from '@/backend/funder'
import { MutationType, createSessionFromProfile, namespace as sessionStoreNamespace } from '~/store/session'
import { setNearUserFromPrivateKey, login, register, IAuthResult, IWalletStatus } from '@/backend/auth'
import { ValidationError } from '@/errors'
import { nearNetwork } from '@/backend/utilities/config'
import { nearNetwork, isPhoneEnabled } from '@/backend/utilities/config'
interface IData {
funds: string
username: null | string
isLoading: boolean
downloadKey: boolean
onboarded: boolean
phoneEnabled: boolean
}
export default Vue.extend({
components: {
DownloadKey,
SelectID,
VerifyPhone,
},
props: {
userInfo: {
Expand All @@ -53,13 +70,22 @@ export default Vue.extend({
username: null,
isLoading: true,
downloadKey: false,
onboarded: false,
phoneEnabled: isPhoneEnabled,
}
},
async created() {
this.$emit(`setIsLoading`, true)
try {
const username = await getUsernameNEAR(this.userInfo.accountId)
if (!username) {
if (this.phoneEnabled) {
const [, onboarded] = await Promise.all([
this.checkFunds(),
await getIsAccountIdOnboarded(this.userInfo.accountId),
])
this.onboarded = onboarded
}
this.$emit(`setIsLoading`, false)
return
}
Expand Down Expand Up @@ -99,6 +125,23 @@ export default Vue.extend({
changeBio: MutationType.CHANGE_BIO,
changeLocation: MutationType.CHANGE_LOCATION,
}),
hasEnoughFunds(): boolean {
return hasSufficientFunds(this.funds)
},
async checkFunds() {
const accountId = this.userInfo.accountId
if (!accountId) {
return
}
const status = await checkAccountStatus(accountId)
this.funds = status.balance
},
updateFunds(balance: string) {
this.funds = balance
},
setIsOnboarded(onboarded: boolean) {
this.onboarded = onboarded
},
async verify(id: string) {
if (!this.userInfo) {
throw new Error(`Unexpected condition!`)
Expand Down
145 changes: 145 additions & 0 deletions src/components/register/VerifyPhone.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<template>
<article>
<h1 class="text-lightPrimaryText dark:text-gray1 text-4xl font-bold">Sign up</h1>
<!-- Enter phone number -->
<div v-show="!otpSent">
<p class="text-gray7 dark:text-gray3 my-10 text-center">
Verify you’re a human with your phone number so that Blogchain can fund your wallet.
</p>
<label for="phoneNumber" class="text-gray5 dark:text-gray3 block pb-1 text-sm font-semibold">Phone Number</label>
<input
id="phoneNumber"
v-model="phoneNumber"
type="tel"
class="focus:outline-none focus:border-primary text-primary dark:text-darkPrimaryText bg-gray2 dark:bg-gray7 mt-1 mb-5 w-full rounded-lg px-3 py-2 font-sans text-sm"
/>
<div class="flex w-full justify-end mt-4">
<BrandedButton :text="otpSent ? `Re-send code` : `Send Code`" :action="sendOTP" />
</div>
<h6 v-show="isLoading" class="text-primary text-center">Sending SMS...</h6>
</div>
<!-- Enter SMS code to complete verify -->
<div v-show="otpSent" class="mt-10">
<label for="otp" class="text-gray5 dark:text-gray3 block pb-1 text-sm font-semibold"
>Enter the one-time verification code sent to your phone number.</label
>
<input
id="otp"
v-model="otp"
type="text"
placeholder=""
class="focus:outline-none focus:border-primary text-primary dark:text-darkPrimaryText bg-gray2 dark:bg-gray7 mt-1 mb-5 w-full rounded-lg px-3 py-2 font-sans text-sm"
/>
<BrandedButton v-show="!isLoading && !waitingForFunds" :text="`Verify`" class="w-full" :action="validateOTP" />
<h6 v-show="isLoading" class="text-primary text-center">Verifying...</h6>
<h6 v-show="waitingForFunds" class="text-primary text-center">Executing smart contract...</h6>
</div>
<p v-show="otpSent" class="text-gray7 dark:text-gray2 mt-10 text-center text-sm">
Didn't receive a code?
<button class="text-primary font-bold" @click="otpSent = false">
Check your phone number and request a new one
</button>
</p>
</article>
</template>

<script lang="ts">
import Vue from 'vue'
import intlTelInput from 'intl-tel-input'
import { AxiosError } from 'axios'
import BrandedButton from '@/components/BrandedButton.vue'
import { requestOTP, requestPhoneOnboard, waitForFunds } from '@/backend/funder'
interface IData {
otp: string
otpSent: boolean
iti: null | intlTelInput.Plugin
phoneNumber: string
inputCode: string
isLoading: boolean
waitingForFunds: boolean
}
export default Vue.extend({
components: {
BrandedButton,
},
props: {
accountId: {
type: String,
required: true,
},
},
data(): IData {
return {
otp: ``,
otpSent: false,
iti: null,
phoneNumber: ``,
inputCode: ``,
isLoading: false,
waitingForFunds: false,
}
},
mounted() {
const input = document.querySelector(`#phoneNumber`)
if (input) {
this.iti = intlTelInput(input, {
utilsScript: require(`intl-tel-input/build/js/utils`),
// any initialisation options go here
})
}
},
methods: {
async sendOTP() {
if (this.iti === null) {
return
}
this.phoneNumber = this.iti.getNumber()
if (!this.iti.isValidNumber()) {
this.$toastError(`Invalid phone number`)
return
}
this.isLoading = true
await requestOTP(this.phoneNumber)
this.otpSent = true
this.$toastSuccess(
`If you haven't used this phone number before on Blogchain, you'll receive a code on your phone`,
)
this.isLoading = false
},
async validateOTP() {
try {
if (this.otp.length !== 6) {
this.$toastError(`OTP should have 6 digits`)
return
}
if (!this.accountId) {
return
}
this.isLoading = true
await requestPhoneOnboard(this.phoneNumber, this.otp, this.accountId)
this.isLoading = false
this.waitingForFunds = true
const { balance } = await waitForFunds(this.accountId)
this.waitingForFunds = false
this.$emit(`setIsOnboarded`, true)
this.$emit(`updateFunds`, balance)
} catch (err: any) {
this.isLoading = false
this.waitingForFunds = false
if (err instanceof AxiosError && err.response) {
this.otp = ``
this.$toastError(err.response.data.error)
return
}
this.$toastError(err.message)
throw err
}
},
},
})
</script>
<style>
.iti {
width: 100%;
}
</style>
6 changes: 2 additions & 4 deletions src/pages/register.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,20 @@ import Vue from 'vue'
import { mapMutations } from 'vuex'
import { AxiosError } from 'axios'
import CustomAuth from '@toruslabs/customauth'
import RegisterMethods from '@/components/register/RegisterMethods.vue'
import InfosPopup from '@/components/register/InfosPopup.vue'
import SignUp from '@/components/register/SignUp.vue'
import CapsuleIcon from '@/components/icons/CapsuleNew.vue'
import { MutationType, namespace as sessionStoreNamespace } from '~/store/session'
import { removeNearPrivateKey, walletLogout } from '@/backend/near'
import { ValidationError } from '@/errors'
import { getAccountIdFromPrivateKey, getUserInfo, IWalletStatus } from '@/backend/auth'
import { domain, torusNetwork } from '@/backend/utilities/config'
import { revokeDiscordKey } from '@/backend/discordRevoke'
import 'intl-tel-input/build/css/intlTelInput.css'
interface IData {
userInfo: null | IWalletStatus
isLoading: boolean
Expand Down
Loading

0 comments on commit 86f929c

Please sign in to comment.