diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx new file mode 100644 index 0000000..2ad1c94 --- /dev/null +++ b/app/(auth)/layout.tsx @@ -0,0 +1,38 @@ +import Image from "next/image" +import Link from "next/link" + +const Layout = ({children} : {children:React.ReactNode} ) => { + return ( +
+
+ + App-logo + + +
+ {children} +
+
+ +
+
+
+ "Be fearful when others are greedy, and greedy when others are fearful." + +
+
+
+ — Warren Buffett +

Chairman and CEO of Berkshire Hathaway

+
+
+
+ +
+ Dashboard +
+
+
+ ) +} +export default Layout diff --git a/app/(auth)/sign-in/page.tsx b/app/(auth)/sign-in/page.tsx new file mode 100644 index 0000000..8c049f2 --- /dev/null +++ b/app/(auth)/sign-in/page.tsx @@ -0,0 +1,68 @@ +'use client' +import React from 'react' +import InputField from '@/components/forms/InputField' +import FooterLink from '@/components/forms/FooterLink' +import { Button } from '@/components/ui/button' +import { SubmitHandler, useForm } from 'react-hook-form' + +type SignInFormData = { + email: string + password: string +} + +const SignIn = () => { + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + email: '', + password: '', + }, + mode: 'onBlur' + }) + + const onSubmit: SubmitHandler = async (data) => { + try { + console.log(data) + } catch (e) { + console.error(e) + } + } + + return ( + <> +

Welcome Back

+ +
+ + + + + + + + + + ) +} + +export default SignIn diff --git a/app/(auth)/sign-up/page.tsx b/app/(auth)/sign-up/page.tsx new file mode 100644 index 0000000..6474d18 --- /dev/null +++ b/app/(auth)/sign-up/page.tsx @@ -0,0 +1,123 @@ +'use client' +import { CountrySelectField } from '@/components/forms/CountrySelectField'; +import FooterLink from '@/components/forms/FooterLink'; +import InputField from '@/components/forms/InputField'; +import SelectField from '@/components/forms/SelectField'; +import { Button } from '@/components/ui/button'; +import { INVESTMENT_GOALS, PREFERRED_INDUSTRIES, RISK_TOLERANCE_OPTIONS } from '@/lib/constants'; +import { SubmitHandler, useForm } from 'react-hook-form' + +const SignUp = () => { + const { + register, + handleSubmit, + watch, + control, + + formState: { errors, isSubmitting }, + } = useForm({ + + defaultValues: { + fullName: '', + email: '', + password: '', + country: 'INDIA', + investmentGoals: 'Growth', + riskTolerance: 'Medium', + preferredIndustry: 'Technology' + }, + mode: 'onBlur' + }, ); + + const onSubmit = async(data: SignUpFormData) => { + try { + console.log(data); + }catch (e){ + console.error(e); + } + } + return ( + <> +

Sign Up & Personalize

+ +
+ {/* Input */} + + + + + + + + + + + + + + + + + + + ) +} + +export default SignUp diff --git a/components/forms/CountrySelectField.tsx b/components/forms/CountrySelectField.tsx new file mode 100644 index 0000000..5677b1b --- /dev/null +++ b/components/forms/CountrySelectField.tsx @@ -0,0 +1,146 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +'use client'; + +import { useState } from 'react'; +import { Control, Controller, FieldError } from 'react-hook-form'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from '@/components/ui/command'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Check, ChevronsUpDown } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import countryList from 'react-select-country-list'; + +type CountrySelectProps = { + name: string; + label: string; + control: Control; + error?: FieldError; + required?: boolean; +}; + +const CountrySelect = ({ + value, + onChange, + }: { + value: string; + onChange: (value: string) => void; +}) => { + const [open, setOpen] = useState(false); + + // Get country options with flags + const countries = countryList().getData(); + + // Helper function to get flag emoji + const getFlagEmoji = (countryCode: string) => { + const codePoints = countryCode + .toUpperCase() + .split('') + .map((char) => 127397 + char.charCodeAt(0)); + return String.fromCodePoint(...codePoints); + }; + + return ( + + + + + + + + + No country found. + + + + {countries.map((country) => ( + { + onChange(country.value); + setOpen(false); + }} + className='country-select-item' + > + + + {getFlagEmoji(country.value)} + {country.label} + + + ))} + + + + + + ); +}; + +export const CountrySelectField = ({ + name, + label, + control, + error, + required = false, + }: CountrySelectProps) => { + return ( +
+ + ( + + )} + /> + {error &&

{error.message}

} +

