Skip to content

Commit

Permalink
Merge pull request #90 from square-story/frontend
Browse files Browse the repository at this point in the history
Frontend
  • Loading branch information
AJMALAJU3 authored Feb 24, 2025
2 parents 7adc3da + 184b449 commit aa99748
Show file tree
Hide file tree
Showing 15 changed files with 706 additions and 29 deletions.
8 changes: 7 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,30 @@
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^4.1.1",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@tailwindcss/vite": "^4.0.6",
"axios": "^1.7.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"embla-carousel-autoplay": "^8.5.2",
"embla-carousel-react": "^8.5.2",
"lucide-react": "^0.475.0",
"next-themes": "^0.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.2",
"react-router-dom": "^7.1.5",
"sonner": "^2.0.1",
"tailwind-merge": "^3.0.1",
"tailwindcss": "^4.0.6",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/api/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import axios from 'axios'

export const axiosInstance = axios.create({
baseURL: 'http://localhost:3000',
headers: {
'Content-Type': 'application/json',
},
withCredentials: true,
});
39 changes: 39 additions & 0 deletions frontend/src/components/password-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from 'react'
import { Eye, EyeOff } from 'lucide-react';
import { cn } from '@/lib/utils'
import { Button } from './ui/button'

type PasswordInputProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'type'
>

const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
({ className, disabled, ...props }, ref) => {
const [showPassword, setShowPassword] = React.useState(false)
return (
<div className={cn('relative rounded-md', className)}>
<input
type={showPassword ? 'text' : 'password'}
className='flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50'
ref={ref}
disabled={disabled}
{...props}
/>
<Button
type='button'
size='icon'
variant='ghost'
disabled={disabled}
className='absolute right-1 top-1/2 h-6 w-6 -translate-y-1/2 rounded-md text-muted-foreground'
onClick={() => setShowPassword((prev) => !prev)}
>
{showPassword ? <Eye size={18} /> : <EyeOff size={18} />}
</Button>
</div>
)
}
)
PasswordInput.displayName = 'PasswordInput'

export { PasswordInput }
175 changes: 175 additions & 0 deletions frontend/src/components/ui/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import * as React from 'react'
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from 'react-hook-form'
import * as LabelPrimitive from '@radix-ui/react-label'
import { Slot } from '@radix-ui/react-slot'
import { cn } from '@/lib/utils'
import { Label } from '@/components/ui/label'

const Form = FormProvider

type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName
}

const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)

const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}

const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()

const fieldState = getFieldState(fieldContext.name, formState)

if (!fieldContext) {
throw new Error('useFormField should be used within <FormField>')
}

const { id } = itemContext

return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}

type FormItemContextValue = {
id: string
}

const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)

const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()

return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn('space-y-2', className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = 'FormItem'

const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()

return (
<Label
ref={ref}
className={cn(error && 'text-destructive', className)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = 'FormLabel'

const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()

return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = 'FormControl'

const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()

return (
<p
ref={ref}
id={formDescriptionId}
className={cn('text-[0.8rem] text-muted-foreground', className)}
{...props}
/>
)
})
FormDescription.displayName = 'FormDescription'

const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children

if (!body) {
return null
}

return (
<p
ref={ref}
id={formMessageId}
className={cn('text-[0.8rem] font-medium text-destructive', className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = 'FormMessage'

export {
useFormField,

Check warning on line 167 in frontend/src/components/ui/form.tsx

View workflow job for this annotation

GitHub Actions / lint

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components

Check warning on line 167 in frontend/src/components/ui/form.tsx

View workflow job for this annotation

GitHub Actions / lint

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}
27 changes: 27 additions & 0 deletions frontend/src/components/ui/sonner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useTheme } from "next-themes"
import { Toaster as Sonner, ToasterProps } from "sonner"

const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()

return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
toastOptions={{
classNames: {
toast:
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground font-medium",
cancelButton:
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground font-medium",
},
}}
{...props}
/>
)
}

export { Toaster }
13 changes: 0 additions & 13 deletions frontend/src/components/user/auth/AuthLeftPanel.tsx

This file was deleted.

66 changes: 66 additions & 0 deletions frontend/src/components/user/auth/components/FormLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Separator } from "@/components/ui/separator"
import { UserAuthForm } from "./user-auth-form"
import Button from "@/components/ui/button"
import { Github } from "lucide-react"
import { useState } from "react"


export const FormLayout = () => {

const [authState, setAuthState] = useState<'login' | 'register'>('login');

return (
<div className='lg:p-8'>
<div className='mx-auto flex w-full flex-col justify-center space-y-2 sm:w-[350px]'>
<div className='flex flex-col space-y-2 text-left'>
<h1 className='text-2xl font-semibold tracking-tight'>{authState === 'login' ? "Welcome Back!" : "Create an Account"}</h1>
<p className='text-sm text-muted-foreground'>
{authState === 'login' ? "Please enter your email and password to log in." : "Please fill in the details to create your account."}
</p>
</div>
<UserAuthForm
authState={authState}
onStateChange={setAuthState}
/>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>

<div className="flex gap-2 ">
<Button variant="outline" className="w-full">
<svg className="mr-2 h-4 w-4" viewBox="0 0 24 24">
<path
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
fill="#4285F4"
/>
<path
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
fill="#34A853"
/>
<path
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
fill="#FBBC05"
/>
<path
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
fill="#EA4335"
/>
</svg>
Google
</Button>
<Button variant="outline" className="w-full">
<Github className="mr-2 h-4 w-4" />
Github
</Button>
</div>
</div>
</div>
)
}
19 changes: 19 additions & 0 deletions frontend/src/components/user/auth/components/SideLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useTheme } from "../../common/theme-provider"
import LightLogo from '@/assets/Logo Light.svg'
import DarkLogo from '@/assets/Logo Dark.svg'

export const SideLayout = () => {
const { theme } = useTheme()
return (
<div className='relative hidden h-full flex-col bg-muted p-10 text-white dark:border-r lg:flex'>
<div className='absolute inset-0 bg-zinc-900' />
<div className='relative z-20 flex items-center text-lg font-medium'>
<img
src={theme == 'light' ? LightLogo : DarkLogo}
className='mr-2 h-20 w-20'
alt='Inker'
/>
</div>
</div>
)
}
Loading

0 comments on commit aa99748

Please sign in to comment.