Skip to content

Commit

Permalink
submit flow
Browse files Browse the repository at this point in the history
  • Loading branch information
iossocket committed Feb 7, 2025
1 parent dac2b53 commit 4b88600
Show file tree
Hide file tree
Showing 10 changed files with 397 additions and 37 deletions.
1 change: 1 addition & 0 deletions packages/nextjs/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
NEXT_PUBLIC_PROVIDER_URL=https://starknet-sepolia.public.blastapi.io/rpc/v0_7
NEXT_PUBLIC_SERVER_BASE_URL=http://localhost:3000
2 changes: 1 addition & 1 deletion packages/nextjs/components/Challenges/ChallengeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export const ChallengeModal = ({ isOpen, onClose, title }: Props) => {
: "max-h-[600px] overflow-y-scroll"
}`}
>
{/* <SubmitChallenge challenge={selectedChallenge} /> */}
<SubmitChallenge challenge={selectedChallenge} />
{fetchState.loading && (
<div className="w-[850px] h-full flex items-center justify-center">
<span className="text-[#4D58FF] loading loading-spinner loading-lg"></span>
Expand Down
49 changes: 39 additions & 10 deletions packages/nextjs/components/Challenges/ConfirmSubmitModal.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
import Image from "next/image";
import { CloseIcon } from "../icons/CloseIcon";

export const ConfirmSubmitModal = ({ onClose }: { onClose: () => void }) => {
export const ConfirmSubmitModal = ({
onClose,
onSubmit,
loading,
}: {
onClose: () => void;
onSubmit: () => void;
loading: boolean;
}) => {
return (
<div className="overflow-hidden absolute z-[98] md:top-1/2 top-0 left-1/2 transform md:-translate-y-1/2 -translate-x-1/2 md:shadow-modal max-w-[320px] w-full mx-auto md:p-[1px] bg-white">
<div className={`w-full pb-1`}>
<div className="bg-[#4D58FF] relative h-[60px] flex items-center justify-center">
<div className="flex items-center gap-1.5 absolute z-30 left-4">
<CloseIcon onClose={onClose} />
<CloseIcon
onClose={() => {
if (loading) {
return;
}
onClose();
}}
/>
</div>
<p className="text-xl relative z-30 uppercase font-vt323 !text-white">
confirmation
Expand All @@ -24,7 +39,12 @@ export const ConfirmSubmitModal = ({ onClose }: { onClose: () => void }) => {
</div>
<div className="flex items-center gap-2.5 px-1">
<div
onClick={onClose}
onClick={() => {
if (loading) {
return;
}
onClose();
}}
className="uppercase w-full text-center py-2 bg-[#FF282C] flex justify-center items-center gap-2 cursor-pointer"
>
<Image
Expand All @@ -38,15 +58,24 @@ export const ConfirmSubmitModal = ({ onClose }: { onClose: () => void }) => {
</p>
</div>
<div
onClick={onClose} //add submit here
onClick={() => {
if (loading) {
return;
}
onSubmit();
}}
className="uppercase w-full text-center py-2 bg-[#4D58FF] flex justify-center items-center gap-2 cursor-pointer"
>
<Image
src={"/homescreen/submit.svg"}
alt="icon"
width={24}
height={24}
/>
{loading ? (
<span className="text-white loading loading-spinner loading-xs" />
) : (
<Image
src={"/homescreen/submit.svg"}
alt="icon"
width={24}
height={24}
/>
)}
<p className="md:text-lg text-sm font-vt323 uppercase !text-white">
confirm
</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState, useEffect, Dispatch, SetStateAction } from "react";

interface InputField {
id: string;
Expand All @@ -19,6 +19,8 @@ interface InputURLProps {
interface DynamicSequentialInputsProps {
inputFields?: InputField[];
onSubmit?: (isAllValidated: boolean) => void;
inputValues: Record<string, string>;
setInputValues: Dispatch<SetStateAction<Record<string, string>>>;
}

const InputURL: React.FC<InputURLProps> = ({
Expand All @@ -35,7 +37,7 @@ const InputURL: React.FC<InputURLProps> = ({
<p className="!text-black">{title}</p>
<input
type="text"
value={value}
value={value || ""}
onChange={(e) => onChange(e.target.value)}
onPaste={(e) => {
e.preventDefault();
Expand All @@ -53,10 +55,7 @@ const InputURL: React.FC<InputURLProps> = ({

export const DynamicSequentialInputs: React.FC<
DynamicSequentialInputsProps
> = ({ inputFields = [], onSubmit }) => {
const [inputValues, setInputValues] = useState<Record<string, string>>(() =>
inputFields.reduce((acc, field) => ({ ...acc, [field.id]: "" }), {}),
);
> = ({ inputFields = [], onSubmit, inputValues, setInputValues }) => {
const [errors, setErrors] = useState<Record<string, string>>({});
const [checking, setChecking] = useState<{ [key: string]: boolean }>({});
const [validatedInputs, setValidatedInputs] = useState<{
Expand Down
142 changes: 126 additions & 16 deletions packages/nextjs/components/Challenges/SubmitChallenge.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,74 @@
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { ConfirmSubmitModal } from "./ConfirmSubmitModal";
import { DynamicSequentialInputs } from "./DynamicSequentialInputs";
import { Challenge } from "~~/mockup/type";
import Image from "next/image";
import { CloseIcon } from "../icons/CloseIcon";
import { ExpandIcon } from "../icons/ExpandIcon";
import { useOutsideClick } from "~~/hooks/scaffold-stark";
import { composeSubmission } from "~~/utils/submission";
import { useAccount } from "@starknet-react/core";
import { socket } from "~~/services/socket";
import axios from "axios";
import toast from "react-hot-toast";

export const SubmitChallenge = ({ challenge }: { challenge: Challenge }) => {
const { address } = useAccount();
const [isExpanded, setIsExpanded] = useState(false);
const [openSubmit, setOpenSubmit] = useState(false);
const [openConfirmSubmit, setOpenConfirmSubmit] = useState(false);
const [inputValues, setInputValues] = useState<Record<string, string>>(() =>
(challenge.inputURL || []).reduce(
(acc, field) => ({ ...acc, [field.id]: "" }),
{},
),
);
const [socketTopic, setSocketTopic] = useState<string | null>(null);
const [submissionStatus, setSubmissionStatus] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setInputValues({});
setSubmissionStatus(null);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [challenge, openSubmit]);

useEffect(() => {
if (!socketTopic) {
return;
}
console.log("subscribed topic: ", socketTopic);
function onConnect() {
setSubmissionStatus("0% Waiting for server verification");
}

function onDisconnect() {
toast.error("CONNECTION DISCONNECTED UNEXPECTED");
}

function onSubmissionEvent(data: any) {
if (data && data.status && data.progress && data.message) {
setSubmissionStatus(`${data.progress}% ${data.message}`);
if (data.progress === 100) {
if (data.success) {
toast.success("SUCCESSFULLY SUBMITTED");
} else {
toast.error("YOUR SUBMISSION FAILED");
}
}
}
}

socket.on("connect", onConnect);
socket.on("disconnect", onDisconnect);
socket.on(`verification:${socketTopic}`, onSubmissionEvent);

return () => {
socket.off("connect", onConnect);
socket.off("disconnect", onDisconnect);
socket.off(`verification:${socketTopic}`, onSubmissionEvent);
};
}, [socketTopic]);

const handleCloseModal = () => {
setOpenSubmit(false);
Expand All @@ -31,20 +88,28 @@ export const SubmitChallenge = ({ challenge }: { challenge: Challenge }) => {

return (
<div className="flex justify-center">
<div
onClick={() => setOpenSubmit(true)}
className="fixed z-[95] md:bottom-5 bottom-3 transform flex justify-center items-center gap-2 bg-[#4D58FF] md:w-fit w-[90%] px-5 cursor-pointer"
>
<Image
src={"/homescreen/submit.svg"}
alt="icon"
width={20}
height={20}
/>
<p className="text-lg font-vt323 uppercase !text-white">
Submit challenge
</p>
</div>
{address ? (
<div
onClick={() => setOpenSubmit(true)}
className="fixed z-[95] md:bottom-5 bottom-3 transform flex justify-center items-center gap-2 bg-[#4D58FF] md:w-fit w-[90%] px-5 cursor-pointer"
>
<Image
src={"/homescreen/submit.svg"}
alt="icon"
width={20}
height={20}
/>
<p className="text-lg font-vt323 uppercase !text-white">
Submit challenge
</p>
</div>
) : (
<div className="fixed z-[95] md:bottom-5 bottom-3 transform flex justify-center items-center gap-2 bg-[#ADADAD] md:w-fit w-[90%] px-5 cursor-pointer">
<p className="text-lg font-vt323 uppercase !text-white">
wallet not connected
</p>
</div>
)}
{openSubmit && (
<section
ref={modalRef}
Expand Down Expand Up @@ -114,13 +179,58 @@ export const SubmitChallenge = ({ challenge }: { challenge: Challenge }) => {
<DynamicSequentialInputs
inputFields={challenge.inputURL}
onSubmit={handleSubmit}
inputValues={inputValues}
setInputValues={setInputValues}
/>
</div>
{submissionStatus && (
<div className="pb-8 flex flex-row items-center text-black">
{submissionStatus}
</div>
)}
</div>
</section>
)}
{openConfirmSubmit && (
<ConfirmSubmitModal onClose={() => setOpenConfirmSubmit(false)} />
<ConfirmSubmitModal
loading={loading}
onClose={() => setOpenConfirmSubmit(false)}
onSubmit={() => {
setOpenConfirmSubmit(false);
const payload = composeSubmission(
challenge,
address || "",
inputValues,
);
setLoading(true);
axios
.post(
`${process.env.NEXT_PUBLIC_SERVER_BASE_URL}/verify`,
payload,
)
.then((response) => {
if (response && response.data && response.data.verificationId) {
setSocketTopic(response.data.verificationId);
} else {
toast.error("Submit failed for unknown error...");
}
})
.catch((error) => {
if (
error.response &&
error.response.data &&
error.response.data.error
) {
toast.error(error.response.data.error);
} else {
toast.error("Submit failed for server unknown error...");
}
})
.finally(() => {
setLoading(false);
});
}}
/>
)}
</div>
);
Expand Down
23 changes: 19 additions & 4 deletions packages/nextjs/mockup/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,12 @@ export const DATA_CHALLENGE_V2 = [
{
id: "staker",
title: "Enter Staker Contract",
placeholder: "Enter staker contract...",
placeholder: "Enter Staker contract...",
},
{
id: "external",
title: "Enter ExampleExternal Contract",
placeholder: "Enter ExampleExternal contract...",
},
{
id: "github",
Expand All @@ -193,7 +198,12 @@ export const DATA_CHALLENGE_V2 = [
{
id: "vendor",
title: "Enter Vendor Contract",
placeholder: "Enter vendor contract...",
placeholder: "Enter Vendor contract...",
},
{
id: "yourToken",
title: "Enter YourToken Contract",
placeholder: "Enter YourToken contract...",
},
{
id: "github",
Expand All @@ -215,8 +225,13 @@ export const DATA_CHALLENGE_V2 = [
},
{
id: "dicegame",
title: "Enter Dice Game Contract",
placeholder: "Enter Dice Game contract...",
title: "Enter DiceGame Contract",
placeholder: "Enter DiceGame contract...",
},
{
id: "riggedRoll",
title: "Enter RiggedRoll Contract",
placeholder: "Enter RiggedRoll contract...",
},
{
id: "github",
Expand Down
2 changes: 2 additions & 0 deletions packages/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@starknet-react/chains": "^0.1.7",
"@starknet-react/core": "^2.3.0",
"abi-wan-kanabi": "^2.2.2",
"axios": "1.7.9",
"blo": "^1.1.1",
"daisyui": "^4.7.3",
"get-starknet-core": "^3.2.0",
Expand All @@ -38,6 +39,7 @@
"react-player": "^2.16.0",
"rehype-raw": "^7.0.0",
"sharp": "^0.29.0",
"socket.io-client": "^4.8.1",
"starknet": "5.25.0",
"starknet-dev": "npm:[email protected]",
"type-fest": "^4.6.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/nextjs/services/socket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { io } from "socket.io-client";

export const socket = io(process.env.NEXT_PUBLIC_SERVER_BASE_URL);
Loading

0 comments on commit 4b88600

Please sign in to comment.