+ Helps us show market data and news relevant to you. +

+
+ ); +}; \ No newline at end of file diff --git a/components/forms/FooterLink.tsx b/components/forms/FooterLink.tsx new file mode 100644 index 0000000..a7ffd0e --- /dev/null +++ b/components/forms/FooterLink.tsx @@ -0,0 +1,16 @@ + +import Link from 'next/link' +import React from 'react' + +const FooterLink = ({text, linkText , href} : FooterLinkProps) => ( +
+

+ {text}{` `} + + {linkText} + +

+
+) + +export default FooterLink diff --git a/components/forms/InputField.tsx b/components/forms/InputField.tsx new file mode 100644 index 0000000..51257c8 --- /dev/null +++ b/components/forms/InputField.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import {Label} from "@/components/ui/label"; +import {Input} from "@/components/ui/input"; +import {cn} from "@/lib/utils"; + +const InputField = ({ name, label, placeholder, type = "text", register, error, validation, disabled, value }: FormInputProps) => { + return ( +
+ + + {error &&

{error.message}

} +
+ ) +} +export default InputField \ No newline at end of file diff --git a/components/forms/SelectField.tsx b/components/forms/SelectField.tsx new file mode 100644 index 0000000..937fbea --- /dev/null +++ b/components/forms/SelectField.tsx @@ -0,0 +1,41 @@ +import {Label} from "@/components/ui/label"; +import {Controller} from "react-hook-form"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" + +const SelectField = ({ name, label, placeholder, options, control, error, required = false }: SelectFieldProps) => { + return ( +
+ + + ( + + )} + /> +
+ ) +} +export default SelectField \ No newline at end of file diff --git a/components/ui/command.tsx b/components/ui/command.tsx new file mode 100644 index 0000000..8cb4ca7 --- /dev/null +++ b/components/ui/command.tsx @@ -0,0 +1,184 @@ +"use client" + +import * as React from "react" +import { Command as CommandPrimitive } from "cmdk" +import { SearchIcon } from "lucide-react" + +import { cn } from "@/lib/utils" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" + +function Command({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandDialog({ + title = "Command Palette", + description = "Search for a command to run...", + children, + className, + showCloseButton = true, + ...props +}: React.ComponentProps & { + title?: string + description?: string + className?: string + showCloseButton?: boolean +}) { + return ( + + + {title} + {description} + + + + {children} + + + + ) +} + +function CommandInput({ + className, + ...props +}: React.ComponentProps) { + return ( +
+ + +
+ ) +} + +function CommandList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandEmpty({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx new file mode 100644 index 0000000..d9ccec9 --- /dev/null +++ b/components/ui/dialog.tsx @@ -0,0 +1,143 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Dialog({ + ...props +}: React.ComponentProps) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/components/ui/input.tsx b/components/ui/input.tsx new file mode 100644 index 0000000..8916905 --- /dev/null +++ b/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/components/ui/label.tsx b/components/ui/label.tsx new file mode 100644 index 0000000..fb5fbc3 --- /dev/null +++ b/components/ui/label.tsx @@ -0,0 +1,24 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" + +import { cn } from "@/lib/utils" + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Label } diff --git a/components/ui/popover.tsx b/components/ui/popover.tsx new file mode 100644 index 0000000..01e468b --- /dev/null +++ b/components/ui/popover.tsx @@ -0,0 +1,48 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +function Popover({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverContent({ + className, + align = "center", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function PopoverAnchor({ + ...props +}: React.ComponentProps) { + return +} + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/components/ui/select.tsx b/components/ui/select.tsx new file mode 100644 index 0000000..25e5439 --- /dev/null +++ b/components/ui/select.tsx @@ -0,0 +1,187 @@ +"use client" + +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Select({ + ...props +}: React.ComponentProps) { + return +} + +function SelectGroup({ + ...props +}: React.ComponentProps) { + return +} + +function SelectValue({ + ...props +}: React.ComponentProps) { + return +} + +function SelectTrigger({ + className, + size = "default", + children, + ...props +}: React.ComponentProps & { + size?: "sm" | "default" +}) { + return ( + + {children} + + + + + ) +} + +function SelectContent({ + className, + children, + position = "popper", + align = "center", + ...props +}: React.ComponentProps) { + return ( + + + + + {children} + + + + + ) +} + +function SelectLabel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function SelectSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} diff --git a/package-lock.json b/package-lock.json index 1523042..b4fdd39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,14 +9,21 @@ "version": "0.1.0", "dependencies": { "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slot": "^1.2.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "lucide-react": "^0.544.0", "next": "15.5.4", "react": "19.1.0", "react-dom": "19.1.0", + "react-hook-form": "^7.64.0", + "react-select-country-list": "^2.2.3", "tailwind-merge": "^3.3.1" }, "devDependencies": { @@ -25,6 +32,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@types/react-select-country-list": "^2.2.3", "eslint": "^9", "eslint-config-next": "15.5.4", "tailwindcss": "^4", @@ -1005,6 +1013,12 @@ "node": ">=12.4.0" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", @@ -1117,6 +1131,42 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "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-direction": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", @@ -1246,6 +1296,29 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "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-menu": { "version": "2.1.16", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", @@ -1286,6 +1359,43 @@ } } }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "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-popper": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", @@ -1420,6 +1530,49 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "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-slot": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", @@ -1541,6 +1694,21 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", @@ -1577,6 +1745,29 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "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/rect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", @@ -1944,6 +2135,13 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-select-country-list": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/react-select-country-list/-/react-select-country-list-2.2.3.tgz", + "integrity": "sha512-nffcYOwuun+5B0EWqubK+amHpPdK9Xj20xkLYNqYrzmESd8FnpLwHsS79ClLAWA9y+icVA8gWPkbwBp1gpjSwA==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.45.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz", @@ -2974,6 +3172,22 @@ "node": ">=6" } }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -5738,6 +5952,22 @@ "react": "^19.1.0" } }, + "node_modules/react-hook-form": { + "version": "7.64.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.64.0.tgz", + "integrity": "sha512-fnN+vvTiMLnRqKNTVhDysdrUay0kUUAymQnFIznmgDvapjveUWOOPqMNzPg+A+0yf9DuE2h6xzBjN1s+Qx8wcg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -5792,6 +6022,12 @@ } } }, + "node_modules/react-select-country-list": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-select-country-list/-/react-select-country-list-2.2.3.tgz", + "integrity": "sha512-eRgXL613dVyJiE99yKDYLvSBKDxvIlhkmvO2DVIjdKVyUQq6kBqoMUV/2zuRIAsbRXgBGmKjeL1dxjf7zTfszg==", + "license": "MIT" + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", diff --git a/package.json b/package.json index a90c51b..492347b 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,21 @@ }, "dependencies": { "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slot": "^1.2.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "lucide-react": "^0.544.0", "next": "15.5.4", "react": "19.1.0", "react-dom": "19.1.0", + "react-hook-form": "^7.64.0", + "react-select-country-list": "^2.2.3", "tailwind-merge": "^3.3.1" }, "devDependencies": { @@ -26,6 +33,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@types/react-select-country-list": "^2.2.3", "eslint": "^9", "eslint-config-next": "15.5.4", "tailwindcss": "^4", diff --git a/types/global.d.ts b/types/global.d.ts new file mode 100644 index 0000000..b01836b --- /dev/null +++ b/types/global.d.ts @@ -0,0 +1,220 @@ +declare global { + type SignInFormData = { + email: string; + password: string; + }; + + type SignUpFormData = { + fullName: string; + email: string; + password: string; + country: string; + investmentGoals: string; + riskTolerance: string; + preferredIndustry: string; + }; + + type CountrySelectProps = { + name: string; + label: string; + control: Control; + error?: FieldError; + required?: boolean; + }; + + type FormInputProps = { + name: string; + label: string; + placeholder: string; + type?: string; + register: UseFormRegister; + error?: FieldError; + validation?: RegisterOptions; + disabled?: boolean; + value?: string; + }; + + type Option = { + value: string; + label: string; + }; + + type SelectFieldProps = { + name: string; + label: string; + placeholder: string; + options: readonly Option[]; + control: Control; + error?: FieldError; + required?: boolean; + }; + + type FooterLinkProps = { + text: string; + linkText: string; + href: string; + }; + + type SearchCommandProps = { + renderAs?: 'button' | 'text'; + label?: string; + initialStocks: StockWithWatchlistStatus[]; + }; + + type WelcomeEmailData = { + email: string; + name: string; + intro: string; + }; + + type User = { + id: string; + name: string; + email: string; + }; + + type Stock = { + symbol: string; + name: string; + exchange: string; + type: string; + }; + + type StockWithWatchlistStatus = Stock & { + isInWatchlist: boolean; + }; + + type FinnhubSearchResult = { + symbol: string; + description: string; + displaySymbol?: string; + type: string; + }; + + type FinnhubSearchResponse = { + count: number; + result: FinnhubSearchResult[]; + }; + + type StockDetailsPageProps = { + params: Promise<{ + symbol: string; + }>; + }; + + type WatchlistButtonProps = { + symbol: string; + company: string; + isInWatchlist: boolean; + showTrashIcon?: boolean; + type?: 'button' | 'icon'; + onWatchlistChange?: (symbol: string, isAdded: boolean) => void; + }; + + type QuoteData = { + c?: number; + dp?: number; + }; + + type ProfileData = { + name?: string; + marketCapitalization?: number; + }; + + type FinancialsData = { + metric?: { [key: string]: number }; + }; + + type SelectedStock = { + symbol: string; + company: string; + currentPrice?: number; + }; + + type WatchlistTableProps = { + watchlist: StockWithData[]; + }; + + type StockWithData = { + userId: string; + symbol: string; + company: string; + addedAt: Date; + currentPrice?: number; + changePercent?: number; + priceFormatted?: string; + changeFormatted?: string; + marketCap?: string; + peRatio?: string; + }; + + type AlertsListProps = { + alertData: Alert[] | undefined; + }; + + type MarketNewsArticle = { + id: number; + headline: string; + summary: string; + source: string; + url: string; + datetime: number; + category: string; + related: string; + image?: string; + }; + + type WatchlistNewsProps = { + news?: MarketNewsArticle[]; + }; + + type SearchCommandProps = { + open?: boolean; + setOpen?: (open: boolean) => void; + renderAs?: 'button' | 'text'; + buttonLabel?: string; + buttonVariant?: 'primary' | 'secondary'; + className?: string; + }; + + type AlertData = { + symbol: string; + company: string; + alertName: string; + alertType: 'upper' | 'lower'; + threshold: string; + }; + + type AlertModalProps = { + alertId?: string; + alertData?: AlertData; + action?: string; + open: boolean; + setOpen: (open: boolean) => void; + }; + + type RawNewsArticle = { + id: number; + headline?: string; + summary?: string; + source?: string; + url?: string; + datetime?: number; + image?: string; + category?: string; + related?: string; + }; + + type Alert = { + id: string; + symbol: string; + company: string; + alertName: string; + currentPrice: number; + alertType: 'upper' | 'lower'; + threshold: number; + changePercent?: number; + }; +} + +export {}; \ No newline at end of file