Skip to content

Commit

Permalink
Merge pull request #99 from CS3219-AY2324S1/add-confirm-modal-to-repl…
Browse files Browse the repository at this point in the history
…ace-windows.confirm

Add confirm modal to replace windows.confirm
  • Loading branch information
samyipsh authored Nov 9, 2023
2 parents b9d375e + 1f0a2ee commit 69ddd87
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 41 deletions.
77 changes: 77 additions & 0 deletions src/components/ConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
type ConfirmModalProps = {
isOpen: boolean;
title?: string;
message: string;
onConfirm: () => void;
onCancel: () => void;
cancelButtonText?: string;
confirmButtonText?: string;
type?: "warning" | "neutral";
};
const ConfirmModal = ({
isOpen,
title,
message,
onConfirm,
onCancel,
cancelButtonText = "Cancel",
confirmButtonText = "Confirm",
type = "neutral",
}: ConfirmModalProps) => {
if (!isOpen) return null;

return (
<div className="fixed z-10 inset-0 overflow-y-auto">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 transition-opacity" aria-hidden="true">
<div className="absolute inset-0 bg-gray-500 opacity-70"></div>
</div>
<span
className="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true"
>
&#8203;
</span>
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3
className="text-lg leading-6 font-medium text-gray-900"
id="modal-title"
>
{title}
</h3>
<div className="mt-2">
<p className="text-sm text-gray-500">{message}</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
className={`w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 text-base font-medium text-white ${
type == "warning"
? "bg-red-600 hover:bg-red-700 focus:ring-red-500"
: "bg-blue-600 hover:bg-blue-700 focus:ring-blue-500"
}focus:outline-none focus:ring-2 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm`}
onClick={onConfirm}
>
{confirmButtonText}
</button>
<button
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm"
onClick={onCancel}
>
{cancelButtonText}
</button>
</div>
</div>
</div>
</div>
);
};

export default ConfirmModal;
52 changes: 30 additions & 22 deletions src/pages/collab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,19 @@ import { LoadingSpinner } from "~/components/Loading";

import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import ConfirmModal from "~/components/ConfirmModal";
dayjs.extend(relativeTime);

