From 9006dcbb382f71c31c36b2f49c6042f02e336d4b Mon Sep 17 00:00:00 2001 From: nirmalkar Date: Tue, 12 Nov 2024 23:54:48 +0530 Subject: [PATCH 1/2] updates --- package-lock.json | 28 ++++ package.json | 1 + src/app/dashboard/page.tsx | 2 + src/app/globals.css | 6 + src/app/layout.tsx | 4 +- src/components/common/Modal/index.tsx | 58 +++++++ .../specific/CarryOnWhereLeft/index.tsx | 5 - .../specific/LearnContent/ContentSections.tsx | 10 +- src/components/specific/hasUpdate/index.tsx | 45 ++++++ src/components/ui/alert-dialog.tsx | 141 ++++++++++++++++++ src/components/ui/alert.tsx | 60 ++++++++ src/components/ui/button.tsx | 2 +- src/context/AuthContext.tsx | 43 +++++- src/types/content.ts | 2 +- tailwind.config.js | 4 + 15 files changed, 394 insertions(+), 17 deletions(-) create mode 100644 src/components/common/Modal/index.tsx create mode 100644 src/components/specific/hasUpdate/index.tsx create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/alert.tsx diff --git a/package-lock.json b/package-lock.json index 355a78e..5cb9e9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "ui", "version": "0.1.0", "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", @@ -2167,6 +2168,33 @@ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.2.tgz", + "integrity": "sha512-eGSlLzPhKO+TErxkiGcCZGuvbVMnLA1MTnyBksGOeGRGkxHiiJUujsjmNTdWTm4iHVSRaUao9/4Ur671auMghQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dialog": "1.1.2", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", diff --git a/package.json b/package.json index b4633ff..befb8cc 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "prepare": "husky install" }, "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index affa37c..db685c9 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -7,6 +7,7 @@ import AuthGuard from '@/components/misc/authGuard'; import CarryOnWhereLeft from '@/components/specific/CarryOnWhereLeft'; import ExploreLanguages from '@/components/specific/ExploreLanguages'; import Greetings from '@/components/specific/Greetings'; +import HasUpdate from '@/components/specific/hasUpdate'; import { Button } from '@/components/ui/button'; import { Card, @@ -46,6 +47,7 @@ function Dashboard() {
+
diff --git a/src/app/globals.css b/src/app/globals.css index 92be081..ef6358c 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -28,6 +28,9 @@ --chart-4: 43 74% 66%; --chart-5: 27 87% 67%; --radius: 0.5rem; + --main: 173, 100%, 24%; + --main-foreground: 0 0% 98%; + --main-dark: 173, 100%, 18%; } .dark { --background: 0 0% 3.9%; @@ -54,6 +57,9 @@ --chart-3: 30 80% 55%; --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; + --main: 173, 100%, 24%; + --main-foreground: 0 0% 98%; + --main-dark: 173, 100%, 18%; } } @layer base { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index a54d6b8..7fe063e 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -19,8 +19,8 @@ const geistMono = localFont({ }); export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', + title: 'biteScript', + description: 'where coding are gods made!', }; export default function RootLayout({ diff --git a/src/components/common/Modal/index.tsx b/src/components/common/Modal/index.tsx new file mode 100644 index 0000000..1fd25d9 --- /dev/null +++ b/src/components/common/Modal/index.tsx @@ -0,0 +1,58 @@ +import Image from 'next/image'; +import React from 'react'; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; +import { ModelTypes } from '@/types/Model.types'; + +const Model: React.FC = ({ + title, + description, + secondaryDescription, + buttonText, + cancelButtonText, + image, + showModel, + hideModel, + handleContinue, +}) => { + return ( + + + + {title} + {image && ( + image-text + )} + + {description} + {secondaryDescription && ( +

{secondaryDescription}

+ )} +
+
+ + + {cancelButtonText} + + + {buttonText} + + +
+
+ ); +}; + +export default Model; diff --git a/src/components/specific/CarryOnWhereLeft/index.tsx b/src/components/specific/CarryOnWhereLeft/index.tsx index 9503831..ef97863 100644 --- a/src/components/specific/CarryOnWhereLeft/index.tsx +++ b/src/components/specific/CarryOnWhereLeft/index.tsx @@ -32,11 +32,6 @@ const CarryOnWhereLeft: React.FC = () => { user && currentLang && getUserLearningProgress(user?.uid, currentLang); }, []); - useEffect(() => { - if (learningProgress) { - // setCurrentTopic() - } - }); const handleCarryOnClick = () => { router.push(carryOnPath); }; diff --git a/src/components/specific/LearnContent/ContentSections.tsx b/src/components/specific/LearnContent/ContentSections.tsx index 06c9ab8..09aac8d 100644 --- a/src/components/specific/LearnContent/ContentSections.tsx +++ b/src/components/specific/LearnContent/ContentSections.tsx @@ -98,8 +98,8 @@ const ContentSections: React.FC = ({ case 'examples': return (
- {courseContent?.examples.length && Examples:} - {courseContent?.examples.map((example, i) => ( + {courseContent?.examples?.length && Examples:} + {courseContent?.examples?.map((example, i) => (

{example.description}

@@ -166,6 +166,12 @@ const ContentSections: React.FC = ({ code={`${decodeEntities(common_mistake.code)}`} /> )} + Fix: + {common_mistake?.fix && ( + + )} ); } diff --git a/src/components/specific/hasUpdate/index.tsx b/src/components/specific/hasUpdate/index.tsx new file mode 100644 index 0000000..ebe265a --- /dev/null +++ b/src/components/specific/hasUpdate/index.tsx @@ -0,0 +1,45 @@ +import { signOut } from 'firebase/auth'; +import React, { useState } from 'react'; + +import Model from '@/components/common/Modal'; +import { useAuth } from '@/context/AuthContext'; +import { useLocalStorage } from '@/context/LocalhostContext'; + +import { auth } from '../../../../lib/firebase'; + +const hasUpdateModel = { + title: `Let's update?`, + description: + 'Update is available. You can update to the latest version with some new features and fixes.', + secondaryDescription: + 'You will be logged out and you will have to re-login to see the changes!', + buttonText: 'Continue', + cancelButtonText: 'Nah! I will do it later!', +}; + +const HasUpdate: React.FC = () => { + const { getItem, removeItem } = useLocalStorage(); + const { updateUser } = useAuth(); + const user = getItem('user'); + const [showModel, setShowModel] = useState(user.has_update); + if (!user.has_update) { + return; + } + const handleContinue = async () => { + updateUser(user, { has_update: false }); + removeItem('content_javascript'); + await signOut(auth); + }; + return ( +
+ setShowModel(false)} + handleContinue={handleContinue} + /> +
+ ); +}; + +export default HasUpdate; diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..1d6d0f9 --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,141 @@ +'use client'; + +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'; +import * as React from 'react'; + +import { buttonVariants } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogHeader.displayName = 'AlertDialogHeader'; + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogFooter.displayName = 'AlertDialogFooter'; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 0000000..a4ef748 --- /dev/null +++ b/src/components/ui/alert.tsx @@ -0,0 +1,60 @@ +import { cva, type VariantProps } from 'class-variance-authority'; +import * as React from 'react'; + +import { cn } from '@/lib/utils'; + +const alertVariants = cva( + 'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7', + { + variants: { + variant: { + default: 'bg-background text-foreground', + destructive: + 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive', + }, + }, + defaultVariants: { + variant: 'default', + }, + } +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = 'Alert'; + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + // eslint-disable-next-line jsx-a11y/heading-has-content +
+)); +AlertTitle.displayName = 'AlertTitle'; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = 'AlertDescription'; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index a371e6e..2343bfc 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { cn } from '@/lib/utils'; const buttonVariants = cva( - 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', { variants: { variant: { diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index 6986db9..6f2cbd5 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -1,5 +1,5 @@ import { signInWithPopup, onAuthStateChanged, getAuth } from 'firebase/auth'; -import { doc, getDoc, setDoc } from 'firebase/firestore/lite'; +import { doc, getDoc, setDoc, updateDoc } from 'firebase/firestore/lite'; import { useRouter } from 'next/navigation'; import { createContext, @@ -18,12 +18,17 @@ interface AuthContextType { signInWithGoogle: () => Promise; signInWithGithub: () => Promise; signOut: () => Promise; + updateUser: (user: UserInfo, updates: Partial) => Promise; } interface UserInfo { uid: string; displayName: string; email: string; photoURL: string | null; + createdAt?: string; + updatedAt?: string; + lastLoginAt?: string; + has_update?: boolean; } const AuthContext = createContext(undefined); @@ -55,15 +60,20 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { const saveUser = async (user: UserInfo) => { const userDocRef = doc(db, 'users', user.uid); const userDoc = await getDoc(userDocRef); // Check if the user exists - + console.log(user, 'user saved'); + const { uid, displayName, email, photoURL, has_update } = user; if (!userDoc.exists()) { // User doesn't exist, save them try { await setDoc(userDocRef, { - uid: user.uid, - displayName: user.displayName, - email: user.email, - photoURL: user.photoURL, + uid, + displayName, + email, + photoURL, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + lastLoginAt: new Date().toISOString(), + has_update, }); console.log('User saved to Firestore:', user); } catch (error) { @@ -84,6 +94,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { displayName: user.displayName || 'Anonymous', email: user.email || 'No Email', photoURL: user.photoURL, + has_update: false, }; localStorage.setItem('user', JSON.stringify(importantUserInfo)); @@ -97,6 +108,24 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { console.error('Google Sign In Error:', error); } }; + const updateUser = async (user: UserInfo, updates: Partial) => { + const userDocRef = doc(db, 'users', user.uid); + const userDoc = await getDoc(userDocRef); + console.log(user.uid, updates, 'user.uid'); + if (userDoc.exists()) { + try { + await updateDoc(userDocRef, { + ...updates, // Only update the fields that are passed + updatedAt: new Date().toISOString(), + }); + console.log('User updated in Firestore:', user); + } catch (error) { + console.error('Error updating user in Firestore:', error); + } + } else { + console.log('User does not exist in Firestore'); + } + }; const signInWithGithub = async () => { try { @@ -107,6 +136,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { displayName: user.displayName || 'Anonymous', email: user.email || 'No Email', photoURL: user.photoURL, + has_update: false, }; localStorage.setItem('user', JSON.stringify(importantUserInfo)); await saveUser(importantUserInfo); @@ -147,6 +177,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { signInWithGoogle, signInWithGithub, signOut, + updateUser, }} > {children} diff --git a/src/types/content.ts b/src/types/content.ts index 6ede338..82ddd6b 100644 --- a/src/types/content.ts +++ b/src/types/content.ts @@ -22,7 +22,7 @@ interface Topic { } interface BestPracticesAndCommonMistakes { - common_mistakes: { text: string; code?: string }[]; + common_mistakes: { text: string; code?: string; fix?: string }[]; best_practices: { text: string; code?: string }[]; } diff --git a/tailwind.config.js b/tailwind.config.js index ea21f67..b1672fc 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -28,6 +28,10 @@ module.exports = { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))', }, + main: { + DEFAULT: 'hsl(var(--main))', + foreground: 'hsl(var(--main-foreground))', + }, secondary: { DEFAULT: 'hsl(var(--secondary))', foreground: 'hsl(var(--secondary-foreground))', From 2022e494d7ea020b99cf1f666b1f3075446e14b9 Mon Sep 17 00:00:00 2001 From: nirmalkar Date: Mon, 2 Dec 2024 12:55:10 +0530 Subject: [PATCH 2/2] adding missed types file for model component --- src/types/Model.types.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/types/Model.types.ts diff --git a/src/types/Model.types.ts b/src/types/Model.types.ts new file mode 100644 index 0000000..fc74892 --- /dev/null +++ b/src/types/Model.types.ts @@ -0,0 +1,11 @@ +export type ModelTypes = { + title: string; + description?: string; + secondaryDescription?: string; + image?: string; + cancelButtonText?: string; + buttonText?: string; + handleContinue?: () => void; + showModel: boolean; + hideModel: () => void; +};