diff --git a/app/api/code/submit/submit-helpers.ts b/app/api/code/submit/submit-helpers.ts index 7a53569..a651727 100644 --- a/app/api/code/submit/submit-helpers.ts +++ b/app/api/code/submit/submit-helpers.ts @@ -7,12 +7,13 @@ const DOMPurify = createDOMPurify(window); // Sanitizer function export const validateHTML = (html: string): boolean => { + html = html.replace(/\n/g, " ").replace(/\s+/g, " ").trim(); if (!isHtml(html)) { return false; } const cleanHTML = DOMPurify.sanitize(html, { - ALLOWED_ATTR: ["href", "src", "alt", "title", "style"], + ALLOWED_ATTR: ["style", "class"], FORBID_TAGS: [ "script", "iframe", @@ -21,6 +22,8 @@ export const validateHTML = (html: string): boolean => { "link", "style", "form", + "img", + "a", ], FORBID_ATTR: [ "onerror", diff --git a/app/page.tsx b/app/page.tsx index 6293a4d..a3ac867 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -6,12 +6,12 @@ import AboutTailspin from "@/components/landing/about-tailspin"; import FAQ from "@/components/landing/faq"; import Timeline from "@/components/landing/timeline"; import ThanksPage from "@/components/landing/thanks"; -import StepperCard from "@/components/ui/useCode/StepperCard"; import RatingBody from "@/components/core/rating-area/rating-component"; import GrowOnScroll from "@/components/ui/grow-on-scroll"; import Footer from "@/components/landing/footer"; import ComponentCarousel from "@/components/ui/component-carousel"; import { Separator } from "@/components/ui/separator"; +import ChallengeMain from "@/components/landing/challenge/challenge-main"; export default function Home() { return ( @@ -41,12 +41,13 @@ export default function Home() {

We hate to be non-inclusive towards phones and - tablets, however we want to provide you with the - best experience possible! + tablets, however coding on small screens is + currently unsupported! Try us on your + laptop/desktop!