/**
* TODO
* - get rid of BE code for confirmation of join req
*/
const MatchRequestPage = () => {
const router = useRouter();
const utils = api.useContext();
const [isWaitingIndefintely, setIsWaitingIndefinitely] = useState(false);

const { data: session } = useSession();
if (!session || !session.user) {
throw new Error("Session cannot be undefined since AuthWrapper wrapped");
}
const curUserId = session.user.id;

const intervalRef = useRef<NodeJS.Timer | null>(null);
const [time, setTime] = useState(0);
const resetTimer = () => {
Expand All @@ -53,11 +52,10 @@ const MatchRequestPage = () => {
useEffect(() => {
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (curUserMatchRequest) {
handleDeleteMatchRequest();
e.preventDefault();
// confirm("Are you sure you want to leave this page? Request will be deleted")
if (true) {
void handleDeleteMatchRequest();
}
e.returnValue = "";
return "Are you sure you want to leave this page? Request will be deleted";
}
};

Expand All @@ -74,7 +72,7 @@ const MatchRequestPage = () => {
const [difficultyFilter, setDifficultyFilter] = useState<string>("");
const [categoryFilter, setCategoryFilter] = useState<string>("");

const { data: numOfMatchRequests } =
const { data: numOfMatchRequests, refetch: refetchGetNumOfMatchReqs } =
api.matchRequest.getNumOfMatchRequests.useQuery();

const { mutate: automaticallyMatchCurrentUserRequest } =
Expand All @@ -92,8 +90,10 @@ const MatchRequestPage = () => {
api.matchRequest.createCurrentUserMatchRequest.useMutation({
onSuccess() {
setIsCreatingMatchRequest(false);
setIsWaitingIndefinitely(false);
resetTimer();
toast.success("Successfully created match request");
void refetchGetNumOfMatchReqs();
toast.success("Created match request");
},
onError(err) {
toast.error(err.message);
Expand All @@ -104,17 +104,18 @@ const MatchRequestPage = () => {
const { mutate: deleteMatchRequest } =
api.matchRequest.deleteCurrentUserMatchRequest.useMutation({
onSuccess() {
toast.success("Successfully deleted match request");
stopTimer();
toast.success("Deleted match request");
},
});

const { mutate: updateMatchRequest } =
api.matchRequest.updateCurrentUserMatchRequest.useMutation({
onSuccess() {
toast.success("Successfully updated match request");
resetTimer();
setIsEditingMatchRequest(false);
setIsWaitingIndefinitely(false);
resetTimer();
toast.success("Updated match request");
},
});

Expand All @@ -125,7 +126,6 @@ const MatchRequestPage = () => {
void refetchCurrentUserRequest();
},
onError(err) {
console.log("Subscription error: ", err);
void Promise.resolve(utils.matchRequest.invalidate());
},
});
Expand Down Expand Up @@ -184,22 +184,30 @@ const MatchRequestPage = () => {

const handleAcceptRequest = (acceptedUserId: string) => {
acceptMatch({ acceptedUserId });
toast.success("Successfully matched with user, redirecting to room...", {
toast.success("Redirecting to room...", {
duration: 2000,
});
console.log("Joining session...");
};

// TODO: add custom confirm modal / hot-toast confirm modal
// if ((timerState.waitingTime = 300)) {
// void deleteMatchRequest();
// }

return (
<>
<Head>
<title>Find practice partner</title>
</Head>
<ConfirmModal
title="Continue Waiting?"
isOpen={!isWaitingIndefintely && time > 300}
message={`You have been waiting for 5 minutes. There ${
numOfMatchRequests == 1
? "is 1 other online user"
: `are ${numOfMatchRequests} other online users`
} looking for a match. Would you like to continue waiting?`}
onCancel={() => handleDeleteMatchRequest()}
onConfirm={() => setIsWaitingIndefinitely(true)}
cancelButtonText="No"
confirmButtonText="Yes"
type="neutral"
/>
{curUserMatchRequest && (
<RequestStatus matchType={curUserMatchRequest.matchType} />
)}
Expand Down
26 changes: 12 additions & 14 deletions src/pages/profile/account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import { z } from "zod";
import { signOut, useSession } from "next-auth/react";
import { LoadingPage } from "~/components/Loading";
import UpdatePasswordModal from "~/components/UpdatePasswordOverlayModal";
import ConfirmModal from "~/components/ConfirmModal";

// TODO:
// - add email verification
// - change password using email link
// - edit imageURL

const id_z = z.string().min(1); // can add error message
const id_z = z.string().min(1);
const name_z = z.string().min(1);
const email_z = z.string().email().min(1);
const emailVerified_z = z.date().nullable();
Expand All @@ -29,6 +30,7 @@ const ProfilePage: NextPage = () => {
const router = useRouter();
const [isEditingUser, setIsEditingUser] = useState(false);
const [isEditingPassword, setIsEditingPassword] = useState(false);
const [isDeletingAccount, setIsDeletingAccount] = useState(false);

// session is `null` until nextauth fetches user's session data
const { data: session, update: updateSession } = useSession({
Expand Down Expand Up @@ -137,6 +139,14 @@ const ProfilePage: NextPage = () => {
updatePassword({ id: userData.id, password: data.password })
}
/>
<ConfirmModal
title="Delete Account?"
isOpen={isDeletingAccount}
message="Are you sure you want to delete your account. If you delete your account you will permanently lose your account information and question submissions history."
onCancel={() => setIsDeletingAccount(false)}
onConfirm={() => deleteUser({ id: userData.id })}
type="warning"
/>
<div className="relative h-48 bg-slate-600 border-b overscroll-y-scroll w-full border-x md:max-w-2xl">
<Image
src={imageURL ?? "https://picsum.photos/300/300"}
Expand Down Expand Up @@ -232,16 +242,8 @@ const ProfilePage: NextPage = () => {
</div>
<div>
<button
onClick={() => {
if (
confirm(
"WARNING! All accout information will be removed on deletion. Are you sure you want to proceed?",
)
) {
deleteUser({ id: userData.id });
}
}}
className="text-neutral-400 rounded-md underline"
onClick={() => setIsDeletingAccount(true)}
>
delete account
</button>
Expand All @@ -265,8 +267,4 @@ const ProfilePage: NextPage = () => {
);
};

// const PasswordChangeModal = ({ isOpen }: { isOpen: boolean }) => {
// return <OverlayModal isOpen={isOpen}>hi</OverlayModal>;
// };

export default ProfilePage;
14 changes: 9 additions & 5 deletions src/pages/sign-up/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ const SignUp = () => {
// to prevent malicious actors to derive db data (eg. email exists)
toast.error("Failed to create account\n Please try again later");
},
onSuccess: async () => {
toast.success("Successfully created account: ");
if (confirm("Sign In?")) {
await signIn(undefined, { callbackUrl: "/" });
}
onSuccess: () => {
const DURATION_TO_LOAD = 1200;
toast.success("Sign up successful, redirecting...", {
duration: DURATION_TO_LOAD,
});
setTimeout(
() => void signIn(undefined, { callbackUrl: "/" }),
DURATION_TO_LOAD,
);
},
});

Expand Down

0 comments on commit 69ddd87

Please sign in to comment.