Use an intl phone input number field and fix the whole verification flow

- There were some state mismatches in configuring a whatsapp number. This commit fixes those issues and uses an external library for phone number validation
This commit is contained in:
sabaimran 2024-08-01 16:44:17 +05:30
parent 60870a7a3e
commit 84dd1b57fe
4 changed files with 56 additions and 26 deletions

View file

@ -1,6 +1,7 @@
'use client' 'use client'
import styles from "./settings.module.css"; import styles from "./settings.module.css";
import "intl-tel-input/styles";
import { Suspense, useEffect, useState } from "react"; import { Suspense, useEffect, useState } from "react";
import { useToast } from "@/components/ui/use-toast" import { useToast } from "@/components/ui/use-toast"
@ -74,6 +75,8 @@ import NavMenu from "../components/navMenu/navMenu";
import SidePanel from "../components/sidePanel/chatHistorySidePanel"; import SidePanel from "../components/sidePanel/chatHistorySidePanel";
import Loading from "../components/loading/loading"; import Loading from "../components/loading/loading";
import IntlTelInput from 'intl-tel-input/react';
const ManageFilesModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { const ManageFilesModal: React.FC<{ onClose: () => void }> = ({ onClose }) => {
const [syncedFiles, setSyncedFiles] = useState<string[]>([]); const [syncedFiles, setSyncedFiles] = useState<string[]>([]);
@ -349,7 +352,7 @@ export default function SettingsView() {
const [userConfig, setUserConfig] = useState<UserConfig | null>(null); const [userConfig, setUserConfig] = useState<UserConfig | null>(null);
const [name, setName] = useState<string | undefined>(undefined); const [name, setName] = useState<string | undefined>(undefined);
const [notionToken, setNotionToken] = useState<string | null>(null); const [notionToken, setNotionToken] = useState<string | null>(null);
const [number, setNumber] = useState<string | undefined>(undefined); const [phoneNumber, setPhoneNumber] = useState<string | undefined>(undefined);
const [otp, setOTP] = useState(""); const [otp, setOTP] = useState("");
const [numberValidationState, setNumberValidationState] = useState<PhoneNumberValidationState>(PhoneNumberValidationState.Verified); const [numberValidationState, setNumberValidationState] = useState<PhoneNumberValidationState>(PhoneNumberValidationState.Verified);
const [isManageFilesModalOpen, setIsManageFilesModalOpen] = useState(false); const [isManageFilesModalOpen, setIsManageFilesModalOpen] = useState(false);
@ -358,7 +361,7 @@ export default function SettingsView() {
useEffect(() => { useEffect(() => {
setUserConfig(initialUserConfig); setUserConfig(initialUserConfig);
setNumber(initialUserConfig?.phone_number); setPhoneNumber(initialUserConfig?.phone_number);
setNumberValidationState( setNumberValidationState(
initialUserConfig?.is_phone_number_verified initialUserConfig?.is_phone_number_verified
? PhoneNumberValidationState.Verified ? PhoneNumberValidationState.Verified
@ -379,7 +382,7 @@ export default function SettingsView() {
const sendOTP = async () => { const sendOTP = async () => {
try { try {
const response = await fetch(`/api/phone?phone_number=${number}`, { const response = await fetch(`/api/phone?phone_number=${phoneNumber}`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -942,15 +945,25 @@ export default function SettingsView() {
Connect your number to chat with Khoj on WhatsApp. Learn more about the integration <a href="https://docs.khoj.dev/clients/whatsapp">here</a>. Connect your number to chat with Khoj on WhatsApp. Learn more about the integration <a href="https://docs.khoj.dev/clients/whatsapp">here</a>.
</p> </p>
<div> <div>
<IntlTelInput
initialValue={phoneNumber || ""}
onChangeNumber={setPhoneNumber}
disabled={numberValidationState === PhoneNumberValidationState.Verified || numberValidationState === PhoneNumberValidationState.VerifyOTP}
initOptions={{
separateDialCode: true,
initialCountry: "us",
utilsScript: "https://assets.khoj.dev/intl-tel-input%4023.8.0_build_js_utils.js",
}}
/>
{numberValidationState === PhoneNumberValidationState.VerifyOTP && ( {numberValidationState === PhoneNumberValidationState.VerifyOTP && (
<> <>
<p>{`Enter the OTP sent to your WhatsApp number: ${number}`}</p> <p>{`Enter the OTP sent to your number: ${phoneNumber}`}</p>
<InputOTP <InputOTP
autoFocus={true} autoFocus={true}
maxLength={6} maxLength={6}
value={otp || ""} value={otp || ""}
onChange={setOTP} onChange={setOTP}
onComplete={() => setNumberValidationState(PhoneNumberValidationState.Verified)} onComplete={() => setNumberValidationState(PhoneNumberValidationState.VerifyOTP)}
> >
<InputOTPGroup> <InputOTPGroup>
<InputOTPSlot index={0} /> <InputOTPSlot index={0} />
@ -962,16 +975,6 @@ export default function SettingsView() {
</InputOTPGroup> </InputOTPGroup>
</InputOTP> </InputOTP>
</> </>
) || (
<>
<Input
type="tel"
onChange={(e) => setNumber(e.target.value)}
value={number || ""}
placeholder="Enter phone number (e.g. +911234567890)"
className="w-full border border-gray-300 rounded-lg px-4 py-6"
/>
</>
)} )}
</div> </div>
</CardContent> </CardContent>
@ -986,14 +989,14 @@ export default function SettingsView() {
) || ( ) || (
<Button <Button
variant="outline" variant="outline"
disabled={!number || number === userConfig.phone_number || !isValidPhoneNumber(number)} disabled={!phoneNumber || (phoneNumber === userConfig.phone_number && numberValidationState === PhoneNumberValidationState.Verified) || !isValidPhoneNumber(phoneNumber)}
onClick={sendOTP} onClick={sendOTP}
> >
{!userConfig.phone_number {!userConfig.phone_number
? (<><Plugs className="inline mr-2" />Setup Whatsapp</>) ? (<><Plugs className="inline mr-2" />Setup Whatsapp</>)
: !number || number === userConfig.phone_number || !isValidPhoneNumber(number) : !phoneNumber || (phoneNumber === userConfig.phone_number && numberValidationState === PhoneNumberValidationState.Verified) || !isValidPhoneNumber(phoneNumber)
? (<><PlugsConnected className="inline mr-2 text-green-400" />Switch Number</>) ? (<><PlugsConnected className="inline mr-2 text-green-400" />Switch Number</>)
: (<>Send OTP to Whatsapp <ArrowRight className="inline ml-2" weight="bold"/></>) : (<>Send OTP <ArrowRight className="inline ml-2" weight="bold"/></>)
} }
</Button> </Button>
)} )}

View file

@ -46,6 +46,7 @@
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.2.3", "eslint-config-next": "14.2.3",
"input-otp": "^1.2.4", "input-otp": "^1.2.4",
"intl-tel-input": "^23.8.0",
"katex": "^0.16.10", "katex": "^0.16.10",
"libphonenumber-js": "^1.11.4", "libphonenumber-js": "^1.11.4",
"lucide-react": "^0.397.0", "lucide-react": "^0.397.0",
@ -64,7 +65,13 @@
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/dompurify": "^3.0.5",
"@types/intl-tel-input": "^18.1.4",
"@types/katex": "^0.16.7",
"@types/markdown-it": "^14.1.1",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.2.3", "eslint-config-next": "14.2.3",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
@ -73,15 +80,10 @@
"lint-staged": "^15.2.7", "lint-staged": "^15.2.7",
"nodemon": "^3.1.3", "nodemon": "^3.1.3",
"prettier": "3.3.3", "prettier": "3.3.3",
"tailwindcss-animate": "^1.0.7",
"tailwind-merge": "^2.3.0", "tailwind-merge": "^2.3.0",
"tailwindcss": "^3.4.6", "tailwindcss": "^3.4.6",
"typescript": "^5", "tailwindcss-animate": "^1.0.7",
"@types/dompurify": "^3.0.5", "typescript": "^5"
"@types/katex": "^0.16.7",
"@types/markdown-it": "^14.1.1",
"@types/react": "^18",
"@types/react-dom": "^18"
}, },
"prettier": { "prettier": {
"tabWidth": 4 "tabWidth": 4

View file

@ -1295,6 +1295,20 @@
dependencies: dependencies:
"@types/trusted-types" "*" "@types/trusted-types" "*"
"@types/intl-tel-input@^18.1.4":
version "18.1.4"
resolved "https://registry.yarnpkg.com/@types/intl-tel-input/-/intl-tel-input-18.1.4.tgz#0eb5211a7490f8a8d7aa940ee594a85138d514c9"
integrity sha512-UT4dQ4dQA0w0uxU161aPROEEwMOQj5Qedm3ImdDNfkxi3JXzBX2FhcUS72pTyZ8ypaUr2e9ruJBiK6bwSGfbew==
dependencies:
"@types/jquery" "*"
"@types/jquery@*":
version "3.5.30"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.30.tgz#888d584cbf844d3df56834b69925085038fd80f7"
integrity sha512-nbWKkkyb919DOUxjmRVk8vwtDb0/k8FKncmUKFi+NY+QXqWltooxTrswvz4LspQwxvLdvzBN1TImr6cw3aQx2A==
dependencies:
"@types/sizzle" "*"
"@types/json5@^0.0.29": "@types/json5@^0.0.29":
version "0.0.29" version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@ -1350,6 +1364,11 @@
"@types/prop-types" "*" "@types/prop-types" "*"
csstype "^3.0.2" csstype "^3.0.2"
"@types/sizzle@*":
version "2.3.8"
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.8.tgz#518609aefb797da19bf222feb199e8f653ff7627"
integrity sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==
"@types/trusted-types@*": "@types/trusted-types@*":
version "2.0.7" version "2.0.7"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
@ -2982,6 +3001,11 @@ internal-slot@^1.0.4, internal-slot@^1.0.7:
hasown "^2.0.0" hasown "^2.0.0"
side-channel "^1.0.4" side-channel "^1.0.4"
intl-tel-input@^23.8.0:
version "23.8.0"
resolved "https://registry.yarnpkg.com/intl-tel-input/-/intl-tel-input-23.8.0.tgz#37bec095605516aa72529b3da11335b253b65b2f"
integrity sha512-lx8Sz5LfVyIXyjWbfjno89o4qHfIuWulctGaWbP2RKKnHvagdt9gdibCsv9uEH7izb/yjB6Nst0sRo988/lhpw==
invariant@^2.2.4: invariant@^2.2.4:
version "2.2.4" version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@ -4442,6 +4466,7 @@ string-argv@~0.3.2:
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
name string-width-cjs
version "4.2.3" version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==

View file

@ -1298,7 +1298,7 @@ def get_user_config(user: KhojUser, request: Request, is_detailed: bool = False)
"user_photo": user_picture, "user_photo": user_picture,
"is_active": is_active, "is_active": is_active,
"given_name": given_name, "given_name": given_name,
"phone_number": user.phone_number, "phone_number": str(user.phone_number),
"is_phone_number_verified": user.verified_phone_number, "is_phone_number_verified": user.verified_phone_number,
# user content settings # user content settings
"enabled_content_source": enabled_content_sources, "enabled_content_source": enabled_content_sources,