Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ function App() {
{ path: "manage-locations", element: <AuthChecker roles={[roles.ADMIN]} children={<ManageLocation />} /> },
{ path: "manage-locations/add/:item", element: <AddLocation /> },
{ path: "manage-locations/edit/:item", element: <EditLocation /> },
{ path: "management/manage-locations/add/:item", element: <AddLocation /> },
{ path: "management/manage-locations/edit/:item", element: <EditLocation /> },
{ path: "hackathon/organizers/:id", element: <AuthChecker roles={[roles.ADMIN]} children={<HackathonOrganizers />} /> },
{ path: "learningcircle/your-circles", element: <YourLC /> },
{ path: "learningcircle/dashboard/:id", element: <DashboardLC /> },
Expand Down
132 changes: 1 addition & 131 deletions src/modules/Dashboard/layouts/DashboardRootLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,16 @@ import styles from "../components/SideNavBar.module.css";
import { Outlet, useNavigate } from "react-router-dom";
import SideNavBar from "../components/SideNavBar";
import TopNavBar from "../components/TopNavBar";
import { AlertBanner } from "../components/AlertBanner";
import React, { Suspense, useEffect, useCallback, useState } from "react";
import React, { Suspense, useEffect, useState } from "react";
import { FaRocket, FaUser, FaUserFriends } from "react-icons/fa";
import { FaMagnifyingGlass, FaMapLocationDot, FaHouse, FaRankingStar } from "react-icons/fa6";
import { IoGlobeOutline } from "react-icons/io5";
import { FiRefreshCw } from "react-icons/fi";
import { roles, managementTypes } from "@/MuLearnServices/types";
import MuLoader from "@/MuLearnComponents/MuLoader/MuLoader";
import { dashboardRoutes, qseverseRoutes } from "@/MuLearnServices/urls";
import { privateGateway, publicGateway } from "@/MuLearnServices/apiGateways";
import { UserProfile, useUserStore, useQseverseStore } from "/src/ZustandProvider";
import { sendRefreshToken } from "@/modules/utils/cdr";
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
Button,
VStack,
Text,
useDisclosure,
} from "@chakra-ui/react";
import toast from "react-hot-toast";

