Compare commits

...

10 Commits

Author SHA1 Message Date
mehedi-hasan 3f8135bdbe fix: typo 2024-05-01 18:02:20 +06:00
mehedi-hasan cd68d6f64c feat: add validation message for title and employer input 2024-05-01 17:46:35 +06:00
mehedi-hasan 645f731a74 feat: add country, state, city job input with options 2024-05-01 17:19:41 +06:00
mehedi-hasan 64be0f87b1 feat: page load progressbar 2024-04-24 20:50:45 +06:00
mehedi-hasan 65ea9e5681 feat: country, state, city select 2024-04-24 20:02:12 +06:00
mehedi-hasan c4c8c34579 feat: add phone input field in profile form 2024-04-24 00:58:49 +06:00
mehedi-hasan 40265d8c72 feat: hide show task buttons on clicking next prev 2024-04-23 22:55:05 +06:00
mehedi-hasan a7049fc9e2 fix: quiz result 2024-04-23 22:10:10 +06:00
mehedi-hasan 4e75d7eea5 fix: hide buttons in desktop 2024-04-21 19:35:53 +06:00
mehedi-hasan 349381f7a2 fix: margin 2024-04-21 19:23:50 +06:00
17 changed files with 897 additions and 302 deletions

100
package-lock.json generated
View File

