├── .env.example
├── .gitignore
├── README.md
├── app
├── (root)
│ ├── (home)
│ │ ├── loading.tsx
│ │ └── page.tsx
│ ├── layout.tsx
│ ├── privacy
│ │ └── page.tsx
│ └── terms
│ │ └── page.tsx
├── favicon.ico
├── globals.css
├── layout.tsx
└── studio
│ └── [[...index]]
│ └── page.tsx
├── components.json
├── components
├── Filters.tsx
├── Footer.tsx
├── Header.tsx
├── Navbar.tsx
├── ResourceCard.tsx
├── SearchForm.tsx
└── ui
│ ├── button.tsx
│ ├── card.tsx
│ ├── dropdown-menu.tsx
│ ├── input.tsx
│ └── skeleton.tsx
├── lib
└── utils.ts
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── arrow-blue.svg
├── arrow_trail.svg
├── arrow_white.svg
├── downloads.svg
├── hamburger-menu.svg
├── jsm-logo.svg
├── jsm_resources_banner.svg
├── jsm_resources_banner.webp
├── logo.png
├── magnifying-glass.svg
├── next.svg
└── vercel.svg
├── sanity.cli.ts
├── sanity.config.ts
├── sanity
├── actions.ts
├── env.ts
├── lib
│ ├── client.ts
│ └── image.ts
├── schemas
│ ├── index.ts
│ ├── resource-playlist.schema.ts
│ └── resource.schema.ts
└── utils.ts
├── tailwind.config.ts
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_SANITY_PROJECT_ID=""
2 | NEXT_PUBLIC_SANITY_DATASET=""
3 | NEXT_PUBLIC_SANITY_TOKEN=""
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 | .env
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | veena mam pdf
2 |
--------------------------------------------------------------------------------
/app/(root)/(home)/loading.tsx:
--------------------------------------------------------------------------------
1 | import { Skeleton } from "@/components/ui/skeleton";
2 |
3 | const Loading = () => {
4 | return (
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default Loading;
23 |
--------------------------------------------------------------------------------
/app/(root)/(home)/page.tsx:
--------------------------------------------------------------------------------
1 | import Filters from "@/components/Filters";
2 | import Header from "@/components/Header";
3 | import ResourceCard from "@/components/ResourceCard";
4 | import SearchForm from "@/components/SearchForm";
5 | import { getResources, getResourcesPlaylist } from "@/sanity/actions";
6 |
7 | export const revalidate = 900;
8 | interface Props {
9 | searchParams: { [key: string]: string | undefined };
10 | }
11 |
12 | const Page = async ({ searchParams }: Props) => {
13 | const resources = await getResources({
14 | query: searchParams?.query || "",
15 | category: searchParams?.category || "",
16 | page: "1",
17 | });
18 |
19 | const resourcesPlaylist = await getResourcesPlaylist();
20 | return (
21 |
22 |
23 |
24 |
25 | V Notes
26 |
27 |
28 | A One Stop Solution for all the handwritten notes and other resources
29 |
30 |
31 |
32 |
33 |
34 |
35 | {(searchParams?.query || searchParams?.category) && (
36 |
37 |
41 |
42 | {resources?.length > 0 ? (
43 | resources.map((resource: any) => (
44 |
51 | ))
52 | ) : (
53 |
No resources found
54 | )}
55 |
56 |
57 | )}
58 |
59 | {resourcesPlaylist.map((item: any) => (
60 |
64 | {item.title}
65 |
66 | {item.resources.map((resource: any) => (
67 |
74 | ))}
75 |
76 |
77 | ))}
78 |
79 | );
80 | };
81 |
82 | export default Page;
83 |
--------------------------------------------------------------------------------
/app/(root)/layout.tsx:
--------------------------------------------------------------------------------
1 | import Footer from '@/components/Footer'
2 | import Navbar from '@/components/Navbar'
3 | import React from 'react'
4 |
5 | const layout = ({ children}: {children: React.ReactNode}) => {
6 | return (
7 | <>
8 |
9 | {children}
10 |
11 | >
12 | )
13 | }
14 |
15 | export default layout
--------------------------------------------------------------------------------
/app/(root)/privacy/page.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 |
3 | const page = () => {
4 | return (
5 |
6 |
7 |
8 |
9 | Privacy policy
10 |
11 |
12 | Copyright @2024 Material displayed on this website cannot be
13 | endorsed for any kind of monetary consideration in any such manner.
14 | The material has displayed accurately for educational purpose and
15 | not to be used in a derogatory manner or in a misleading context.
16 | The source of material is prominently acknowledged on the material
17 | itself, however if any material which is identified as being
18 | copyrighted material of a third party, please write us{" "}
19 |
24 | vishalbusiness3108@gmail.com
25 | {" "}
26 | for copyright claim.
27 |
28 |
29 | Changes To This Privacy Policy
30 |
31 |
32 | We may update our Privacy Policy from time to time. We will notify
33 | but we suggest that you review it from time to time, as our Platform
34 | may change. As a result, at times it may be necessary for V Notes to
35 | make changes to this Privacy Policy. V Notes reserves the right to
36 | update or modify this Privacy Policy at any time and from time to
37 | time without prior notice. Your continued use of the Platform after
38 | any changes or revisions to this Privacy Policy shall indicate your
39 | agreement with the terms of such revised Privacy Policy.
40 |
41 |
Contact Us
42 |
43 | If you have any questions or suggestions regarding this Privacy
44 | Policy, please contact us at{" "}
45 |
50 | vishalbusiness3108@gmail.com
51 |
52 | .
53 |
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export default page;
61 |
--------------------------------------------------------------------------------
/app/(root)/terms/page.tsx:
--------------------------------------------------------------------------------
1 | const page = () => {
2 | return (
3 |
4 |
5 |
6 |
7 | Terms & Condition
8 |
9 |
10 | By accessing or using any of the V Notes Website online facilities
11 | including, without limitation, www.vnotes.me, social media sites and
12 | any other digital services or facilities operated or used by V Notes
13 | from time to time (collectively referred to as the “Sites”), you
14 | agree to comply with and be bound by these Terms of Use. Please read
15 | these Terms of Use carefully as well as the Privacy Policy. If you
16 | do not agree to the Terms of Use or Privacy Policy, you must
17 | immediately terminate use of the Sites.{" "}
18 |
19 |
20 | Terms of Use
21 |
22 |
23 | Subject to the Terms of Use, V Notes allows you to only access and
24 | make personal non-commercial use of the sites. This website is
25 | created solely for educational purpose and does not intend to ask
26 | any kind of money against its resources which is freely provided. V
27 | Notes reserves the right to revise these Terms of Use at any time in
28 | its sole discretion by posting revised Terms of Use to the Sites.
29 | Your use of the Sites signifies your acceptance of all the terms and
30 | conditions contained within the Terms of Use posted at the time of
31 | your use..
32 |
33 |
34 | Fees and Charges
35 |
36 |
37 | V Notes currently does not charge any kind of monetary or
38 | non-monetary consideration from its user but it reserves the right,
39 | at any time, to charge fees for accessing whole or part of the
40 | Sites. If, at any time, you are required to pay fee for accessing
41 | any part of the Sites, you will be intimated about the same and you
42 | will be allowed to access such part of the Site only upon payment of
43 | a fee (“Subscription Fee”) for a particular period specified at the
44 | time of subscription (“Subscription Period”).
45 |
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default page;
53 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishalparmarr/V-Notes/98dc3464a8a717619a8d8d1863c30be3a8624546/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400&display=swap");
2 |
3 | @tailwind base;
4 | @tailwind components;
5 | @tailwind utilities;
6 |
7 | * {
8 | font-family: "Poppins", sans-serif;
9 | }
10 |
11 | @layer utilities {
12 | .paddle-checkout {
13 | @apply min-h-screen w-full py-10 md:py-20 lg:w-1/2;
14 | }
15 |
16 | .hero-height {
17 | @apply min-h-[calc(100vh-100px)];
18 | }
19 |
20 | .text-gradient {
21 | background: linear-gradient(90deg, #4ca5ff 2.34%, #b673f8 100.78%);
22 | -webkit-background-clip: text;
23 | -webkit-text-fill-color: transparent;
24 | background-clip: text;
25 | text-fill-color: transparent;
26 | }
27 |
28 | .heading1 {
29 | @apply text-[64px] leading-[67.2px] font-bold;
30 | }
31 |
32 | .heading2 {
33 | @apply font-bold text-[48px] leading-[50.4px];
34 | }
35 |
36 | .heading3 {
37 | @apply font-bold sm:text-[32px] sm:leading-[33.6px] text-[28px] leading-[40px] tracking-[-0.25%];
38 | }
39 |
40 | .base-regular {
41 | @apply text-[24px] font-normal leading-[31.2px];
42 | }
43 |
44 | .base-bold {
45 | @apply text-[24px] font-semibold leading-[31.2px];
46 | }
47 |
48 | .paragraph-regular {
49 | @apply text-[20px] font-normal leading-[26px];
50 | }
51 |
52 | .paragraph-semibold {
53 | @apply text-[20px] font-semibold leading-[26px];
54 | }
55 |
56 | .body-regular {
57 | @apply text-[16px] font-normal leading-[20.8px];
58 | }
59 |
60 | .body-semibold {
61 | @apply text-[16px] font-semibold leading-[20.8px];
62 | }
63 |
64 | .body-medium {
65 | @apply text-[16px] font-medium leading-[22.4px];
66 | }
67 |
68 | .small-regular {
69 | @apply text-[14px] font-normal leading-[17.5px];
70 | }
71 |
72 | .small-bold {
73 | @apply text-[14px] font-semibold leading-[17.5px];
74 | }
75 |
76 | .heading4 {
77 | @apply font-semibold text-[20px] leading-[26px] tracking-[0.25%];
78 | }
79 |
80 | .body-text {
81 | @apply text-[16px] leading-[22px] font-normal;
82 | }
83 |
84 | .nav-padding {
85 | @apply pt-[98px];
86 | }
87 |
88 | .paddings {
89 | @apply sm:p-16 xs:p-8 px-6 py-12;
90 | }
91 |
92 | .y-paddings {
93 | @apply sm:py-16 py-12;
94 | }
95 |
96 | .x-paddings {
97 | @apply sm:px-16 px-6;
98 | }
99 |
100 | .career-paddings {
101 | @apply sm:p-28 xs:p-8 px-6 py-12;
102 | }
103 |
104 | .flex-between {
105 | @apply flex justify-between items-center;
106 | }
107 |
108 | .flex-center {
109 | @apply flex justify-center items-center;
110 | }
111 |
112 | .flex-start {
113 | @apply flex justify-start items-start;
114 | }
115 |
116 | .flex-end {
117 | @apply flex justify-end;
118 | }
119 |
120 | .inner-width {
121 | @apply 3xl:max-w-[1280px] w-full mx-auto;
122 | }
123 |
124 | .inter-width {
125 | @apply lg:w-[80%] w-[100%];
126 | }
127 |
128 | .no-focus {
129 | @apply focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 !important;
130 | }
131 | }
132 |
133 | .frame {
134 | border-style: inset;
135 | border: 1px solid #40a0ff;
136 | position: relative;
137 | }
138 |
139 | .sm-box {
140 | width: 25px;
141 | height: 25px;
142 | position: absolute;
143 | border: 1px solid #40a0ff;
144 | }
145 |
146 | .sm-box-1 {
147 | top: -15px;
148 | left: -15px;
149 | }
150 |
151 | .sm-box-2 {
152 | top: -15px;
153 | right: -15px;
154 | }
155 |
156 | .sm-box-3 {
157 | bottom: -15px;
158 | right: -15px;
159 | }
160 |
161 | .sm-box-4 {
162 | bottom: -15px;
163 | left: -15px;
164 | }
165 |
166 | .sm-box-5 {
167 | bottom: 50%;
168 | left: -15px;
169 | }
170 |
171 | .sm-box-6 {
172 | bottom: 50%;
173 | right: -15px;
174 | }
175 |
176 | .sm-box-7 {
177 | bottom: -15px;
178 | left: 50%;
179 | }
180 |
181 | .sm-box-8 {
182 | top: -15px;
183 | left: 50%;
184 | }
185 |
186 | @media screen and (max-width: 500px) {
187 | .sm-box-5,
188 | .sm-box-6,
189 | .sm-box-7,
190 | .sm-box-8 {
191 | display: none;
192 | }
193 |
194 | .frame {
195 | border-radius: 10px;
196 | }
197 | }
198 |
199 | .text-gradient_purple-blue {
200 | background: linear-gradient(90deg, #4c73ff 0%, #73e0f8 100%);
201 | -webkit-background-clip: text;
202 | -webkit-text-fill-color: transparent;
203 | background-clip: text;
204 | text-fill-color: transparent;
205 | }
206 |
207 | .gradient_blue-purple {
208 | background: linear-gradient(90deg, #4ca5ff 0%, #b573f8 100%);
209 | }
210 |
211 | .text-gradient_blue {
212 | background: linear-gradient(90deg, #4c73ff 2.34%, #389bff 100.78%);
213 | -webkit-background-clip: text;
214 | -webkit-text-fill-color: transparent;
215 | background-clip: text;
216 | text-fill-color: transparent;
217 | }
218 |
219 | .gradient_purple {
220 | background: linear-gradient(90deg, #854cff 0%, #b573f8 100%);
221 | }
222 |
223 | .text-gradient_blue-purple {
224 | background: linear-gradient(90deg, #4ca5ff 0%, #b573f8 100%);
225 | -webkit-background-clip: text;
226 | -webkit-text-fill-color: transparent;
227 | background-clip: text;
228 | text-fill-color: transparent;
229 | }
230 |
231 | /* Hide scrollbar for Chrome, Safari and Opera */
232 | .no-scrollbar::-webkit-scrollbar {
233 | display: none;
234 | }
235 |
236 | /* Hide scrollbar for IE, Edge and Firefox */
237 | .no-scrollbar {
238 | -ms-overflow-style: none; /* IE and Edge */
239 | scrollbar-width: none; /* Firefox */
240 | }
241 |
242 | @layer components {
243 | .drop1 {
244 | @apply w-full px-6 py-4 xs:px-8 flex-start font-bold flex-col;
245 | }
246 | .drop2 {
247 | @apply flex items-center gap-2 text-base;
248 | }
249 | .drop3 {
250 | @apply text-white-800 text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300 px-4 py-2;
251 | }
252 | }
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import "./globals.css";
3 | import { SpeedInsights } from "@vercel/speed-insights/next";
4 | import { Analytics } from "@vercel/analytics/react"
5 |
6 | export const metadata: Metadata = {
7 | title: "V Notes",
8 | description: " A One Stop Solution for all the handwritten notes and other resources",
9 | other: {
10 | keywords: "handwritten notes, resources, notes, handwritten, handwritten notes, resources, notes, handwritten",
11 | 'theme-color': '#0d1117',
12 | 'color-scheme': 'dark only',
13 | 'twitter:image': 'https://ibb.co/kGKscQH',
14 | 'twitter:card': 'summary_large_image',
15 | 'twitter:site': '@vercel',
16 | "og:image": "https://ibb.co/kGKscQH",
17 | "og:url": "www.vnotes.me",
18 | "og:type": "website",
19 | }
20 | };
21 |
22 | export default function RootLayout({
23 | children,
24 | }: {
25 | children: React.ReactNode;
26 | }) {
27 | return (
28 |
29 |
30 | {children}
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/app/studio/[[...index]]/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | /**
4 | * This route is responsible for the built-in authoring environment using Sanity Studio.
5 | * All routes under your studio path is handled by this file using Next.js' catch-all routes:
6 | * https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes
7 | *
8 | * You can learn more about the next-sanity package here:
9 | * https://github.com/sanity-io/next-sanity
10 | */
11 |
12 | import { NextStudio } from 'next-sanity/studio'
13 | import config from '../../../sanity.config'
14 |
15 | export default function StudioPage() {
16 | return
17 | }
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/components/Filters.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 | import { useSearchParams, useRouter } from "next/navigation";
5 | import { formUrlQuery } from "@/sanity/utils";
6 |
7 | const links = ["All", "Frontend", "Backend", "Programming", "Engineering", "Other"];
8 |
9 | const Filters = () => {
10 | const [active, setActive] = useState("");
11 | const searchParams = useSearchParams();
12 | const router = useRouter();
13 |
14 | const handleFilter = (link: string) => {
15 | let newUrl = "";
16 |
17 | if (active === link) {
18 | setActive("");
19 |
20 | newUrl = formUrlQuery({
21 | params: searchParams.toString(),
22 | keysToRemove: ["category"],
23 | });
24 | } else {
25 | setActive(link);
26 |
27 | newUrl = formUrlQuery({
28 | params: searchParams.toString(),
29 | key: "category",
30 | value: link.toLowerCase(),
31 | });
32 | }
33 |
34 | router.push(newUrl, { scroll: false });
35 | };
36 | return (
37 |
38 | {links.map((link) => (
39 | handleFilter(link)}
42 | className={`${
43 | active === link ? "gradient_blue-purple" : ""
44 | } whitespace-nowrap rounded-lg px-4 py-2.5`}
45 | >
46 | {link}
47 |
48 | ))}
49 |
50 | );
51 | };
52 |
53 | export default Filters;
54 |
--------------------------------------------------------------------------------
/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import React from "react";
3 |
4 | const Footer = () => {
5 | return (
6 |
14 | );
15 | };
16 |
17 | export default Footer;
18 |
--------------------------------------------------------------------------------
/components/Header.tsx:
--------------------------------------------------------------------------------
1 | interface Props {
2 | query: string;
3 | category: string;
4 | }
5 |
6 | const Header = ({ query, category }: Props) => {
7 | if (query && category) {
8 | return (
9 |
10 | Search results for "{query}" in{" "}
11 | {category}
12 |
13 | );
14 | }
15 |
16 | if (query) {
17 | return (
18 |
19 | Search results for "{query}"
20 |
21 | );
22 | }
23 |
24 | if (category) {
25 | return (
26 |
27 | {category}
28 |
29 | );
30 | }
31 |
32 | return No Results ;
33 | };
34 |
35 | export default Header;
36 |
--------------------------------------------------------------------------------
/components/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Link from "next/link";
3 | import React from "react";
4 | import { FaLinkedin } from "react-icons/fa";
5 | import { FiGithub } from "react-icons/fi";
6 | import { CiCoffeeCup } from "react-icons/ci";
7 | import {
8 | DropdownMenu,
9 | DropdownMenuContent,
10 | DropdownMenuItem,
11 | DropdownMenuTrigger,
12 | } from "@/components/ui/dropdown-menu";
13 |
14 | const Navbar = () => {
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
31 |
32 |
33 |
34 |
39 |
46 | My Portfolio
47 |
48 |
49 |
50 |
55 |
56 | GitHub
57 |
58 |
59 |
60 |
65 |
66 | LinkedIn
67 |
68 |
69 |
70 |
75 |
76 | Buy Me A Coffee
77 |
78 |
79 |
80 |
81 |
82 |
83 |
98 |
99 | My Portfolio
100 |
101 |
102 |
103 |
104 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
126 |
127 | {" "}
132 | Buy Me A Coffee
133 |
134 |
135 |
136 |
137 |
138 |
139 | );
140 | };
141 |
142 | export default Navbar;
143 |
--------------------------------------------------------------------------------
/components/ResourceCard.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
4 | import Image from "next/image";
5 | import Link from "next/link";
6 | import { useEffect, useState } from "react";
7 | interface Props {
8 | title: string;
9 | image: string;
10 | downloadLink: string;
11 | downloadNumber: number;
12 | }
13 |
14 | const ResourceCard = ({ title, image, downloadLink }: Props) => {
15 | const [downloads, setDownloads] = useState(0);
16 |
17 | useEffect(() => {
18 | const localStorageKey = `downloads-${title}`;
19 | const savedDownloads = localStorage.getItem(localStorageKey);
20 | if (savedDownloads !== null) {
21 | setDownloads(Number(savedDownloads));
22 | }
23 | }, [title]);
24 |
25 | const handleClick = () => {
26 | const localStorageKey = `downloads-${title}`;
27 | const newDownloads = downloads + 1;
28 | setDownloads(newDownloads);
29 | localStorage.setItem(localStorageKey, String(newDownloads));
30 | };
31 |
32 | return (
33 |
34 |
35 |
36 |
37 |
44 |
45 |
46 | {title}
47 |
48 |
49 |
50 |
51 |
52 |
53 | {downloads}
54 |
55 |
60 | Download Now
61 |
62 |
63 |
64 |
65 | );
66 | };
67 |
68 | export default ResourceCard;
69 |
--------------------------------------------------------------------------------
/components/SearchForm.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Image from "next/image";
4 | import { usePathname, useSearchParams, useRouter } from "next/navigation";
5 | import { Input } from "@/components/ui/input";
6 | import { useState, useEffect } from "react";
7 | import { formUrlQuery } from "@/sanity/utils";
8 |
9 | const SearchForm = () => {
10 | const searchParams = useSearchParams();
11 | const router = useRouter();
12 | const pathname = usePathname();
13 | const [search, setSearch] = useState('');
14 |
15 | useEffect(() => {
16 | const delayDebounceFn = setTimeout(() => {
17 | let newUrl = '';
18 |
19 | if(search) {
20 | newUrl = formUrlQuery({
21 | params: searchParams.toString(),
22 | key: 'query',
23 | value: search
24 | })
25 | } else {
26 | newUrl = formUrlQuery({
27 | params: searchParams.toString(),
28 | keysToRemove: ['query'],
29 | })
30 | }
31 | router.push(newUrl, { scroll: false });
32 | }, 300)
33 |
34 | return () => clearTimeout(delayDebounceFn)
35 | }, [search])
36 |
37 | return (
38 |
56 | );
57 | };
58 |
59 | export default SearchForm;
60 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const DropdownMenu = DropdownMenuPrimitive.Root
10 |
11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12 |
13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
14 |
15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16 |
17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
18 |
19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20 |
21 | const DropdownMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | DropdownMenuSubTrigger.displayName =
41 | DropdownMenuPrimitive.SubTrigger.displayName
42 |
43 | const DropdownMenuSubContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, ...props }, ref) => (
47 |
55 | ))
56 | DropdownMenuSubContent.displayName =
57 | DropdownMenuPrimitive.SubContent.displayName
58 |
59 | const DropdownMenuContent = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, sideOffset = 4, ...props }, ref) => (
63 |
64 |
73 |
74 | ))
75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76 |
77 | const DropdownMenuItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef & {
80 | inset?: boolean
81 | }
82 | >(({ className, inset, ...props }, ref) => (
83 |
92 | ))
93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94 |
95 | const DropdownMenuCheckboxItem = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, children, checked, ...props }, ref) => (
99 |
108 |
109 |
110 |
111 |
112 |
113 | {children}
114 |
115 | ))
116 | DropdownMenuCheckboxItem.displayName =
117 | DropdownMenuPrimitive.CheckboxItem.displayName
118 |
119 | const DropdownMenuRadioItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140 |
141 | const DropdownMenuLabel = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef & {
144 | inset?: boolean
145 | }
146 | >(({ className, inset, ...props }, ref) => (
147 |
156 | ))
157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158 |
159 | const DropdownMenuSeparator = React.forwardRef<
160 | React.ElementRef,
161 | React.ComponentPropsWithoutRef
162 | >(({ className, ...props }, ref) => (
163 |
168 | ))
169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170 |
171 | const DropdownMenuShortcut = ({
172 | className,
173 | ...props
174 | }: React.HTMLAttributes) => {
175 | return (
176 |
180 | )
181 | }
182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183 |
184 | export {
185 | DropdownMenu,
186 | DropdownMenuTrigger,
187 | DropdownMenuContent,
188 | DropdownMenuItem,
189 | DropdownMenuCheckboxItem,
190 | DropdownMenuRadioItem,
191 | DropdownMenuLabel,
192 | DropdownMenuSeparator,
193 | DropdownMenuShortcut,
194 | DropdownMenuGroup,
195 | DropdownMenuPortal,
196 | DropdownMenuSub,
197 | DropdownMenuSubContent,
198 | DropdownMenuSubTrigger,
199 | DropdownMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images:{
4 | domains: ['cdn.sanity.io']
5 | }
6 | };
7 |
8 | export default nextConfig;
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vnotes",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@radix-ui/react-dropdown-menu": "^2.0.6",
13 | "@radix-ui/react-slot": "^1.0.2",
14 | "@sanity/image-url": "^1.0.2",
15 | "@sanity/vision": "^3.36.2",
16 | "@vercel/analytics": "^1.2.2",
17 | "@vercel/speed-insights": "^1.0.10",
18 | "class-variance-authority": "^0.7.0",
19 | "clsx": "^2.1.0",
20 | "lucide-react": "^0.314.0",
21 | "next": "14.1.0",
22 | "next-sanity": "^7.1.4",
23 | "query-string": "^8.1.0",
24 | "react": "^18",
25 | "react-dom": "^18",
26 | "react-icons": "^5.0.1",
27 | "sanity": "^3.36.2",
28 | "styled-components": "^6.1.8",
29 | "tailwind-merge": "^2.2.1",
30 | "tailwindcss-animate": "^1.0.7"
31 | },
32 | "devDependencies": {
33 | "@types/node": "^20",
34 | "@types/react": "^18",
35 | "@types/react-dom": "^18",
36 | "autoprefixer": "^10.0.1",
37 | "postcss": "^8",
38 | "tailwindcss": "^3.3.0",
39 | "typescript": "^5"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/arrow-blue.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/public/arrow_trail.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/arrow_white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/downloads.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/public/hamburger-menu.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/public/jsm_resources_banner.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/public/jsm_resources_banner.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishalparmarr/V-Notes/98dc3464a8a717619a8d8d1863c30be3a8624546/public/jsm_resources_banner.webp
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishalparmarr/V-Notes/98dc3464a8a717619a8d8d1863c30be3a8624546/public/logo.png
--------------------------------------------------------------------------------
/public/magnifying-glass.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sanity.cli.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This configuration file lets you run `$ sanity [command]` in this folder
3 | * Go to https://www.sanity.io/docs/cli to learn more.
4 | **/
5 | import { defineCliConfig } from 'sanity/cli'
6 |
7 | const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID
8 | const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET
9 |
10 | export default defineCliConfig({ api: { projectId, dataset } })
11 |
--------------------------------------------------------------------------------
/sanity.config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This configuration is used to for the Sanity Studio that’s mounted on the `\app\y\[[...index]]\page.tsx` route
3 | */
4 |
5 | import {visionTool} from '@sanity/vision'
6 | import {defineConfig} from 'sanity'
7 | import {structureTool} from 'sanity/structure'
8 |
9 | // Go to https://www.sanity.io/docs/api-versioning to learn how API versioning works
10 | import {apiVersion, dataset, projectId} from './sanity/env'
11 | import schemas from './sanity/schemas'
12 |
13 | export default defineConfig({
14 | basePath: '/studio',
15 | projectId,
16 | dataset,
17 | // Add and edit the content schema in the './sanity/schema' folder
18 | schema: {types: schemas},
19 | plugins: [
20 | structureTool(),
21 | // Vision is a tool that lets you query your content with GROQ in the studio
22 | // https://www.sanity.io/docs/the-vision-plugin
23 | visionTool({defaultApiVersion: apiVersion}),
24 | ],
25 | })
26 |
--------------------------------------------------------------------------------
/sanity/actions.ts:
--------------------------------------------------------------------------------
1 | import { groq } from 'next-sanity';
2 | import { readClient } from './lib/client';
3 | import { buildQuery } from './utils';
4 |
5 | interface GetResourcesParams {
6 | query: string;
7 | category: string;
8 | page: string;
9 | }
10 |
11 | export const getResourcesPlaylist = async () => {
12 | try {
13 | const resources = await readClient.fetch(
14 | groq`*[_type == "resourcePlaylist"]{
15 | _id,
16 | title,
17 | resources[0...6]->{
18 | title,
19 | _id,
20 | downloadLink,
21 | "image": poster.asset->url,
22 | views,
23 | category
24 | }
25 | }`
26 | );
27 |
28 | return resources;
29 | } catch (error) {
30 | console.log(error);
31 | }
32 | }
33 |
34 | export const getResources = async (params: GetResourcesParams) => {
35 | const { query, category, page } = params;
36 |
37 | try {
38 | const resources = await readClient.fetch(
39 | groq`${buildQuery({
40 | type: 'resource',
41 | query,
42 | category,
43 | page: parseInt(page),
44 | })}{
45 | title,
46 | _id,
47 | downloadLink,
48 | "image": poster.asset->url,
49 | views,
50 | slug,
51 | category
52 | }`
53 | );
54 |
55 | return resources;
56 | } catch (error) {
57 | console.log(error);
58 | }
59 | }
--------------------------------------------------------------------------------
/sanity/env.ts:
--------------------------------------------------------------------------------
1 | export const apiVersion =
2 | process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2024-03-31'
3 |
4 | export const dataset = assertValue(
5 | process.env.NEXT_PUBLIC_SANITY_DATASET,
6 | 'Missing environment variable: NEXT_PUBLIC_SANITY_DATASET'
7 | )
8 |
9 | export const projectId = assertValue(
10 | process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
11 | 'Missing environment variable: NEXT_PUBLIC_SANITY_PROJECT_ID'
12 | )
13 |
14 | export const token = assertValue(
15 | process.env.NEXT_PUBLIC_SANITY_TOKEN,
16 | 'Missing environment variable: NEXT_PUBLIC_SANITY_TOKEN'
17 | )
18 |
19 | export const useCdn = false
20 |
21 | function assertValue(v: T | undefined, errorMessage: string): T {
22 | if (v === undefined) {
23 | throw new Error(errorMessage)
24 | }
25 |
26 | return v
27 | }
28 |
--------------------------------------------------------------------------------
/sanity/lib/client.ts:
--------------------------------------------------------------------------------
1 | import { createClient } from 'next-sanity'
2 |
3 | import { apiVersion, dataset, projectId, useCdn, token } from '../env'
4 |
5 | export const readClient = createClient({
6 | apiVersion,
7 | dataset,
8 | projectId,
9 | useCdn,
10 | })
11 |
12 | export const writeClient = createClient({
13 | apiVersion,
14 | dataset,
15 | projectId,
16 | useCdn,
17 | token,
18 | })
--------------------------------------------------------------------------------
/sanity/lib/image.ts:
--------------------------------------------------------------------------------
1 | import createImageUrlBuilder from '@sanity/image-url'
2 | import type { Image } from 'sanity'
3 |
4 | import { dataset, projectId } from '../env'
5 |
6 | const imageBuilder = createImageUrlBuilder({
7 | projectId: projectId || '',
8 | dataset: dataset || '',
9 | })
10 |
11 | export const urlForImage = (source: Image) => {
12 | return imageBuilder?.image(source).auto('format').fit('max').url()
13 | }
14 |
--------------------------------------------------------------------------------
/sanity/schemas/index.ts:
--------------------------------------------------------------------------------
1 | import resource from './resource.schema';
2 | import resourcePlaylist from './resource-playlist.schema';
3 |
4 | const schemas = [resource, resourcePlaylist];
5 |
6 | export default schemas;
--------------------------------------------------------------------------------
/sanity/schemas/resource-playlist.schema.ts:
--------------------------------------------------------------------------------
1 | const schema = {
2 | name: "resourcePlaylist",
3 | title: "Resource Playlist",
4 | type: "document",
5 | fields: [
6 | {
7 | name: "title",
8 | title: "Title",
9 | type: "string",
10 | validation: (Rule: any) => Rule.required(),
11 | },
12 | {
13 | name: "resources",
14 | title: "Resources",
15 | type: "array",
16 | of: [
17 | {
18 | type: "reference",
19 | to: [{ type: "resource" }],
20 | },
21 | ],
22 | },
23 | ],
24 | };
25 |
26 | export default schema;
--------------------------------------------------------------------------------
/sanity/schemas/resource.schema.ts:
--------------------------------------------------------------------------------
1 | const schema = {
2 | name: 'resource',
3 | title: 'Resource',
4 | type: 'document',
5 | fields: [
6 | {
7 | name: 'title',
8 | title: 'Title',
9 | type: 'string',
10 | require,
11 | validation: (Rule : any) => Rule.required()
12 | },
13 | {
14 | name: 'slug',
15 | title: 'Slug',
16 | type: 'slug',
17 | options: { source: 'title' },
18 | },
19 | {
20 | name: 'downloadLink',
21 | title: 'Download Link',
22 | type: 'url',
23 | validation: (Rule : any) => Rule.required()
24 | },
25 | {
26 | name: 'views',
27 | title: 'Views',
28 | type: 'number',
29 | initialValue: 0,
30 | },
31 | {
32 | name: 'poster',
33 | title: 'Poster',
34 | type: 'image',
35 | validation: (Rule : any) => Rule.required(),
36 | options: {
37 | hotspot: true
38 | }
39 | },
40 | {
41 | name: 'category',
42 | title: 'Category',
43 | type: 'string',
44 | validations: (Rule : any) => Rule.required(),
45 | options : {
46 | list : ['frontend', 'backend', 'programming','engineering', 'other']
47 |
48 | }
49 | }
50 | ]
51 | };
52 |
53 | export default schema;
--------------------------------------------------------------------------------
/sanity/utils.ts:
--------------------------------------------------------------------------------
1 | import qs from 'query-string'
2 |
3 | interface BuildQueryParams {
4 | type: string;
5 | query: string;
6 | category: string;
7 | page: number;
8 | perPage?: number;
9 | }
10 |
11 | export function buildQuery(params: BuildQueryParams) {
12 | const { type, query, category, page = 1, perPage = 20 } = params;
13 |
14 | const conditions = [`*[_type=="${type}"`];
15 |
16 | if (query) conditions.push(`title match "*${query}*"`);
17 |
18 | if (category && category !== "all") {
19 | conditions.push(`category == "${category}"`);
20 | }
21 |
22 | // Calculate pagination limits
23 | const offset = (page - 1) * perPage;
24 | const limit = perPage;
25 |
26 | return conditions.length > 1
27 | ? `${conditions[0]} && (${conditions
28 | .slice(1)
29 | .join(" && ")})][${offset}...${limit}]`
30 | : `${conditions[0]}][${offset}...${limit}]`;
31 | }
32 |
33 | interface UrlQueryParams {
34 | params: string;
35 | key?: string;
36 | value?: string | null;
37 | keysToRemove?: string[];
38 | }
39 |
40 | export function formUrlQuery({ params, key, value, keysToRemove }: UrlQueryParams) {
41 | const currentUrl = qs.parse(params);
42 |
43 | if(keysToRemove) {
44 | keysToRemove.forEach((keyToRemove) => {
45 | delete currentUrl[keyToRemove];
46 | })
47 | } else if(key && value) {
48 | currentUrl[key] = value;
49 | }
50 |
51 | return qs.stringifyUrl(
52 | { url: window.location.pathname, query: currentUrl },
53 | { skipNull: true }
54 | )
55 | }
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | "./pages/**/*.{ts,tsx}",
6 | "./pages/terms.tsx",
7 | "./components/**/*.{ts,tsx}",
8 | "./app/**/*.{ts,tsx}",
9 | ],
10 | theme: {
11 | screens: {
12 | xs: "400px",
13 | sm: "640px",
14 | md: "768px",
15 | lg: "1024px",
16 | xl: "1220px",
17 | "2xl": "1440px",
18 | "3xl": "1700px",
19 | },
20 | container: {
21 | center: true,
22 | padding: "2rem",
23 | screens: {
24 | "2xl": "1400px",
25 | },
26 | },
27 | extend: {
28 | fontFamily: {
29 | poppins: ["Poppins", "sans-serif"],
30 | inter: ["Inter", "sans-serif"],
31 | },
32 | colors: {
33 | primary: "#2190FF",
34 | black: {
35 | DEFAULT: "#000",
36 | 100: "#0D1117",
37 | 200: "#161B22",
38 | 300: "#1F2428",
39 | 400: "#242C38",
40 | },
41 | grey: {
42 | 100: "#969BA5",
43 | 200: "#55616D",
44 | },
45 | white: {
46 | DEFAULT: "#FFF",
47 | 400: "#A3B3BC",
48 | 500: "#A4B8D5",
49 | 800: "#D0DFFF",
50 | },
51 | purple: "#8C7CFF",
52 | pink: "#ED5FBD",
53 | violet: "#F16565",
54 | orange: "#FF964B",
55 | },
56 | backgroundImage: {
57 | banner: "url('/jsm_resources_banner.svg')",
58 | },
59 | keyframes: {
60 | "accordion-down": {
61 | from: { height: 0 },
62 | to: { height: "var(--radix-accordion-content-height)" },
63 | },
64 | "accordion-up": {
65 | from: { height: "var(--radix-accordion-content-height)" },
66 | to: { height: 0 },
67 | },
68 | },
69 | animation: {
70 | "accordion-down": "accordion-down 0.2s ease-out",
71 | "accordion-up": "accordion-up 0.2s ease-out",
72 | },
73 | },
74 | },
75 | plugins: [require("tailwindcss-animate")],
76 | };
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./*"]
22 | },
23 | "target": "ES2017"
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/(root)/terms/layout.js"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------