diff --git a/package-lock.json b/package-lock.json index 4714149..e36a9c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", + "@react-oauth/google": "^0.12.2", + "axios": "^1.9.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "core-js": "^3.39.0", @@ -20,7 +22,9 @@ "next": "14.2.5", "react": "^18", "react-dom": "^18", + "react-icons": "^5.5.0", "react-pdf": "^9.1.1", + "sonner": "^2.0.3", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7" }, @@ -641,6 +645,16 @@ } } }, + "node_modules/@react-oauth/google": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.2.tgz", + "integrity": "sha512-d1GVm2uD4E44EJft2RbKtp8Z1fp/gK8Lb6KHgs3pHlM0PxCXGLaq8LLYQYENnN4xPWO1gkL4apBtlPKzpLvZwg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -1044,6 +1058,23 @@ "node": ">=10" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1143,6 +1174,19 @@ "node": ">=10.16.0" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1305,6 +1349,18 @@ "color-support": "bin.js" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1403,6 +1459,15 @@ "node": ">=8" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -1446,6 +1511,20 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1488,6 +1567,24 @@ "node": ">=10.13.0" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", @@ -1495,6 +1592,33 @@ "license": "MIT", "peer": true }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1643,6 +1767,26 @@ "node": ">=8" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", @@ -1659,6 +1803,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/framer-motion": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.0.tgz", @@ -1813,6 +1972,30 @@ "node": ">=8" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -1821,6 +2004,19 @@ "node": ">=6" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -1860,6 +2056,18 @@ "license": "BSD-2-Clause", "peer": true }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1876,6 +2084,33 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -2190,6 +2425,15 @@ "url": "https://github.com/wojtekmaj/make-event-props?sponsor=1" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-refs": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.3.0.tgz", @@ -2241,7 +2485,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -2251,7 +2494,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -2822,6 +3064,12 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2886,6 +3134,15 @@ "react": "^18.3.1" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-pdf": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-9.1.1.tgz", @@ -3274,6 +3531,16 @@ "simple-concat": "^1.0.0" } }, + "node_modules/sonner": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz", + "integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index 60ce73a..8d749e4 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", + "@react-oauth/google": "^0.12.2", + "axios": "^1.9.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "core-js": "^3.39.0", @@ -21,7 +23,9 @@ "next": "14.2.5", "react": "^18", "react-dom": "^18", + "react-icons": "^5.5.0", "react-pdf": "^9.1.1", + "sonner": "^2.0.3", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7" }, diff --git a/src/actions/cookie_actions.ts b/src/actions/cookie_actions.ts new file mode 100644 index 0000000..59aa782 --- /dev/null +++ b/src/actions/cookie_actions.ts @@ -0,0 +1,7 @@ +"use server"; + +import { cookies } from "next/headers"; + +export const getCookie = async (name: string) => { + return cookies().get(name)?.value ?? ""; +}; diff --git a/src/actions/index.ts b/src/actions/index.ts new file mode 100644 index 0000000..10c6505 --- /dev/null +++ b/src/actions/index.ts @@ -0,0 +1,50 @@ +import { getCookie } from "./cookie_actions"; + +export const createReferralCode = async (data?: any) => { + const token = await getCookie("token"); + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_STUDENT_API_URL}/api/user/referral/create`, + { + method: "POST", + body: JSON.stringify(data), + headers: { + "Content-Type": "application/json", + // "x-api-key": process.env.NEXT_PUBLIC_STUDENT_API_KEY, + Cookie: `token=${token}`, + }, + credentials: "include", + } + ); + + const responseData = await res.json(); + + return responseData; + } catch (error: unknown) { + return error + } + }; + +export const fetchReferralCode = async () => { + const token = await getCookie("token"); + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_STUDENT_API_URL}/api/user/referral/get`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + // "x-api-key": process.env.NEXT_STUDENT_API_KEY, + Cookie: `token=${token}`, + }, + credentials: "include", + } + ); + + const responseData = await res.json(); + + return responseData; + } catch (error: unknown) { + return error + } + }; \ No newline at end of file diff --git a/src/apiClient/apiClient.ts b/src/apiClient/apiClient.ts new file mode 100644 index 0000000..8a89d71 --- /dev/null +++ b/src/apiClient/apiClient.ts @@ -0,0 +1,31 @@ +import axios from 'axios'; + +const apiClient = axios.create({ + baseURL: process.env.NEXT_PUBLIC_STUDENT_API_URL, + withCredentials: true, + headers: { + "Content-Type": "application/json", + "x-api-key": process.env.NEXT_PUBLIC_STUDENT_API_KEY, + }, +}); + +apiClient.interceptors.request.use( + (config) => { + // config.headers.Authorization = `Bearer ${yourToken}`; + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +apiClient.interceptors.response.use( + (response) => { + return response; + }, + (error) => { + return Promise.reject(error); + } +); + +export default apiClient; diff --git a/src/app/(auth)/_components/GoogleLoginButton.tsx b/src/app/(auth)/_components/GoogleLoginButton.tsx new file mode 100644 index 0000000..2d7b159 --- /dev/null +++ b/src/app/(auth)/_components/GoogleLoginButton.tsx @@ -0,0 +1,70 @@ +import { useGoogleLogin } from "@react-oauth/google"; +import axios from "axios"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; +import { toast } from "sonner"; + +interface GoogleLoginButtonProps { + onLoginSuccess?: () => void; +} + +const GoogleLoginButton = ({ onLoginSuccess }: GoogleLoginButtonProps) => { + const router = useRouter(); + + const login = useGoogleLogin({ + onSuccess: async (credentialResponse) => { + try { + const res = await axios.post("/api/google/auth", { + access_token: credentialResponse.access_token + },{ + withCredentials: true, + headers: { + 'Content-Type': 'application/json', + }, + }); + + // Instead of using Redux, we can store user data in localStorage or cookies if needed + // Or simply use the response data directly + + toast.success("Login success", { + description: res.data.message, + }); + + // Call the onLoginSuccess callback if provided + if (onLoginSuccess) { + onLoginSuccess(); + } + + } catch (error: any) { + console.error("Axios error:", error); + toast.error("Google login failed!", { + description: error.response?.data?.message || error.message, + }); + } + }, + onError: (error: any) => { + console.error("Google login error:", error); + toast.error("Google login failed!", { + description: error.message, + }); + }, + }); + + + return ( + + ); +}; + +export default GoogleLoginButton; diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx new file mode 100644 index 0000000..de84c7e --- /dev/null +++ b/src/app/(auth)/layout.tsx @@ -0,0 +1,19 @@ +import { Metadata } from "next"; +import React from "react"; +import { Mada as FontSans } from "next/font/google"; +import "../globals.css"; +import { cn } from "@/lib/utils"; + +const fontSans = FontSans({ subsets: ["latin"], variable: "--font-sans" }); + +export const metadata: Metadata = { + title: "Leadlly | Auth", +}; + +export default function AuthLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return
+ Share the gift of personalized learning with your friends and earn exciting rewards! +
++ Login to get your unique referral code and start earning rewards when your friends join Leadlly. +
+Your Referral Code:
+ +{customCodeError}
+ )} ++ Custom code must be 6-12 characters and can only contain letters and numbers. +
+Total Referrals
++ {isLoading ? "..." : referralStats.totalReferrals} +
+Pending
++ {isLoading ? "..." : referralStats.pendingReferrals} +
+Completed
++ {isLoading ? "..." : referralStats.completedReferrals} +
+Rewards Earned
++ ₹{isLoading ? "..." : referralStats.rewards} +
+Share your unique referral code with friends and family
+Your friends sign up using your referral code
+Get exciting rewards for each successful referral
++ Earn exciting rewards for each friend who joins Leadlly using your referral code! +
+