@ -44,15 +44,18 @@
"axios": "^1.6.8", "axios": "^1.6.8",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"cmdk": "^1.0.0",
"contentlayer": "^0.3.4", "contentlayer": "^0.3.4",
"eslint": "8.48.0", "eslint": "8.48.0",
"eslint-config-next": "^14.0.1", "eslint-config-next": "^14.0.1",
"geonames.js": "^3.0.6",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"lucide-react": "^0.291.0", "lucide-react": "^0.291.0",
"monaco-jsx-highlighter": "^2.77.77", "monaco-jsx-highlighter": "^2.77.77",
"monaco-themes": "^0.4.4", "monaco-themes": "^0.4.4",
"next": "^14.0.1", "next": "^14.0.1",
"next-mdx-remote": "^4.4.1", "next-mdx-remote": "^4.4.1",
"next-nprogress-bar": "^2.3.11",
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"postcss": "8.4.28", "postcss": "8.4.28",
"react": "^18.2.0", "react": "^18.2.0",
@ -61,6 +64,7 @@
"react-error-boundary": "^4.0.13", "react-error-boundary": "^4.0.13",
"react-error-overlay": "^6.0.11", "react-error-overlay": "^6.0.11",
"react-hook-form": "^7.47.0", "react-hook-form": "^7.47.0",
"react-phone-number-input": "^3.4.0",
"react-resizable-panels": "^2.0.16", "react-resizable-panels": "^2.0.16",
"rehype-pretty-code": "^0.13.1", "rehype-pretty-code": "^0.13.1",
"sharp": "^0.32.5", "sharp": "^0.32.5",
@ -4414,6 +4418,11 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
},
"node_modules/client-only": { "node_modules/client-only": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@ -4515,6 +4524,19 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/cmdk": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.0.0.tgz",
"integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==",
"dependencies": {
"@radix-ui/react-dialog": "1.0.5",
"@radix-ui/react-primitive": "1.0.3"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/color": { "node_modules/color": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@ -4646,6 +4668,11 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
}, },
"node_modules/country-flag-icons": {
"version": "1.5.11",
"resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.11.tgz",
"integrity": "sha512-B+mvFywunkRJs270k7kCBjhogvIA0uNn6GAXv6m2cPn3rrwqZzZVr2gBWcz+Cz7OGVWlcbERlYRIX0S6OGr8Bw=="
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -6152,6 +6179,23 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/geonames.js": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/geonames.js/-/geonames.js-3.0.6.tgz",
"integrity": "sha512-iDnDEzknKB7RXunjwYNgWg8ZbxjJhO9ChauXjo3njcsqhpHp3rpssqJLhATEp6T6NIybWlT/V3i6dxw+kFcICA==",
"dependencies": {
"axios": "^0.21.0",
"qs": "^6.9.4"
}
},
"node_modules/geonames.js/node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dependencies": {
"follow-redirects": "^1.14.0"
}
},
"node_modules/get-caller-file": { "node_modules/get-caller-file": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@ -6947,6 +6991,14 @@
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
"integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="
}, },
"node_modules/input-format": {
"version": "0.3.10",
"resolved": "https://registry.npmjs.org/input-format/-/input-format-0.3.10.tgz",
"integrity": "sha512-5cFv/kOZD7Ch0viprVkuYPDkAU7HBZYBx8QrIpQ6yXUWbAQ0+RQ8IIojDJOf/RO6FDJLL099HDSK2KoVZ2zevg==",
"dependencies": {
"prop-types": "^15.8.1"
}
},
"node_modules/internal-slot": { "node_modules/internal-slot": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
@ -7572,6 +7624,11 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/libphonenumber-js": {
"version": "1.10.61",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.61.tgz",
"integrity": "sha512-TsQsyzDttDvvzWNkbp/i0fVbzTGJIG0mUu/uNalIaRQEYeJxVQ/FPg+EJgSqfSXezREjM0V3RZ8cLVsKYhhw0Q=="
},
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@ -12745,6 +12802,14 @@
"react-dom": ">=16.x <=18.x" "react-dom": ">=16.x <=18.x"
} }
}, },
"node_modules/next-nprogress-bar": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/next-nprogress-bar/-/next-nprogress-bar-2.3.11.tgz",
"integrity": "sha512-OjSvsQwgSWa2qBMYO478QreGG9Jt82tr4wTQptmiyzNqqjzHCyKZNkhANnzPrjuFAoelIvmruJuakODofSnvTQ==",
"dependencies": {
"nprogress": "^0.2.0"
}
},
"node_modules/next-themes": { "node_modules/next-themes": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz",
@ -12863,6 +12928,11 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/nprogress": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
"integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA=="
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -13483,6 +13553,20 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/qs": {
"version": "6.12.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz",
"integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==",
"dependencies": {
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -13610,6 +13694,22 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"node_modules/react-phone-number-input": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/react-phone-number-input/-/react-phone-number-input-3.4.0.tgz",
"integrity": "sha512-anL8OAqlSnOXd6O+lidkprOO5+OpgW+ODrbfyLc6u8lOX8ghT0nO6ZOPrGjotpZND4cr0xxH+vu3dgbdUB2lBA==",
"dependencies": {
"classnames": "^2.5.1",
"country-flag-icons": "^1.5.11",
"input-format": "^0.3.10",
"libphonenumber-js": "^1.10.61",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/react-remove-scroll": { "node_modules/react-remove-scroll": {
"version": "2.5.5", "version": "2.5.5",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",

View File

@ -46,15 +46,18 @@
"axios": "^1.6.8", "axios": "^1.6.8",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"cmdk": "^1.0.0",
"contentlayer": "^0.3.4", "contentlayer": "^0.3.4",
"eslint": "8.48.0", "eslint": "8.48.0",
"eslint-config-next": "^14.0.1", "eslint-config-next": "^14.0.1",
"geonames.js": "^3.0.6",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"lucide-react": "^0.291.0", "lucide-react": "^0.291.0",
"monaco-jsx-highlighter": "^2.77.77", "monaco-jsx-highlighter": "^2.77.77",
"monaco-themes": "^0.4.4", "monaco-themes": "^0.4.4",
"next": "^14.0.1", "next": "^14.0.1",
"next-mdx-remote": "^4.4.1", "next-mdx-remote": "^4.4.1",
"next-nprogress-bar": "^2.3.11",
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"postcss": "8.4.28", "postcss": "8.4.28",
"react": "^18.2.0", "react": "^18.2.0",
@ -63,6 +66,7 @@
"react-error-boundary": "^4.0.13", "react-error-boundary": "^4.0.13",
"react-error-overlay": "^6.0.11", "react-error-overlay": "^6.0.11",
"react-hook-form": "^7.47.0", "react-hook-form": "^7.47.0",
"react-phone-number-input": "^3.4.0",
"react-resizable-panels": "^2.0.16", "react-resizable-panels": "^2.0.16",
"rehype-pretty-code": "^0.13.1", "rehype-pretty-code": "^0.13.1",
"sharp": "^0.32.5", "sharp": "^0.32.5",

View File

@ -1,6 +1,6 @@
import Header from "@/components/layout/header"; import Header from "@/components/layout/header";
import Sidebar from "@/components/layout/sidebar"; import Sidebar from "@/components/layout/sidebar";
import StoreProvider from "@/lib/store-provider"; // import StoreProvider from "@/lib/store-provider";
import { createClient } from "@/utils/supabase/server"; import { createClient } from "@/utils/supabase/server";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
@ -29,14 +29,14 @@ export default async function DashboardLayout({
return ( return (
<> <>
<StoreProvider lastUpdate={new Date().getTime()}> {/* <StoreProvider lastUpdate={new Date().getTime()}> */}
<Header /> <Header />
<div className="flex h-screen overflow-hidden"> <div className="flex h-screen overflow-hidden">
<Sidebar /> <Sidebar />
<main className="w-full pt-16">{children}</main> <main className="w-full pt-16">{children}</main>
</div> </div>
</StoreProvider> {/* </StoreProvider> */}
</> </>
); );
} }

View File

@ -1,16 +1,5 @@
import { ScrollArea } from "@/components/ui/scroll-area";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import markdownStyles from "@/styles/markdown-styles.module.css";
import { tutorialData } from "@/constants/tutorial-data"; import { tutorialData } from "@/constants/tutorial-data";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { Button, buttonVariants } from "@/components/ui/button";
import Link from "next/link";
import { cn } from "@/lib/utils";
import EditorWindow from "@/components/task/editor-window";
import { LANGUAGE_VERSIONS } from "@/constants/language-options"; import { LANGUAGE_VERSIONS } from "@/constants/language-options";
import { markdownToHtml } from "@/lib/markdown-to-html"; import { markdownToHtml } from "@/lib/markdown-to-html";
import TaskPage from "@/components/task-page"; import TaskPage from "@/components/task-page";
@ -18,10 +7,6 @@ import TaskPage from "@/components/task-page";
const languages = Object.keys(LANGUAGE_VERSIONS); const languages = Object.keys(LANGUAGE_VERSIONS);
const Page = async ({ params }: { params: { taskId: string[] } }) => { const Page = async ({ params }: { params: { taskId: string[] } }) => {
// const [activeMobileView, setActiveMobileView] = useState<
// "tutorial" | "code" | "output"
// >("tutorial");
if (!params.taskId[0] || !languages.includes(params.taskId[0])) { if (!params.taskId[0] || !languages.includes(params.taskId[0])) {
notFound(); notFound();
} }
@ -44,15 +29,13 @@ const Page = async ({ params }: { params: { taskId: string[] } }) => {
const content = await markdownToHtml(tutorial?.content || ""); const content = await markdownToHtml(tutorial?.content || "");
return ( return (
<> <TaskPage
<TaskPage params={params}
params={params} langTutorial={langTutorial}
langTutorial={langTutorial} tutorial={tutorial}
tutorial={tutorial} tutorialIndex={tutorialIndex}
tutorialIndex={tutorialIndex} content={content}
content={content} />
/>
</>
); );
}; };

View File

@ -4,6 +4,7 @@ import "@uploadthing/react/styles.css";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import "./globals.css"; import "./globals.css";
import StoreProvider from "@/lib/store-provider";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
@ -21,10 +22,13 @@ export default async function RootLayout({
return ( return (
<html lang="en" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<body className={`${inter.className} overflow-x-hidden`}> <body className={`${inter.className} overflow-x-hidden`}>
<Providers> <StoreProvider lastUpdate={new Date().getTime()}>
<Toaster /> <Providers>
{children} <Toaster />
</Providers>
{children}
</Providers>
</StoreProvider>
</body> </body>
</html> </html>
); );

View File

@ -1,4 +1,6 @@
"use client"; "use client";
import Geolocation from "@/components/geolocation";
import { PhoneInput } from "@/components/phone-input";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@ -29,7 +31,7 @@ import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangleIcon, Trash, Trash2Icon } from "lucide-react"; import { AlertTriangleIcon, Trash, Trash2Icon } from "lucide-react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
import { useState } from "react"; import { useEffect, useState } from "react";
import { SubmitHandler, useFieldArray, useForm } from "react-hook-form"; import { SubmitHandler, useFieldArray, useForm } from "react-hook-form";
interface ProfileFormType { interface ProfileFormType {
@ -56,8 +58,13 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
const [currentStep, setCurrentStep] = useState(0); const [currentStep, setCurrentStep] = useState(0);
const [data, setData] = useState({}); const [data, setData] = useState({});
const delta = currentStep - previousStep; const delta = currentStep - previousStep;
const [countryGeoId, setCountryGeoId] = useState<null | number>(null);
const [stateGeoId, setStateGeoId] = useState<null | number>(null);
const [countryGeoId2, setCountryGeoId2] = useState<null | number>(null);
const [stateGeoId2, setStateGeoId2] = useState<null | number>(null);
const defaultValues = { const defaultValues = {
// state: "",
jobs: [ jobs: [
{ {
jobtitle: "", jobtitle: "",
@ -65,6 +72,7 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
startdate: "", startdate: "",
enddate: "", enddate: "",
jobcountry: "", jobcountry: "",
jobstate: "",
jobcity: "", jobcity: "",
}, },
], ],
@ -96,7 +104,7 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
// console.log("product", res); // console.log("product", res);
} }
router.refresh(); router.refresh();
router.push(`/dashboard/products`); router.push(`/dashboard`);
} catch (error: any) { } catch (error: any) {
} finally { } finally {
setLoading(false); setLoading(false);
@ -135,12 +143,13 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
"email", "email",
"contactno", "contactno",
"country", "country",
"state",
"city", "city",
], ],
}, },
{ {
id: "Step 2", id: "Step 2",
name: "Professional Informations", name: "Professional Information",
// fields are mapping and flattening for the error to be trigger for the dynamic fields // fields are mapping and flattening for the error to be trigger for the dynamic fields
fields: fields fields: fields
?.map((_, index) => [ ?.map((_, index) => [
@ -149,6 +158,7 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
`jobs.${index}.startdate`, `jobs.${index}.startdate`,
`jobs.${index}.enddate`, `jobs.${index}.enddate`,
`jobs.${index}.jobcountry`, `jobs.${index}.jobcountry`,
`jobs.${index}.jobstate`,
`jobs.${index}.jobcity`, `jobs.${index}.jobcity`,
// Add other field names as needed // Add other field names as needed
]) ])
@ -182,8 +192,8 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
} }
}; };
const countries = [{ id: "wow", name: "india" }]; const countries = [{ id: "1", name: "india" }];
const cities = [{ id: "2", name: "kerala" }]; const cities = [{ id: "1", name: "kerala" }];
return ( return (
<> <>
@ -206,18 +216,18 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
{steps.map((step, index) => ( {steps.map((step, index) => (
<li key={step.name} className="md:flex-1"> <li key={step.name} className="md:flex-1">
{currentStep > index ? ( {currentStep > index ? (
<div className="group flex w-full flex-col border-l-4 border-sky-600 py-2 pl-4 transition-colors md:border-l-0 md:border-t-4 md:pb-0 md:pl-0 md:pt-4"> <div className="group flex w-full flex-col border-l-4 border-primary py-2 pl-4 transition-colors md:border-l-0 md:border-t-4 md:pb-0 md:pl-0 md:pt-4">
<span className="text-sm font-medium text-sky-600 transition-colors "> <span className="text-sm font-medium text-primary transition-colors ">
{step.id} {step.id}
</span> </span>
<span className="text-sm font-medium">{step.name}</span> <span className="text-sm font-medium">{step.name}</span>
</div> </div>
) : currentStep === index ? ( ) : currentStep === index ? (
<div <div
className="flex w-full flex-col border-l-4 border-sky-600 py-2 pl-4 md:border-l-0 md:border-t-4 md:pb-0 md:pl-0 md:pt-4" className="flex w-full flex-col border-l-4 border-primary py-2 pl-4 md:border-l-0 md:border-t-4 md:pb-0 md:pl-0 md:pt-4"
aria-current="step" aria-current="step"
> >
<span className="text-sm font-medium text-sky-600"> <span className="text-sm font-medium text-primary">
{step.id} {step.id}
</span> </span>
<span className="text-sm font-medium">{step.name}</span> <span className="text-sm font-medium">{step.name}</span>
@ -307,11 +317,20 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
<FormItem> <FormItem>
<FormLabel>Contact Number</FormLabel> <FormLabel>Contact Number</FormLabel>
<FormControl> <FormControl>
<Input {/* <Input
type="number" type="number"
placeholder="Enter you contact number" placeholder="Enter your contact number"
disabled={loading} disabled={loading}
{...field} {...field}
/> */}
<PhoneInput
id="phone"
// onChange={(v) => setPhone(v)}
// value={phone}
placeholder="Enter a phone number..."
defaultCountry="IN"
international
{...field}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -323,63 +342,68 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
name="country" name="country"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Country</FormLabel> <FormLabel
<Select // className="block mb-[6px] mt-[2px]"
disabled={loading}
onValueChange={field.onChange}
value={field.value}
defaultValue={field.value}
> >
<FormControl> Country
<SelectTrigger> </FormLabel>
<SelectValue <Geolocation
defaultValue={field.value} geoId={null}
placeholder="Select a country" onChange={(country, geoId) => {
/> setCountryGeoId(geoId);
</SelectTrigger> field.onChange(country);
</FormControl> }}
<SelectContent> isCountry
{/* @ts-ignore */} placeholderText="Select a country"
{countries.map((country) => ( searchPlaceholder="Search country..."
<SelectItem key={country.id} value={country.id}> emptyPlaceholder="No country found"
{country.name} isDisabled={false}
</SelectItem> />
))}
</SelectContent>
</Select>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={form.control}
name="state"
render={({ field }) => (
<FormItem>
<FormLabel>State</FormLabel>
<Geolocation
geoId={countryGeoId}
onChange={(state, geoId) => {
setStateGeoId(geoId);
field.onChange(state);
}}
isCountry={false}
placeholderText="Select a state"
searchPlaceholder="Search state..."
emptyPlaceholder="No state found"
isDisabled={!countryGeoId}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="city" name="city"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>City</FormLabel> <FormLabel>City</FormLabel>
<Select <Geolocation
disabled={loading} geoId={stateGeoId}
onValueChange={field.onChange} onChange={(v) => field.onChange(v)}
value={field.value} isCountry={false}
defaultValue={field.value} placeholderText="Select a city"
> searchPlaceholder="Search city..."
<FormControl> emptyPlaceholder="No city found"
<SelectTrigger> isDisabled={!stateGeoId}
<SelectValue />
defaultValue={field.value}
placeholder="Select a city"
/>
</SelectTrigger>
</FormControl>
<SelectContent>
{/* @ts-ignore */}
{cities.map((city) => (
<SelectItem key={city.id} value={city.id}>
{city.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
@ -498,31 +522,42 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Job country</FormLabel> <FormLabel>Job country</FormLabel>
<Select <Geolocation
disabled={loading} geoId={null}
onValueChange={field.onChange} onChange={(country, geoId) => {
value={field.value} setCountryGeoId2(geoId);
defaultValue={field.value} field.onChange(country);
> }}
<FormControl> isCountry
<SelectTrigger> placeholderText="Select job country"
<SelectValue searchPlaceholder="Search country..."
defaultValue={field.value} emptyPlaceholder="No country found"
placeholder="Select your job country" isDisabled={false}
/> />
</SelectTrigger>
</FormControl> <FormMessage />
<SelectContent> </FormItem>
{countries.map((country) => ( )}
<SelectItem />
key={country.id} <FormField
value={country.id} control={form.control}
> name={`jobs.${index}.jobstate`}
{country.name} render={({ field }) => (
</SelectItem> <FormItem>
))} <FormLabel>Job State</FormLabel>
</SelectContent> <Geolocation
</Select> geoId={countryGeoId2}
onChange={(state, geoId) => {
setStateGeoId2(geoId);
field.onChange(state);
}}
isCountry={false}
placeholderText="Select job state"
searchPlaceholder="Search state..."
emptyPlaceholder="No state found"
isDisabled={!countryGeoId2}
/>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
@ -533,28 +568,16 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Job city</FormLabel> <FormLabel>Job city</FormLabel>
<Select <Geolocation
disabled={loading} geoId={stateGeoId2}
onValueChange={field.onChange} onChange={(v) => field.onChange(v)}
value={field.value} isCountry={false}
defaultValue={field.value} placeholderText="Select job city"
> searchPlaceholder="Search city..."
<FormControl> emptyPlaceholder="No city found"
<SelectTrigger> isDisabled={!stateGeoId2}
<SelectValue />
defaultValue={field.value}
placeholder="Select your job city"
/>
</SelectTrigger>
</FormControl>
<SelectContent>
{cities.map((city) => (
<SelectItem key={city.id} value={city.id}>
{city.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
@ -577,6 +600,7 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
startdate: "", startdate: "",
enddate: "", enddate: "",
jobcountry: "", jobcountry: "",
jobstate: "",
jobcity: "", jobcity: "",
}) })
} }
@ -590,7 +614,7 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
<div> <div>
<h1>Completed</h1> <h1>Completed</h1>
<pre className="whitespace-pre-wrap"> <pre className="whitespace-pre-wrap">
{JSON.stringify(data)} {JSON.stringify(data, null, 2)}
</pre> </pre>
</div> </div>
)} )}
@ -604,11 +628,12 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
{/* Navigation */} {/* Navigation */}
<div className="mt-8 pt-5"> <div className="mt-8 pt-5">
<div className="flex justify-between"> <div className="flex justify-between">
<button <Button
type="button" type="button"
onClick={prev} onClick={prev}
disabled={currentStep === 0} disabled={currentStep === 0}
className="rounded bg-white px-2 py-1 text-sm font-semibold text-sky-900 shadow-sm ring-1 ring-inset ring-sky-300 hover:bg-sky-50 disabled:cursor-not-allowed disabled:opacity-50" size="icon"
// className="rounded bg-white px-2 py-1 text-sm font-semibold text-sky-900 shadow-sm ring-1 ring-inset ring-sky-300 hover:bg-sky-50 disabled:cursor-not-allowed disabled:opacity-50"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -624,12 +649,13 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
d="M15.75 19.5L8.25 12l7.5-7.5" d="M15.75 19.5L8.25 12l7.5-7.5"
/> />
</svg> </svg>
</button> </Button>
<button <Button
type="button" type="button"
onClick={next} onClick={next}
disabled={currentStep === steps.length - 1} disabled={currentStep === steps.length - 1}
className="rounded bg-white px-2 py-1 text-sm font-semibold text-sky-900 shadow-sm ring-1 ring-inset ring-sky-300 hover:bg-sky-50 disabled:cursor-not-allowed disabled:opacity-50" size="icon"
// className="rounded bg-white px-2 py-1 text-sm font-semibold text-sky-900 shadow-sm ring-1 ring-inset ring-sky-300 hover:bg-sky-50 disabled:cursor-not-allowed disabled:opacity-50"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -645,7 +671,7 @@ export const CreateProfileOne: React.FC<ProfileFormType> = ({
d="M8.25 4.5l7.5 7.5-7.5 7.5" d="M8.25 4.5l7.5 7.5-7.5 7.5"
/> />
</svg> </svg>
</button> </Button>
</div> </div>
</div> </div>
</> </>

View File

@ -0,0 +1,161 @@
"use client";
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { FC, useEffect, useState } from "react";
import Geonames from "geonames.js";
import { FormControl } from "./ui/form";
const frameworks = [
{
value: "next.js",
label: "Next.js",
},
{
value: "sveltekit",
label: "SvelteKit",
},
{
value: "nuxt.js",
label: "Nuxt.js",
},
{
value: "remix",
label: "Remix",
},
{
value: "astro",
label: "Astro",
},
];
// @ts-ignore
const geonames = new Geonames({
username: "thalesandrade",
lan: "en",
encoding: "JSON",
});
interface Props {
geoId: number | null;
onChange: (country: string, geoId: number) => void;
isCountry: boolean;
placeholderText: string;
searchPlaceholder: string;
emptyPlaceholder: string;
isDisabled: boolean;
}
const Geolocation: FC<Props> = ({
geoId,
onChange,
isCountry,
placeholderText,
searchPlaceholder,
emptyPlaceholder,
isDisabled,
}) => {
const [open, setOpen] = useState(false);
const [currentValue, setCurrentValue] = useState<null | number>(null);
const [options, setOptions] = useState<
{ countryName: string; geonameId: number; name: string }[]
>([]);
useEffect(() => {
try {
const data = () => {
isCountry
? geonames.countryInfo({}).then((res: any) => {
// console.log(res);
setOptions(res.geonames);
})
: geonames.children({ geonameId: geoId }).then((res: any) => {
if (res.totalResultsCount) setOptions(res.geonames);
});
};
data();
} catch (err) {
console.error(err);
}
}, [geoId, isCountry]);
const currentPlaceholderText =
currentValue && isCountry
? options.find((option: any) => option.geonameId === currentValue)
?.countryName
: currentValue
? options.find((option: any) => option.geonameId === currentValue)?.name
: placeholderText;
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild disabled={isDisabled}>
<FormControl>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between"
>
{currentPlaceholderText}
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-[200px] md:w-[400px] p-0">
<Command>
<CommandList>
<CommandInput placeholder={searchPlaceholder} className="h-9" />
<CommandEmpty>{emptyPlaceholder}</CommandEmpty>
<CommandGroup>
{options.map((v: any, index) => (
<CommandItem
key={index}
value={v.geonameId}
onSelect={() => {
setCurrentValue(
v.geonameId === currentValue ? null : v.geonameId,
);
const name = isCountry ? v.countryName : v.name;
onChange(
v.geonameId === currentValue ? "" : name,
v.geonameId === currentValue ? null : v.geonameId,
);
setOpen(false);
}}
>
{isCountry ? v.countryName : v.name}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
currentValue === v.geonameId
? "opacity-100"
: "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
};
export default Geolocation;

View File

@ -1,11 +1,19 @@
"use client"; "use client";
import React from "react"; import React from "react";
import ThemeProvider from "./ThemeToggle/theme-provider"; import ThemeProvider from "./ThemeToggle/theme-provider";
import { AppProgressBar as ProgressBar } from "next-nprogress-bar";
export default function Providers({ children }: { children: React.ReactNode }) { export default function Providers({ children }: { children: React.ReactNode }) {
return ( return (
<> <>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem> <ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
{children} {children}
<ProgressBar
height="4px"
color="hsl(217.2 91.2% 59.8%)"
options={{ showSpinner: false }}
shallowRouting
/>
</ThemeProvider> </ThemeProvider>
</> </>
); );

View File

@ -0,0 +1,167 @@
import { CheckIcon, ChevronsUpDown } from "lucide-react";
import * as React from "react";
import * as RPNInput from "react-phone-number-input";
import flags from "react-phone-number-input/flags";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { Input, InputProps } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { ScrollArea } from "./ui/scroll-area";
type PhoneInputProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
"onChange" | "value"
> &
Omit<RPNInput.Props<typeof RPNInput.default>, "onChange"> & {
onChange?: (value: RPNInput.Value) => void;
};
const PhoneInput: React.ForwardRefExoticComponent<PhoneInputProps> =
React.forwardRef<React.ElementRef<typeof RPNInput.default>, PhoneInputProps>(
({ className, onChange, ...props }, ref) => {
return (
<RPNInput.default
ref={ref}
className={cn("flex", className)}
flagComponent={FlagComponent}
countrySelectComponent={CountrySelect}
inputComponent={InputComponent}
/**
* Handles the onChange event.
*
* react-phone-number-input might trigger the onChange event as undefined
* when a valid phone number is not entered. To prevent this,
* the value is coerced to an empty string.
*
* @param {E164Number | undefined} value - The entered value
*/
onChange={(value) => onChange?.(value || "")}
{...props}
/>
);
},
);
PhoneInput.displayName = "PhoneInput";
const InputComponent = React.forwardRef<HTMLInputElement, InputProps>(
({ className, ...props }, ref) => (
<Input
className={cn("rounded-e-lg rounded-s-none", className)}
{...props}
ref={ref}
/>
),
);
InputComponent.displayName = "InputComponent";
type CountrySelectOption = { label: string; value: RPNInput.Country };
type CountrySelectProps = {
disabled?: boolean;
value: RPNInput.Country;
onChange: (value: RPNInput.Country) => void;
options: CountrySelectOption[];
};
const CountrySelect = ({
disabled,
value,
onChange,
options,
}: CountrySelectProps) => {
const handleSelect = React.useCallback(
(country: RPNInput.Country) => {
onChange(country);
},
[onChange],
);
return (
<Popover>
<PopoverTrigger asChild>
<Button
type="button"
variant={"outline"}
className={cn("flex gap-1 rounded-e-none rounded-s-lg px-3")}
disabled={disabled}
>
<FlagComponent country={value} countryName={value} />
<ChevronsUpDown
className={cn(
"-mr-2 h-4 w-4 opacity-50",
disabled ? "hidden" : "opacity-100",
)}
/>
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0">
<Command>
<CommandList>
<ScrollArea className="h-72">
<CommandInput placeholder="Search country..." />
<CommandEmpty>No country found.</CommandEmpty>
<CommandGroup>
{options
.filter((x) => x.value)
.map((option) => (
<CommandItem
className="gap-2"
key={option.value}
onSelect={() => handleSelect(option.value)}
>
<FlagComponent
country={option.value}
countryName={option.label}
/>
<span className="flex-1 text-sm">{option.label}</span>
{option.value && (
<span className="text-foreground/50 text-sm">
{`+${RPNInput.getCountryCallingCode(option.value)}`}
</span>
)}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
option.value === value ? "opacity-100" : "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</ScrollArea>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
};
const FlagComponent = ({ country, countryName }: RPNInput.FlagProps) => {
const Flag = flags[country];
return (
<span className="bg-foreground/20 flex h-4 w-6 overflow-hidden rounded-sm">
{Flag && <Flag title={countryName} />}
</span>
);
};
FlagComponent.displayName = "FlagComponent";
export { PhoneInput };

View File

@ -25,6 +25,7 @@ export const FinishedScreen = () => {
totalCorrectAns, totalCorrectAns,
questionsArray, questionsArray,
reset, reset,
correctAns
} = useQuizStore( } = useQuizStore(
useShallow((store) => ({ useShallow((store) => ({
points: store.points, points: store.points,
@ -33,6 +34,7 @@ export const FinishedScreen = () => {
totalCorrectAns: store.totalCorrectAns, totalCorrectAns: store.totalCorrectAns,
questionsArray: store.questionsArray, questionsArray: store.questionsArray,
reset: store.reset, reset: store.reset,
correctAns: store.correctAns
})), })),
); );
// const {points, highscore} = useSelector(store => store.questions) // const {points, highscore} = useSelector(store => store.questions)
@ -58,10 +60,14 @@ export const FinishedScreen = () => {
<CardTitle>Quiz Results</CardTitle> <CardTitle>Quiz Results</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<p className="text-green-500">Correct: {totalCorrectAns}</p> <p className="text-green-500">Correct: {correctAns.length}</p>
<p className="text-rose-500">
Incorrect: {questionsArray.length - correctAns.length}
</p>
{/* <p className="text-green-500">Correct: {totalCorrectAns}</p>
<p className="text-rose-500"> <p className="text-rose-500">
Incorrect: {questionsArray.length - totalCorrectAns} Incorrect: {questionsArray.length - totalCorrectAns}
</p> </p> */}
</CardContent> </CardContent>
<CardFooter> <CardFooter>
<Button <Button

View File

@ -7,17 +7,19 @@ import { Button } from "../ui/button";
// import { useDispatch, useSelector } from 'react-redux' // import { useDispatch, useSelector } from 'react-redux'
// import { gameEnded, nextQuestion } from '../features/questions/questionsSlice' // import { gameEnded, nextQuestion } from '../features/questions/questionsSlice'
export const Next = () => { export const Next = ({ currentCorrectAns }: { currentCorrectAns: string }) => {
const router = useRouter(); const router = useRouter();
const { index, gameEnded, nextQuestion, questionsArray } = useQuizStore( const { index, gameEnded, nextQuestion, questionsArray, newAnswer } =
useShallow((store) => ({ useQuizStore(
index: store.index, useShallow((store) => ({
gameEnded: store.gameEnded, index: store.index,
nextQuestion: store.nextQuestion, gameEnded: store.gameEnded,
questionsArray: store.questionsArray, nextQuestion: store.nextQuestion,
})), questionsArray: store.questionsArray,
); newAnswer: store.newAnswer,
})),
);
// const {index} = useSelector(store => store.questions) // const {index} = useSelector(store => store.questions)
@ -31,7 +33,13 @@ export const Next = () => {
if (index < questionsArray.length - 1) if (index < questionsArray.length - 1)
return ( return (
<Button className="" onClick={nextQuestion}> <Button
className=""
onClick={() => {
newAnswer(currentCorrectAns);
nextQuestion();
}}
>
Next Next
</Button> </Button>
); );

View File

@ -18,6 +18,7 @@ export const Question = ({ difficulty }: { difficulty: string }) => {
// const { status, index, currentQuestion, answer } = useSelector( // const { status, index, currentQuestion, answer } = useSelector(
// (store) => store.questions, // (store) => store.questions,
// ); // );
const [currentCorrectAns, setCurrentCorrectAns] = useState("");
const [isStart, setIsStart] = useState(false); const [isStart, setIsStart] = useState(false);
@ -75,7 +76,10 @@ export const Question = ({ difficulty }: { difficulty: string }) => {
{options?.map((option: any, index: number) => { {options?.map((option: any, index: number) => {
return ( return (
<Button <Button
variant={`${answer === option ? "default" : "secondary"}`} // variant={`${answer === option ? "default" : "secondary"}`}
variant={`${
currentCorrectAns === option ? "default" : "secondary"
}`}
key={index} key={index}
className={cn( className={cn(
"justify-start text-lg py-6 disabled:pointer-events-auto disabled:cursor-not-allowed", "justify-start text-lg py-6 disabled:pointer-events-auto disabled:cursor-not-allowed",
@ -90,8 +94,9 @@ export const Question = ({ difficulty }: { difficulty: string }) => {
// : "" // : ""
// } // }
// `} // `}
disabled={ !isStart} disabled={!isStart}
onClick={() => newAnswer(option)} onClick={() => setCurrentCorrectAns(option)}
// onClick={() => newAnswer(option)}
> >
{option} {option}
</Button> </Button>
@ -100,9 +105,18 @@ export const Question = ({ difficulty }: { difficulty: string }) => {
</div> </div>
</div> </div>
<div className="flex justify-between mt-6 items-center"> <div className="flex justify-between mt-6 items-center">
<div>{answer && <Next />}</div> <div>
{/* {answer && <Next currentCorrectAns={currentCorrectAns} />} */}
{currentCorrectAns !== "" && (
<Next currentCorrectAns={currentCorrectAns} />
)}
</div>
{isStart ? <Timer /> : <Button onClick={() => setIsStart(true)}>Start</Button>} {isStart ? (
<Timer />
) : (
<Button onClick={() => setIsStart(true)}>Start</Button>
)}
</div> </div>
</> </>
)} )}

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { import {
ResizableHandle, ResizableHandle,
@ -8,19 +8,14 @@ import {
ResizablePanelGroup, ResizablePanelGroup,
} from "@/components/ui/resizable"; } from "@/components/ui/resizable";
import markdownStyles from "@/styles/markdown-styles.module.css"; import markdownStyles from "@/styles/markdown-styles.module.css";
import { tutorialData } from "@/constants/tutorial-data";
import { notFound } from "next/navigation";
import { Button, buttonVariants } from "@/components/ui/button"; import { Button, buttonVariants } from "@/components/ui/button";
import Link from "next/link"; import Link from "next/link";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import EditorWindow from "@/components/task/editor-window"; import EditorWindow from "@/components/task/editor-window";
import { LANGUAGE_VERSIONS } from "@/constants/language-options";
import { markdownToHtml } from "@/lib/markdown-to-html";
import Header from "./task/header";
import Image from "next/image"; import Image from "next/image";
import ThemeToggle from "./layout/ThemeToggle/theme-toggle"; import ThemeToggle from "./layout/ThemeToggle/theme-toggle";
import { useQuizStore } from "@/lib/quiz-store";
const languages = Object.keys(LANGUAGE_VERSIONS); import { useShallow } from "zustand/react/shallow";
interface LangTutorial { interface LangTutorial {
id: number; id: number;
@ -55,55 +50,50 @@ const TaskPage = ({
"tutorial" | "code" | "output" "tutorial" | "code" | "output"
>("tutorial"); >("tutorial");
// if (!params.taskId[0] || !languages.includes(params.taskId[0])) { const { isTaskPageLoading, updateTaskPageLoading } = useQuizStore(
// notFound(); useShallow((store) => ({
// } isTaskPageLoading: store.isTaskPageLoading,
updateTaskPageLoading: store.updateTaskPageLoading,
})),
);
// const langTutorial = tutorialData.find( useEffect(() => {
// (t) => t.language === params.taskId[0], if (typeof window !== "undefined") {
// )!; // console.log(isTaskPageLoading);
updateTaskPageLoading(false);
}
}, []);
// const tutorial = langTutorial.tutorial.find(
// (t) => t.id === (Number(params.taskId[1]) || 1),
// );
// const tutorialIndex = langTutorial.tutorial.findIndex(
// (t) => t.id === (Number(params.taskId[1]) || 1),
// );
// if (!tutorial || tutorialIndex === -1) {
// notFound();
// }
// const content = await markdownToHtml(tutorial?.content || "");
return ( return (
<> <>
<div className="flex md:hidden items-center justify-center gap-4 fixed md:static w-screen bottom-0 left-0 z-50 px-4 py-3 bg-background border-t"> <div className="md:hidden">
<Button <div
variant={activeMobileView === "tutorial" ? "default" : "outline"} className={cn(
onClick={() => setActiveMobileView("tutorial")} "flex items-center justify-center gap-4 fixed w-screen bottom-0 left-0 z-50 px-4 py-3 bg-background border-t transition-transform",
isTaskPageLoading ? "translate-y-full" : "translate-y-0",
)}
> >
Tutorial <Button
</Button> variant={activeMobileView === "tutorial" ? "default" : "outline"}
<Button onClick={() => setActiveMobileView("tutorial")}
variant={activeMobileView === "code" ? "default" : "outline"} >
onClick={() => setActiveMobileView("code")} Tutorial
> </Button>
Code <Button
</Button> variant={activeMobileView === "code" ? "default" : "outline"}
<Button onClick={() => setActiveMobileView("code")}
variant={activeMobileView === "output" ? "default" : "outline"} >
onClick={() => setActiveMobileView("output")} Code
> </Button>
Output <Button
</Button> variant={activeMobileView === "output" ? "default" : "outline"}
onClick={() => setActiveMobileView("output")}
>
Output
</Button>
</div>
</div> </div>
<div className="md:hidden w-full h-full"> <div className="md:hidden w-full h-full">
{/* <ResizablePanelGroup
direction="vertical"
className="max-w-[1440px] mx-auto h-full"
> */}
{/* <ResizablePanel defaultSize={50}> */}
<div <div
className={cn( className={cn(
"fixed left-0 top-0 h-[calc(100vh-59px)] w-screen transition-transform translate-x-0", "fixed left-0 top-0 h-[calc(100vh-59px)] w-screen transition-transform translate-x-0",
@ -136,16 +126,14 @@ const TaskPage = ({
</Link> </Link>
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
{/* <Link href="/">Home</Link> */}
<Link href="/dashboard">Dashboard</Link> <Link href="/dashboard">Dashboard</Link>
{/* <Link href="/login">Login</Link> */}
<ThemeToggle /> <ThemeToggle />
</div> </div>
</nav> </nav>
</div> </div>
<ScrollArea className="h-[calc(100vh-calc(53px+59px))]"> <ScrollArea className="h-[calc(100vh-calc(53px+59px))]">
<div className="p-4 flex flex-col gap-4 h-full mb-14"> <div className="p-4 flex flex-col gap-4 h-full mb-16">
<div <div
className={cn("grow", markdownStyles["markdown"])} className={cn("grow", markdownStyles["markdown"])}
dangerouslySetInnerHTML={{ __html: content }} dangerouslySetInnerHTML={{ __html: content }}
@ -154,6 +142,7 @@ const TaskPage = ({
<div> <div>
{langTutorial?.tutorial[tutorialIndex - 1] && ( {langTutorial?.tutorial[tutorialIndex - 1] && (
<Link <Link
onClick={() => updateTaskPageLoading(true)}
href={`/dashboard/task/${params.taskId[0]}/${ href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) - 1 Number(params.taskId[1]) - 1
}`} }`}
@ -169,6 +158,7 @@ const TaskPage = ({
<div> <div>
{langTutorial?.tutorial[tutorialIndex + 1] && ( {langTutorial?.tutorial[tutorialIndex + 1] && (
<Link <Link
onClick={() => updateTaskPageLoading(true)}
href={`/dashboard/task/${params.taskId[0]}/${ href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) + 1 Number(params.taskId[1]) + 1
}`} }`}
@ -184,70 +174,14 @@ const TaskPage = ({
</div> </div>
</ScrollArea> </ScrollArea>
</div> </div>
{/* </ResizablePanel> */}
{/* <ResizableHandle withHandle /> */}
{/* <ResizablePanel defaultSize={50}> */}
<EditorWindow <EditorWindow
activeMobileView={activeMobileView} activeMobileView={activeMobileView}
language={params.taskId[0]} language={params.taskId[0]}
step={Number(params.taskId[1]) || 1} step={Number(params.taskId[1]) || 1}
/> />
{/* </ResizablePanel> */}
{/* </ResizablePanelGroup> */}
</div> </div>
{/* <div className="md:hidden w-full h-full">
<ResizablePanelGroup
direction="vertical"
className="max-w-[1440px] mx-auto h-full"
>
<ResizablePanel defaultSize={50}>
<ScrollArea className="h-[calc(100vh-55px)]">
<div className="px-4 py-6 flex flex-col gap-4 h-full mb-[60vh]">
<div
className={cn("grow", markdownStyles["markdown"])}
dangerouslySetInnerHTML={{ __html: content }}
></div>
<div className="flex justify-between">
<div className="mb-6">
{langTutorial?.tutorial[tutorialIndex - 1] && (
<Link
href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) - 1
}`}
className={buttonVariants({
variant: "outline",
})}
>
Prev
</Link>
)}
</div>
<div className="mb-6">
{langTutorial?.tutorial[tutorialIndex + 1] && (
<Link
href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) + 1
}`}
className={buttonVariants({ variant: "outline" })}
>
Next
</Link>
)}
</div>
</div>
</div>
</ScrollArea>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={50}>
<EditorWindow
language={params.taskId[0]}
step={Number(params.taskId[1]) || 1}
/>
</ResizablePanel>
</ResizablePanelGroup>
</div> */}
<div className="hidden md:block w-full h-full"> <div className="hidden md:block w-full h-full">
<ResizablePanelGroup <ResizablePanelGroup
direction="horizontal" direction="horizontal"
@ -255,14 +189,14 @@ const TaskPage = ({
> >
<ResizablePanel defaultSize={50}> <ResizablePanel defaultSize={50}>
<ScrollArea className="h-[calc(100vh-55px)]"> <ScrollArea className="h-[calc(100vh-55px)]">
<div className="px-4 py-6 flex flex-col gap-4 h-full"> <div className="px-4 py-6 flex flex-col gap-4 h-full mb-6">
<div <div
className={cn("grow", markdownStyles["markdown"])} className={cn("grow", markdownStyles["markdown"])}
dangerouslySetInnerHTML={{ __html: content }} dangerouslySetInnerHTML={{ __html: content }}
></div> ></div>
<div className="flex justify-between"> <div className="flex justify-between">
<div className="mb-6"> <div>
{langTutorial?.tutorial[tutorialIndex - 1] && ( {langTutorial?.tutorial[tutorialIndex - 1] && (
<Link <Link
href={`/dashboard/task/${params.taskId[0]}/${ href={`/dashboard/task/${params.taskId[0]}/${
@ -277,7 +211,7 @@ const TaskPage = ({
)} )}
</div> </div>
<div className="mb-6"> <div>
{langTutorial?.tutorial[tutorialIndex + 1] && ( {langTutorial?.tutorial[tutorialIndex + 1] && (
<Link <Link
href={`/dashboard/task/${params.taskId[0]}/${ href={`/dashboard/task/${params.taskId[0]}/${

View File

@ -12,7 +12,6 @@ import {
ResizablePanelGroup, ResizablePanelGroup,
} from "@/components/ui/resizable"; } from "@/components/ui/resizable";
import { ThemeDropdown } from "./theme-dropdown"; import { ThemeDropdown } from "./theme-dropdown";
import LanguagesDropdown from "./languages-dropdown";
import Output from "./output"; import Output from "./output";
import { tutorialData } from "@/constants/tutorial-data"; import { tutorialData } from "@/constants/tutorial-data";
import { notFound, useRouter } from "next/navigation"; import { notFound, useRouter } from "next/navigation";
@ -93,13 +92,10 @@ export default function EditorWindow({
if (!tutorial) { if (!tutorial) {
notFound(); notFound();
} }
// console.log(tutorial);
return ( return (
<> <>
<div className="md:hidden"> <div className="md:hidden">
{/* <ResizablePanelGroup direction="vertical" className="border-r">
<ResizablePanel defaultSize={50}> */}
<div <div
className={cn( className={cn(
"fixed left-0 top-0 h-[calc(100vh-59px)] w-screen translate-x-full transition-transform ", "fixed left-0 top-0 h-[calc(100vh-59px)] w-screen translate-x-full transition-transform ",
@ -117,10 +113,6 @@ export default function EditorWindow({
handleThemeChange={handleThemeChange} handleThemeChange={handleThemeChange}
theme={theme} theme={theme}
/> />
{/* <LanguagesDropdown
handleLanguageChange={handleLanguageChange}
language={lang}
/> */}
</div> </div>
<div className="h-[calc(100vh-calc(55px+53px))]"> <div className="h-[calc(100vh-calc(55px+53px))]">
<CodeEditor <CodeEditor
@ -135,9 +127,7 @@ export default function EditorWindow({
/> />
</div> </div>
</div> </div>
{/* </ResizablePanel> */}
{/* <ResizableHandle withHandle /> */}
{/* <ResizablePanel defaultSize={50}> */}
<ErrorBoundary FallbackComponent={PreviewErrorFallback}> <ErrorBoundary FallbackComponent={PreviewErrorFallback}>
<div <div
className={cn( className={cn(
@ -167,8 +157,6 @@ export default function EditorWindow({
)} )}
</div> </div>
</ErrorBoundary> </ErrorBoundary>
{/* </ResizablePanel> */}
{/* </ResizablePanelGroup> */}
</div> </div>
<div className="hidden md:block h-[calc(100vh-55px)]"> <div className="hidden md:block h-[calc(100vh-55px)]">

View File

@ -0,0 +1,155 @@
"use client"
import * as React from "react"
import { type DialogProps } from "@radix-ui/react-dialog"
import { MagnifyingGlassIcon } from "@radix-ui/react-icons"
import { Command as CommandPrimitive } from "cmdk"
import { cn } from "@/lib/utils"
import { Dialog, DialogContent } from "@/components/ui/dialog"
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className
)}
{...props}
/>
))
Command.displayName = CommandPrimitive.displayName
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<MagnifyingGlassIcon className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
))
CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
))
CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-border", className)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled='true']:pointer-events-none data-[disabled='true']:opacity-50",
className
)}
{...props}
/>
))
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
CommandShortcut.displayName = "CommandShortcut"
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}

View File

@ -1,29 +1,49 @@
import * as z from "zod"; import * as z from "zod";
import { isValidPhoneNumber } from "react-phone-number-input";
export const profileSchema = z.object({ export const profileSchema = z.object({
firstname: z firstname: z
.string() .string({ required_error: "Please enter your first name" })
.min(3, { message: "Product Name must be at least 3 characters" }), .trim()
.min(1, { message: "Please enter your first name" }),
lastname: z lastname: z
.string() .string({ required_error: "Please enter your last name" })
.min(3, { message: "Product Name must be at least 3 characters" }), .trim()
.min(1, { message: "Please enter your last name" }),
email: z email: z
.string() .string({ required_error: "Please enter your email" })
.email({ message: "Product Name must be at least 3 characters" }), .email({ message: "Please enter a valid email" }),
contactno: z.coerce.number(), contactno: z
country: z.string().min(1, { message: "Please select a category" }), .string({ required_error: "Please enter your phone number" })
city: z.string().min(1, { message: "Please select a category" }), .refine((value) => isValidPhoneNumber(value), {
message: "Please enter a valid phone number",
}),
country: z
.string({ required_error: "Please select your country" })
.trim()
.min(1, { message: "Please select your country" }),
state: z
.string({ required_error: "Please select your state" })
.trim()
.min(1, { message: "Please select your state" }),
city: z
.string({ required_error: "Please select your city" })
.trim()
.min(1, { message: "Please select your city" }),
// jobs array is for the dynamic fields // jobs array is for the dynamic fields
jobs: z.array( jobs: z.array(
z.object({ z.object({
jobcountry: z.string().min(1, { message: "Please select a category" }), jobcountry: z.string().min(1, { message: "Please select a country" }),
jobcity: z.string().min(1, { message: "Please select a category" }), jobstate: z.string().min(1, { message: "Please select a job state" }),
jobcity: z.string().min(1, { message: "Please select a city" }),
jobtitle: z jobtitle: z
.string() .string()
.min(3, { message: "Product Name must be at least 3 characters" }), .trim()
.min(1, { message: "Please enter your job title" }),
employer: z employer: z
.string() .string()
.min(3, { message: "Product Name must be at least 3 characters" }), .trim()
.min(1, { message: "Please enter your employer name" }),
startdate: z startdate: z
.string() .string()
.refine((value) => /^\d{4}-\d{2}-\d{2}$/.test(value), { .refine((value) => /^\d{4}-\d{2}-\d{2}$/.test(value), {

View File

@ -39,6 +39,10 @@ export interface StoreInterface {
// increment: () => void; // increment: () => void;
// decrement: () => void; // decrement: () => void;
reset: () => void; reset: () => void;
correctAns: string[];
isTaskPageLoading: boolean;
updateTaskPageLoading: (value: boolean) => void;
} }
function getDefaultInitialState() { function getDefaultInitialState() {
@ -65,6 +69,8 @@ function getDefaultInitialState() {
highscore: 0, highscore: 0,
secondsRemaining: 210, secondsRemaining: 210,
totalCorrectAns: 0, totalCorrectAns: 0,
correctAns: [],
isTaskPageLoading: false,
}; };
} }
@ -94,10 +100,15 @@ export function initializeStore(preloadedState: PreloadedStoreInterface) {
// console.log(get().totalCorrectAns); // console.log(get().totalCorrectAns);
set({ set({
answer, answer,
points: // points:
answer === get().currentQuestion.correctAnswer // answer === get().currentQuestion.correctAnswer
? get().points + 20 // ? get().points + 20
: get().points, // : get().points,
correctAns:
answer === get().currentQuestion.correctAnswer &&
!get().correctAns.includes(answer)
? [...get().correctAns, answer]
: get().correctAns,
totalCorrectAns: totalCorrectAns:
answer === get().currentQuestion.correctAnswer answer === get().currentQuestion.correctAnswer
? get().totalCorrectAns + 1 ? get().totalCorrectAns + 1
@ -167,5 +178,11 @@ export function initializeStore(preloadedState: PreloadedStoreInterface) {
// set({ // set({
// count: getDefaultInitialState().count, // count: getDefaultInitialState().count,
// }), // }),
updateTaskPageLoading: (value) => {
console.log(value)
set({
isTaskPageLoading: value,
})},
})); }));
} }