- + @@ -56,11 +57,11 @@ export default function Home() {
, ]} - title='Some useless information' + title='Incase you were wondering...' />
-
+
diff --git a/components/core/code-area-actions/submit-button.tsx b/components/core/code-area-actions/submit-button.tsx index ed0354c..b33e4f0 100644 --- a/components/core/code-area-actions/submit-button.tsx +++ b/components/core/code-area-actions/submit-button.tsx @@ -82,8 +82,8 @@ const SubmitButton = () => { }; const handleContinueButtonClick = () => { - setContinueClicked(true); handleReset(); + setContinueClicked(true); router.push("/"); }; diff --git a/components/core/rating-area/components/submit-rating-component.tsx b/components/core/rating-area/components/submit-rating-component.tsx index 16f5250..d87f4a7 100644 --- a/components/core/rating-area/components/submit-rating-component.tsx +++ b/components/core/rating-area/components/submit-rating-component.tsx @@ -3,6 +3,7 @@ import { usePutRating } from "@/client-side-queries/rq-queries/rating-submit"; import { Button } from "@/components/ui/button"; import { useRatingStore } from "@/data-store/rating-store"; +import { saveToLocalStorage } from "@/lib/localStorage"; import { Check, Loader2 } from "lucide-react"; import { useEffect, useState } from "react"; import toast from "react-hot-toast"; @@ -28,6 +29,7 @@ const RatingSubmitButton = () => { if (mutation.isSuccess) { toast.success("We really appreciated your help!"); setSubmittingRating(false); + saveToLocalStorage("rating", "true"); } }, [mutation.isSuccess]); diff --git a/components/core/rating-area/rating-component-server.tsx b/components/core/rating-area/rating-component-server.tsx new file mode 100644 index 0000000..dd2b369 --- /dev/null +++ b/components/core/rating-area/rating-component-server.tsx @@ -0,0 +1,57 @@ +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import OverallRating from "./components/overall-rating"; +import Grid from "./util/arrange-rating-sections"; +import RatingSubmitButton from "./components/submit-rating-component"; +import UxRating from "./components/ux-rating"; +import FunRating from "./components/fun-rating"; +import { FeedbackModal } from "@/components/landing/feedback/feedback-modal"; + +/* +Ratings are stored as per the following schema: + : + +For example: +- Overall_Rating : 500-100 + - This states that we currently have a total score of 500 over 100 ratings. Which means, on average, we get 5 stars from every rating. +- Ux_Rating : 100-30 + - Implies we have about 3.3333333... stars per rate +- Fun_Rating : 0-2345 + - We got 0 stars on average +*/ +const RatingBodyServer = () => { + return ( +
+ + + Rate Tailspin + + 30 seconds of your time could translates to a lot of + feedback for the team! + + + + + + + + + + +
+ +
+ +
+
+
+ ); +}; + +export default RatingBodyServer; diff --git a/components/core/rating-area/rating-component.tsx b/components/core/rating-area/rating-component.tsx index 9ef1a98..bab9776 100644 --- a/components/core/rating-area/rating-component.tsx +++ b/components/core/rating-area/rating-component.tsx @@ -1,57 +1,20 @@ -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import OverallRating from "./components/overall-rating"; -import Grid from "./util/arrange-rating-sections"; -import RatingSubmitButton from "./components/submit-rating-component"; -import UxRating from "./components/ux-rating"; -import FunRating from "./components/fun-rating"; -import { FeedbackModal } from "@/components/landing/feedback/feedback-modal"; +"use client"; -/* -Ratings are stored as per the following schema: - : +import { loadFromLocalStorage } from "@/lib/localStorage"; +import RatingBodyServer from "./rating-component-server"; -For example: -- Overall_Rating : 500-100 - - This states that we currently have a total score of 500 over 100 ratings. Which means, on average, we get 5 stars from every rating. -- Ux_Rating : 100-30 - - Implies we have about 3.3333333... stars per rate -- Fun_Rating : 0-2345 - - We got 0 stars on average -*/ const RatingBody = () => { - return ( -
- - - Rate Tailspin - - 30 seconds of your time could translates to a lot of - feedback for the team! - - - - - - - - - - -
- -
- -
-
-
- ); + const rated = loadFromLocalStorage("rating"); + + if (!rated) { + return ; + } else { + return ( +

+ You've already rated Tailspin. Thanks! +

+ ); + } }; export default RatingBody; diff --git a/components/landing/about-tailspin.tsx b/components/landing/about-tailspin.tsx index 699e732..b48a6de 100644 --- a/components/landing/about-tailspin.tsx +++ b/components/landing/about-tailspin.tsx @@ -2,6 +2,7 @@ import Link from "next/link"; import { Button } from "@/components/ui/button"; import { BarChartBig } from "lucide-react"; import FlipOnScroll from "../ui/flip-on-scroll"; +import { TailspinLogo } from "../ui/spinning-logo"; type AboutTailSpinBoxesProps = { title: string; @@ -37,7 +38,8 @@ const AboutTailspin = () => { className='relative flex h-full flex-col items-center justify-center rounded-lg bg-black p-6' id='about-section' > -

+ +

Tailspin

diff --git a/components/landing/challenge/challenge-intro.tsx b/components/landing/challenge/challenge-intro.tsx new file mode 100644 index 0000000..ef4e930 --- /dev/null +++ b/components/landing/challenge/challenge-intro.tsx @@ -0,0 +1,75 @@ +import { Badge } from "@/components/ui/badge"; +import Link from "next/link"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + CardFooter, +} from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; + +interface ChallengeIntroProps { + button: () => React.ReactNode; +} + +const ChallengeIntro: React.FC = ({ button }) => { + return ( + + + + 🥳 We're glad you're here! + + + + Alpha + + + MVP + + + + + +

+ It's simple. We'll provide you a coding + environment and a target image 🖼️. Your job is to recreate + that image using{" "} + HTML{" "} + and{" "} + + TailwindCSS + + . After you submit, we'll send your scores via Email + 📧.
+
Currently, we only provide a playground which + represents the core services of Tailspin. When you submit + code from our platform, we score your code and email the + result of similarity to you.{" "} + + In this MVP, we don't have a way for you to track + all your previous submissions or rank yourself against + other developers trying out Tailspin. + +

+
+ +

+ At this stage, we're presenting a bare bones look at + what's to come for Tailspin and looking for{" "} + + feedback + + ! +

+
+ +
+ {button()} +
+
+ ); +}; + +export default ChallengeIntro; diff --git a/components/landing/challenge/challenge-main.tsx b/components/landing/challenge/challenge-main.tsx new file mode 100644 index 0000000..c089107 --- /dev/null +++ b/components/landing/challenge/challenge-main.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { useState } from "react"; +import ChallengeIntro from "./challenge-intro"; +import StepperCard from "./stepper-pages/StepperCard"; +import { Button } from "@/components/ui/button"; +import { Code } from "lucide-react"; + +const ChallengeMain = () => { + const [goToStepper, setGoToStepper] = useState(false); + + const readyToCodeButton = () => { + return ( + + ); + }; + return ( + <> + {!goToStepper && } + {goToStepper && } + + ); +}; + +export default ChallengeMain; diff --git a/components/ui/enter-code-area-form/Challenge-FormField.tsx b/components/landing/challenge/forms/Challenge-FormField.tsx similarity index 100% rename from components/ui/enter-code-area-form/Challenge-FormField.tsx rename to components/landing/challenge/forms/Challenge-FormField.tsx diff --git a/components/ui/enter-code-area-form/Email-FormField.tsx b/components/landing/challenge/forms/Email-FormField.tsx similarity index 100% rename from components/ui/enter-code-area-form/Email-FormField.tsx rename to components/landing/challenge/forms/Email-FormField.tsx diff --git a/components/ui/useCode/Stepper-Form.tsx b/components/landing/challenge/forms/Stepper-Form.tsx similarity index 82% rename from components/ui/useCode/Stepper-Form.tsx rename to components/landing/challenge/forms/Stepper-Form.tsx index b7f289b..fbddabf 100644 --- a/components/ui/useCode/Stepper-Form.tsx +++ b/components/landing/challenge/forms/Stepper-Form.tsx @@ -2,9 +2,9 @@ import { useStepperStore } from "@/data-store/stepper-store"; -import { StepOne } from "./StepOne"; -import { StepTwo } from "./StepTwo"; import { useEffect } from "react"; +import { StepOne } from "../stepper-pages/StepOne"; +import { StepTwo } from "../stepper-pages/StepTwo"; export function StepperForm() { const { step, setChallenge, setCheck, setEmail, setProgress, setStep } = diff --git a/components/ui/enter-code-area-form/TOS-FormField.tsx b/components/landing/challenge/forms/TOS-FormField.tsx similarity index 100% rename from components/ui/enter-code-area-form/TOS-FormField.tsx rename to components/landing/challenge/forms/TOS-FormField.tsx diff --git a/components/ui/enter-code-area-form/StepOne.tsx b/components/landing/challenge/stepper-pages/StepOne.tsx similarity index 95% rename from components/ui/enter-code-area-form/StepOne.tsx rename to components/landing/challenge/stepper-pages/StepOne.tsx index 52c0b72..f44deea 100644 --- a/components/ui/enter-code-area-form/StepOne.tsx +++ b/components/landing/challenge/stepper-pages/StepOne.tsx @@ -7,9 +7,9 @@ import { useStepperStore, progressIncrements, } from "@/data-store/stepper-store"; -import { EmailFormField } from "./Email-FormField"; -import { TOSFormField } from "./TOS-FormField"; import { Loader2 } from "lucide-react"; +import { TOSFormField } from "../forms/TOS-FormField"; +import { EmailFormField } from "../forms/Email-FormField"; export function StepOne() { const { diff --git a/components/ui/useCode/StepTwo.tsx b/components/landing/challenge/stepper-pages/StepTwo.tsx similarity index 97% rename from components/ui/useCode/StepTwo.tsx rename to components/landing/challenge/stepper-pages/StepTwo.tsx index 67cb6e9..ec9a691 100644 --- a/components/ui/useCode/StepTwo.tsx +++ b/components/landing/challenge/stepper-pages/StepTwo.tsx @@ -10,10 +10,10 @@ import { import useSessionStore from "@/data-store/session-store"; import LandingPageCode from "@/components/landing/test-challenges/placeholder-code"; import { useRouter } from "next/navigation"; -import { ChallengeFormField } from "./Challenge-FormField"; import { Loader2 } from "lucide-react"; import { challengeEnum } from "@/data-store/challenge-store"; import { saveToLocalStorage } from "@/lib/localStorage"; +import { ChallengeFormField } from "../forms/Challenge-FormField"; const formStepTwoSchema = z.object({ challenge: z.nativeEnum(challengeEnum, { diff --git a/components/ui/useCode/StepperCard.tsx b/components/landing/challenge/stepper-pages/StepperCard.tsx similarity index 59% rename from components/ui/useCode/StepperCard.tsx rename to components/landing/challenge/stepper-pages/StepperCard.tsx index 21086ea..17d00eb 100644 --- a/components/ui/useCode/StepperCard.tsx +++ b/components/landing/challenge/stepper-pages/StepperCard.tsx @@ -7,20 +7,22 @@ import { } from "@/components/ui/card"; import { Progress } from "@/components/ui/progress"; -import { StepperForm } from "./Stepper-Form"; +import { StepperForm } from "../forms/Stepper-Form"; +import { Badge } from "@/components/ui/badge"; export function StepperCard() { return ( - Try One Of Our Coding Challenges!!! +
+

Try Out A Coding Challenge!

+ Alpha +
- {" "} - Here at TailSpin, we like to have fun!
By filling out - this form, you can use our code editor and attempt to do an - awesome Tailwind UI challenge! + It's simple. Give us an email, accept the TOS, and + select a challenge.
diff --git a/components/landing/faq.tsx b/components/landing/faq.tsx index 2a5996c..9b91697 100644 --- a/components/landing/faq.tsx +++ b/components/landing/faq.tsx @@ -15,7 +15,11 @@ const FAQ = () => { id='accordion' > FAQ - + Sounds like an interesting app... What's the catch? @@ -190,6 +194,61 @@ const FAQ = () => {

+ + + When are we implementing accounts? + + + In this Alpha stage, our plan is to gather feedback from + users before we roll out stateful user accounts. + We're doing this to test the waters and viability + of the concept of Tailspin because developing good + quality software takes time and effort. That said, if + you haven't given us feedback, you may do so{" "} + + here + + ! + + + + + Why do I have to provide my email in the playground? + + + To make it harder for bad people to spam us and to + provide you with a better experience. Tailspin's + tech stack includes a managed database, GPT3.5, and + serverless compute. We don't want our resources + being abused so we insist on an email so that it becomes + an extra layer of effort for bad actors to misuse + Tailspin. As mentioned, Tailspin uses GPT3.5 and + serverless compute. These technologies can take a long + time, depending on the total load on these services from + other applications. As such, in order to facilitate an + asynchronous processing experience, we are essentially + running these longer tasks in the background; so we have + to send you the results of processing in an asynchronous + fashion too - email! + + + + + What was the motivation behind Tailspin? + + + Wise man once said: "There's no better way to + learn than to do." At the time of writing, there + isn't a mainstream online TailwindCSS centered + platform, where we can write Tailwind powered UI + building code and challenge ourselves and one another in + the process. Tailspin hopes to close that gap in the + community. + +
); diff --git a/components/landing/footer.tsx b/components/landing/footer.tsx index d7599af..ea5c56d 100644 --- a/components/landing/footer.tsx +++ b/components/landing/footer.tsx @@ -1,5 +1,7 @@ import Link from "next/link"; import { TailspinLogo } from "../ui/spinning-logo"; +import { Button } from "../ui/button"; +import { FeedbackModal } from "./feedback/feedback-modal"; const Footer = () => { return ( @@ -17,27 +19,26 @@ const Footer = () => {
- No © 2023{" "} - - Tailspin + Built because we love it. Source code available in{" "} + + Github - . No Rights Reserved. diff --git a/components/landing/hero.tsx b/components/landing/hero.tsx index c2fb108..be98d94 100644 --- a/components/landing/hero.tsx +++ b/components/landing/hero.tsx @@ -3,6 +3,12 @@ import { HeroSubText } from "@/components/ui/typewriter-effect"; import HeroPageToast from "./hero-page-toast"; import { Button } from "../ui/button"; import Link from "next/link"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; export const Hero = () => { return ( @@ -20,12 +26,12 @@ export const Hero = () => {
- Forever free. Not affiliated with{" "} + Forever free 💰. Not affiliated with{" "} { The Tailwind Org
- -
- -
-
- -
-
- -
- + + + + +
+ +
+
+ +
+
+ +
+ +
+ +

Jump to coding section

+
+
+
+ ); diff --git a/components/landing/stats.tsx b/components/landing/stats.tsx index 0514e71..26703a0 100644 --- a/components/landing/stats.tsx +++ b/components/landing/stats.tsx @@ -4,11 +4,11 @@ import { SiteVisitCountBox } from "../ui/statistics/site-visit-count-box"; const SiteCounter = async () => { return ( -
+

Some Statistics

-
+
diff --git a/components/landing/thanks.tsx b/components/landing/thanks.tsx index aae36da..70fc5b7 100644 --- a/components/landing/thanks.tsx +++ b/components/landing/thanks.tsx @@ -139,8 +139,8 @@ const ThanksPage = async () => { Tech that made this possible! ({techs.size})
-
- + <> + {Array.from(techs.entries()).map(([url, name]) => (
@@ -156,7 +156,7 @@ const ThanksPage = async () => { ))} -
+
diff --git a/components/landing/timeline.tsx b/components/landing/timeline.tsx index 4bf47cf..cfaa18b 100644 --- a/components/landing/timeline.tsx +++ b/components/landing/timeline.tsx @@ -22,35 +22,22 @@ const Timeline = () => {

    -
  1. - -

    - V0 - - Current - -

    - -

    - Building out this landing page. Polishing the - interactive playground. Adding nice UI/UX. -

    -
  2. V1.0 + + Current +

    - Innovate and make incremental improvements to the - core business of Tailspin - the coding page. - Iteratively add, optimize, and beautify features to - the coding page. + Bare bones MVP. Users can try the coding challenges + and explore Tailspin. We'll work on the + feedback we receive from our users and improve our + MVP.

  3. diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx index be5e311..f42a988 100644 --- a/components/ui/accordion.tsx +++ b/components/ui/accordion.tsx @@ -28,7 +28,7 @@ const AccordionTrigger = React.forwardRef< svg]:rotate-180", + "text-md flex flex-1 items-center justify-between py-4 font-medium text-muted-foreground transition-all hover:underline [&[data-state=open]>svg]:rotate-180", className )} {...props} diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..476d402 --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
    + ); +} + +export { Badge, badgeVariants }; diff --git a/components/ui/component-carousel.tsx b/components/ui/component-carousel.tsx index 42d3e1e..354ee32 100644 --- a/components/ui/component-carousel.tsx +++ b/components/ui/component-carousel.tsx @@ -15,33 +15,54 @@ const ComponentCarousel: React.FC = ({ }) => { const [page, setPage] = useState(0); + const leftArrow = () => { + return ( + + ); + }; + + const rightArrow = () => { + return ( + + ); + }; + + const pageNumber = () => { + return ( +

    + {page + 1}/{nodes.length} +

    + ); + }; + return (

    {title}

    - +
    {leftArrow()}
    {nodes[page]} -

    - {page + 1}/{nodes.length} -

    +
    {pageNumber()}
    - +
    {rightArrow()}
    +
    +
    + {leftArrow()} + {rightArrow()}
    +
    {pageNumber()}
    ); }; diff --git a/components/ui/enter-code-area-form/StepTwo.tsx b/components/ui/enter-code-area-form/StepTwo.tsx deleted file mode 100644 index db053c4..0000000 --- a/components/ui/enter-code-area-form/StepTwo.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { zodResolver } from "@hookform/resolvers/zod"; -import * as z from "zod"; -import { useForm } from "react-hook-form"; -import { Button } from "@/components/ui/button"; -import { Form } from "@/components/ui/form"; -import { - progressIncrements, - useStepperStore, -} from "@/data-store/stepper-store"; -import useSessionStore from "@/data-store/session-store"; -import LandingPageCode from "@/components/landing/test-challenges/placeholder-code"; -import { useRouter } from "next/navigation"; -import { ChallengeFormField } from "./Challenge-FormField"; -import { Loader2 } from "lucide-react"; -import { challengeEnum } from "@/data-store/challenge-store"; - -const formStepTwoSchema = z.object({ - challenge: z.nativeEnum(challengeEnum, { - errorMap: (issue) => { - switch (issue.code) { - case "invalid_type": - return { message: "Please select a challenge!" }; - default: - return { - message: - "This is not a valid challenge, select a different challenge!", - }; - } - }, - }), -}); - -export function StepTwo() { - const { progress, setProgress, step, setStep, setChallenge } = - useStepperStore(); - const { setCode } = useSessionStore(); - - const router = useRouter(); - - const form = useForm>({ - resolver: zodResolver(formStepTwoSchema), - }); - - async function onSubmit(values: z.infer) { - if (step === 2) { - const selection: string = values.challenge; - setCode(LandingPageCode()); - setChallenge(selection); - setProgress(progress + progressIncrements); - - router.push("/code-area"); - } - } - - async function onBack() { - setProgress(progress - progressIncrements); - setStep(step - 1); - } - - return ( -
    -
    - - - - - {form.formState.isSubmitting ? ( - - ) : ( - - )} - - -
    - ); -} diff --git a/components/ui/enter-code-area-form/Stepper-Form.tsx b/components/ui/enter-code-area-form/Stepper-Form.tsx deleted file mode 100644 index b7f289b..0000000 --- a/components/ui/enter-code-area-form/Stepper-Form.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client"; - -import { useStepperStore } from "@/data-store/stepper-store"; - -import { StepOne } from "./StepOne"; -import { StepTwo } from "./StepTwo"; -import { useEffect } from "react"; - -export function StepperForm() { - const { step, setChallenge, setCheck, setEmail, setProgress, setStep } = - useStepperStore(); - - useEffect(() => { - setChallenge(""); - setCheck(false); - setEmail(""); - setProgress(0); - setStep(1); - }, []); - - if (step === 1) { - return ; - } else { - return ; - } -} diff --git a/components/ui/enter-code-area-form/StepperCard.tsx b/components/ui/enter-code-area-form/StepperCard.tsx deleted file mode 100644 index 21086ea..0000000 --- a/components/ui/enter-code-area-form/StepperCard.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; - -import { Progress } from "@/components/ui/progress"; -import { StepperForm } from "./Stepper-Form"; - -export function StepperCard() { - return ( - - - - Try One Of Our Coding Challenges!!! - - - {" "} - Here at TailSpin, we like to have fun!
    By filling out - this form, you can use our code editor and attempt to do an - awesome Tailwind UI challenge! -
    - -
    - - - -
    - ); -} - -export default StepperCard; diff --git a/components/ui/statistics/code-submit-count-box.tsx b/components/ui/statistics/code-submit-count-box.tsx index 889bcf8..3fb7d93 100644 --- a/components/ui/statistics/code-submit-count-box.tsx +++ b/components/ui/statistics/code-submit-count-box.tsx @@ -9,7 +9,7 @@ export const CodeSubmitCountBox = (props: any) => { return ( } - content={`How many people have submitted their code : ${value}`} + content={`Submissions to date: ${value}`} /> ); }; diff --git a/components/ui/statistics/site-rating.tsx b/components/ui/statistics/site-rating.tsx index 3de9aa4..aaaa5be 100644 --- a/components/ui/statistics/site-rating.tsx +++ b/components/ui/statistics/site-rating.tsx @@ -18,8 +18,21 @@ function SkeletonDemo() { ); } -export const SiteRatingStatBox = (props: any) => { +const RatingStars = ({ score }: { score: string }) => { + const rating = parseInt(score, 10); + const stars = Array(rating).fill(null); + return ( +
    + {stars.map((_, index) => ( + // Replace with your Star icon component + ))} +
    + ); +}; + +export const SiteRatingStatBox = () => { const value = useGetRating(); + return ( <> {value ? ( @@ -27,8 +40,21 @@ export const SiteRatingStatBox = (props: any) => { (val: { field_name: string; field_value: string }) => ( } - content={`${val.field_name}:${( + icon={ + + } + content={`${val.field_name + .split("_") + .join(" ")}: ${( Number(val.field_value.split("-")[0]) / Number(val.field_value.split("-")[1]) ).toFixed(2)} with ${ diff --git a/components/ui/tooltip.tsx b/components/ui/tooltip.tsx new file mode 100644 index 0000000..0990b90 --- /dev/null +++ b/components/ui/tooltip.tsx @@ -0,0 +1,30 @@ +"use client"; + +import * as React from "react"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; + +import { cn } from "@/lib/utils"; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/components/ui/useCode/Challenge-FormField.tsx b/components/ui/useCode/Challenge-FormField.tsx deleted file mode 100644 index 1a89537..0000000 --- a/components/ui/useCode/Challenge-FormField.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { challengeMap } from "@/data-store/challenge-store"; - -export function ChallengeFormField(form: any) { - return ( - ( - - Challenge/Level - -
    - -
    -
    - -
    - )} - /> - ); -} diff --git a/components/ui/useCode/Email-FormField.tsx b/components/ui/useCode/Email-FormField.tsx deleted file mode 100644 index 107c32e..0000000 --- a/components/ui/useCode/Email-FormField.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { useStepperStore } from "@/data-store/stepper-store"; - -export function EmailFormField(form: any) { - const { email } = useStepperStore(); - - return ( - ( - - Email - - - - - - )} - /> - ); -} diff --git a/components/ui/useCode/StepOne.tsx b/components/ui/useCode/StepOne.tsx deleted file mode 100644 index 52c0b72..0000000 --- a/components/ui/useCode/StepOne.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { zodResolver } from "@hookform/resolvers/zod"; -import * as z from "zod"; -import { useForm } from "react-hook-form"; -import { Button } from "@/components/ui/button"; -import { Form } from "@/components/ui/form"; -import { - useStepperStore, - progressIncrements, -} from "@/data-store/stepper-store"; -import { EmailFormField } from "./Email-FormField"; -import { TOSFormField } from "./TOS-FormField"; -import { Loader2 } from "lucide-react"; - -export function StepOne() { - const { - progress, - setProgress, - step, - setStep, - email, - setEmail, - check, - setCheck, - } = useStepperStore(); - - const formStepOneSchema = z.object({ - email: z - .string() - .email({ - message: "Invalid email address.", - }) - .default(email), - - accept: z - .literal(true, { - errorMap: () => ({ - message: "You must accept the terms & conditions", - }), - }) - .default(check), - }); - - const form = useForm>({ - resolver: zodResolver(formStepOneSchema), - }); - - async function onContinue(values: z.infer) { - setCheck(values.accept); - - if (values.accept === true) { - const userEmail: string = values.email; - setEmail(userEmail); - setCheck(values.accept); - - if (step === 1) { - setProgress(progress + progressIncrements); - setStep(step + 1); - } - } - } - return ( -
    - - - - - {form.formState.isSubmitting ? ( - - ) : ( - - )} - - - ); -} diff --git a/components/ui/useCode/TOS-FormField.tsx b/components/ui/useCode/TOS-FormField.tsx deleted file mode 100644 index 7ddc36d..0000000 --- a/components/ui/useCode/TOS-FormField.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { - FormControl, - FormField, - FormItem, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { - progressIncrements, - useStepperStore, -} from "@/data-store/stepper-store"; -import Image from "next/image"; -import Link from "next/link"; - -export function TOSFormField(form: any) { - const { check, setCheck, progress, setProgress } = useStepperStore(); - - return ( - ( - - -
    - { - setCheck(!check); - setProgress( - check - ? progress - progressIncrements - : progress + progressIncrements - ); - }} - className='peer mr-[10px] mt-[5px] h-4 w-[20px] cursor-pointer' - /> - -
    -
    - -
    - )} - /> - ); -} diff --git a/lib/localStorage.ts b/lib/localStorage.ts index bf74363..043054c 100644 --- a/lib/localStorage.ts +++ b/lib/localStorage.ts @@ -1,25 +1,30 @@ -export const saveToLocalStorage = (key: string, value: string) => { +export const saveToLocalStorage = (key: string, value: string): void => { try { - localStorage.setItem(key, value); + if (typeof window !== "undefined") { + localStorage.setItem(key, value); + } } catch (error) { console.error("Error saving to localStorage", error); } }; -export const loadFromLocalStorage = (key: string) => { +export const loadFromLocalStorage = (key: string): string => { try { - const value = localStorage.getItem(key); - if (value === null) { - return ""; + if (typeof window !== "undefined") { + const value = localStorage.getItem(key); + if (value === null || value === undefined) { + return ""; + } + return value; } - return value; + return ""; } catch (error) { console.error("Error reading from localStorage", error); return ""; } }; -export const removeItemFromLocalStorage = (key: string) => { +export const removeItemFromLocalStorage = (key: string): void => { if (typeof window !== "undefined") { localStorage.removeItem(key); } diff --git a/package.json b/package.json index 44d3919..0180e25 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-tooltip": "^1.0.7", "@react-email/components": "^0.0.7", "@react-email/render": "^0.0.7", "@tanstack/react-query": "^4.35.3", diff --git a/yarn.lock b/yarn.lock index 3ebafbd..f8cff22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -674,6 +674,25 @@ "@radix-ui/react-roving-focus" "1.0.4" "@radix-ui/react-use-controllable-state" "1.0.1" +"@radix-ui/react-tooltip@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz#8f55070f852e7e7450cc1d9210b793d2e5a7686e" + integrity sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-popper" "1.1.3" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-visually-hidden" "1.0.3" + "@radix-ui/react-use-callback-ref@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a"