interface CrateType {
navigate: (channelId: string) => void;
Expand Down Expand Up @@ -252,45 +236,7 @@ const DashboardRootLayout = (props: { component?: any }) => {
// }
];

// Modal for connect wallet
const { isOpen: isConnectModalOpen, onOpen: onConnectModalOpen, onClose: onConnectModalClose } = useDisclosure();
const [isRefreshingConnection, setIsRefreshingConnection] = useState(false);

// Handler for QSeverse connect action - opens modal
const handleConnectQseverse = useCallback(() => {
onConnectModalOpen();
}, [onConnectModalOpen]);

// Handler for refreshing connection status
const handleRefreshConnection = useCallback(async () => {
if (!userInfo.muid) {
toast.error("Unable to check connection - user info not available");
return;
}

setIsRefreshingConnection(true);
try {
const response = await publicGateway.get(qseverseRoutes.getConnectedUsers, {
params: { key: 'muid', value: userInfo.muid }
});
const dids = response?.data?.response?.dids;
if (dids && Array.isArray(dids) && dids.length > 0) {
setQseverseStatus('connected');
toast.success("Wallet connected successfully!");
onConnectModalClose();
} else {
toast.error("No connected wallet found. Please link your wallet in the QSeverse app first.");
}
} catch (error) {
console.error("Error refreshing connection:", error);
toast.error("Failed to check connection status.");
} finally {
setIsRefreshingConnection(false);
}
}, [userInfo.muid, setQseverseStatus, onConnectModalClose]);

const refreshToken = localStorage.getItem("refreshToken");
const showQseverseBanner = refreshToken && qseverseStatus === 'not_connected';

if (isLoading) {
return <div className={styles.loader}>
Expand All @@ -309,83 +255,7 @@ const DashboardRootLayout = (props: { component?: any }) => {
<Outlet />
</Suspense>
</div>
{showQseverseBanner && (
<div style={{
position: 'fixed',
bottom: '20px',
left: '50%',
transform: 'translateX(-50%)',
zIndex: 1000,
maxWidth: '90vw',
}}>
<AlertBanner
variant="warning"
title="Connect your QSeverse Wallet"
description="Link your wallet to claim verifiable credentials."
actionLabel="Connect Now"
onAction={handleConnectQseverse}
dismissible={false}
icon={<i className="fi fi-rr-wallet"></i>}
className="floating-pill"
/>
</div>
)}
</div>

{/* Connect Wallet Modal */}
<Modal isOpen={isConnectModalOpen} onClose={onConnectModalClose} isCentered>
<ModalOverlay />
<ModalContent mx={4}>
<ModalHeader>Connect your QSeverse Wallet</ModalHeader>
<ModalCloseButton />
<ModalBody>
<VStack spacing={4} align="stretch">
<Text fontSize="sm" color="gray.600">
To claim verifiable credentials for your achievements, you need to link your QSeverse wallet.
</Text>
<Text fontSize="sm" fontWeight="medium">
Steps to connect:
</Text>
<VStack as="ol" spacing={2} align="stretch" pl={4} fontSize="sm">
<Text as="li">Download the QSeverse app from your app store and sign up</Text>
<Text as="li">Connect your MuLearn account</Text>
<Text as="li">Your wallet will be automatically linked</Text>
<Text as="li">Click "Refresh Status" below to verify</Text>
</VStack>
</VStack>
</ModalBody>
<ModalFooter flexWrap="wrap" gap={2} justifyContent="center">
<Button
as="a"
href="https://apps.apple.com/us/app/qs-passport/id6477819506"
target="_blank"
colorScheme="blue"
size="sm"
>
App Store
</Button>
<Button
as="a"
href="https://play.google.com/store/apps/details?id=com.qseverse.passport"
target="_blank"
colorScheme="blue"
size="sm"
>
Play Store
</Button>
<Button
colorScheme="green"
size="sm"
leftIcon={<FiRefreshCw />}
onClick={handleRefreshConnection}
isLoading={isRefreshingConnection}
loadingText="Checking..."
>
Refresh Status
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</div>
);
};
Expand Down
134 changes: 131 additions & 3 deletions src/modules/Dashboard/modules/Profile/pages/Profile.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import moment from "moment";
import { useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState, useCallback } from "react";
import dpm from "../assets/images/dpm.webp";
import Karma, { KarmaWhite } from "../assets/svg/Karma";
import MulearnBrand from "../assets/svg/MulearnBrand";
Expand Down Expand Up @@ -35,9 +35,9 @@ import Socials from "../components/Socials/pages/Socials";
import ShareProfilePopUp from "../components/ShareProfilePopUp/pages/ShareProfilePopUp";
import HelmetMetaTags from "../components/HelmetMetaTags/HelmetMetaTags";
import { isDev } from "@/MuLearnServices/common_functions";
import { Img, SimpleGrid, Switch } from "@chakra-ui/react";
import { Img, SimpleGrid, Switch, Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, ModalBody, ModalCloseButton, Button, VStack, Text, useDisclosure } from "@chakra-ui/react";
import AchievementCard from "../components/Achievements/AchievementCard";
import { useUserStore } from "/src/ZustandProvider";
import { useUserStore, useQseverseStore } from "/src/ZustandProvider";
import { getAchievements } from "../../ManageAchievements/services/api";
import { AchievementData } from "../../ManageAchievements/ManageAchievementsInterface";
import AchievementCardOne from "../components/Achievements/AchievementCardOne";
Expand All @@ -47,6 +47,10 @@ import EditCollegePopUp from "../components/EditProfilePopUp/pages/EditCollegePo
import { useMuShepherdTour } from "../../../../../components/MuComponents/MuTour/MuShepherdTour";
import MuShepherdTourButton from "../../../../../components/MuComponents/MuTour/MuShepherdTourButton";
import { profileShepherdTourSteps } from "../../../../../components/MuComponents/MuTour/profileShepherdTourSteps";
import { FiRefreshCw } from "react-icons/fi";
import { AlertBanner } from "../../../components/AlertBanner";
import { qseverseRoutes } from "@/MuLearnServices/urls";
import { publicGateway } from "@/MuLearnServices/apiGateways";



Expand Down Expand Up @@ -144,6 +148,53 @@ const Profile = () => {
const [userPreferences, setUserPreferences] = useState<any>(null);
const [preferencesLoading, setPreferencesLoading] = useState(false);

// QWallet Modal state and handlers
const {
connectionStatus: qseverseStatus,
hasCheckedConnection: hasCheckedQseverse,
setConnectionStatus: setQseverseStatus,
setHasCheckedConnection: setHasCheckedQseverse
} = useQseverseStore();
const { isOpen: isConnectModalOpen, onOpen: onConnectModalOpen, onClose: onConnectModalClose } = useDisclosure();
const [isRefreshingConnection, setIsRefreshingConnection] = useState(false);
const { userInfo } = useUserStore();

// Handler for QSeverse connect action - opens modal
const handleConnectQseverse = useCallback(() => {
onConnectModalOpen();
}, [onConnectModalOpen]);

// Handler for refreshing connection status
const handleRefreshConnection = useCallback(async () => {
if (!userInfo.muid) {
toast.error("Unable to check connection - user info not available");
return;
}

setIsRefreshingConnection(true);
try {
const response = await publicGateway.get(qseverseRoutes.getConnectedUsers, {
params: { key: 'muid', value: userInfo.muid }
});
const dids = response?.data?.response?.dids;
if (dids && Array.isArray(dids) && dids.length > 0) {
setQseverseStatus('connected');
toast.success("Wallet connected successfully!");
onConnectModalClose();
} else {
toast.error("No connected wallet found. Please link your wallet in the QSeverse app first.");
}
} catch (error) {
console.error("Error refreshing connection:", error);
toast.error("Failed to check connection status.");
} finally {
setIsRefreshingConnection(false);
}
}, [userInfo.muid, setQseverseStatus, onConnectModalClose]);

const refreshToken = localStorage.getItem("refreshToken");
const showQseverseBanner = refreshToken && qseverseStatus === 'not_connected' && !id;

// Initialize Profile Tour - using Shepherd.js for better scroll handling
const tour = useMuShepherdTour({
steps: profileShepherdTourSteps,
Expand Down Expand Up @@ -1018,6 +1069,83 @@ const Profile = () => {
)
)}
</div>

{showQseverseBanner && (
<div style={{
position: 'fixed',
bottom: '20px',
left: '50%',
transform: 'translateX(-50%)',
zIndex: 1000,
maxWidth: '90vw',
}}>
<AlertBanner
variant="warning"
title="Connect your QSeverse Wallet"
description="Link your wallet to claim verifiable credentials."
actionLabel="Connect Now"
onAction={handleConnectQseverse}
dismissible={false}
icon={<i className="fi fi-rr-wallet"></i>}
className="floating-pill"
/>
</div>
)}

{/* Connect Wallet Modal */}
<Modal isOpen={isConnectModalOpen} onClose={onConnectModalClose} isCentered>
<ModalOverlay />
<ModalContent mx={4}>
<ModalHeader>Connect your QSeverse Wallet</ModalHeader>
<ModalCloseButton />
<ModalBody>
<VStack spacing={4} align="stretch">
<Text fontSize="sm" color="gray.600">
To claim verifiable credentials for your achievements, you need to link your QSeverse wallet.
</Text>
<Text fontSize="sm" fontWeight="medium">
Steps to connect:
</Text>
<VStack as="ol" spacing={2} align="stretch" pl={4} fontSize="sm">
<Text as="li">Download the QSeverse app from your app store and sign up</Text>
<Text as="li">Connect your MuLearn account</Text>
<Text as="li">Your wallet will be automatically linked</Text>
<Text as="li">Click "Refresh Status" below to verify</Text>
</VStack>
</VStack>
</ModalBody>
<ModalFooter flexWrap="wrap" gap={2} justifyContent="center">
<Button
as="a"
href="https://apps.apple.com/us/app/qs-passport/id6477819506"
target="_blank"
colorScheme="blue"
size="sm"
>
App Store
</Button>
<Button
as="a"
href="https://play.google.com/store/apps/details?id=com.qseverse.passport"
target="_blank"
colorScheme="blue"
size="sm"
>
Play Store
</Button>
<Button
colorScheme="green"
size="sm"
leftIcon={<FiRefreshCw />}
onClick={handleRefreshConnection}
isLoading={isRefreshingConnection}
loadingText="Checking..."
>
Refresh Status
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
Expand Down