From 2d4d473451419568aa762e31aea5216ba96e31e2 Mon Sep 17 00:00:00 2001 From: "ALMAZROUEI Shamma (2021) WKIS203" <shamma.almazrouei.2021@live.rhul.ac.uk> Date: Sun, 23 Feb 2025 10:18:22 +0530 Subject: [PATCH] create login form --- skillswap/eslint.config.js | 13 +-- skillswap/package-lock.json | 87 +++++++++++++++++++- skillswap/package.json | 2 + skillswap/src/App.jsx | 8 +- skillswap/src/app/login/page.jsx | 12 +++ skillswap/src/components/login-form.jsx | 101 ++++++++++++++++++++++++ skillswap/src/components/ui/button.jsx | 48 +++++++++++ skillswap/src/components/ui/card.jsx | 50 ++++++++++++ skillswap/src/components/ui/input.jsx | 19 +++++ skillswap/src/components/ui/label.jsx | 16 ++++ 10 files changed, 346 insertions(+), 10 deletions(-) create mode 100644 skillswap/src/app/login/page.jsx create mode 100644 skillswap/src/components/login-form.jsx create mode 100644 skillswap/src/components/ui/button.jsx create mode 100644 skillswap/src/components/ui/card.jsx create mode 100644 skillswap/src/components/ui/input.jsx create mode 100644 skillswap/src/components/ui/label.jsx diff --git a/skillswap/eslint.config.js b/skillswap/eslint.config.js index 238d2e4..fcd4b8c 100644 --- a/skillswap/eslint.config.js +++ b/skillswap/eslint.config.js @@ -1,8 +1,8 @@ -import js from '@eslint/js' -import globals from 'globals' -import react from 'eslint-plugin-react' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' +import js from '@eslint/js'; +import globals from 'globals'; +import react from 'eslint-plugin-react'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; export default [ { ignores: ['dist'] }, @@ -28,6 +28,7 @@ export default [ ...react.configs.recommended.rules, ...react.configs['jsx-runtime'].rules, ...reactHooks.configs.recommended.rules, + 'react/prop-types': 'off', 'react/jsx-no-target-blank': 'off', 'react-refresh/only-export-components': [ 'warn', @@ -35,4 +36,4 @@ export default [ ], }, }, -] +]; diff --git a/skillswap/package-lock.json b/skillswap/package-lock.json index a345899..3a76774 100644 --- a/skillswap/package-lock.json +++ b/skillswap/package-lock.json @@ -8,6 +8,8 @@ "name": "skillswap", "version": "0.0.0", "dependencies": { + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-slot": "^1.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.475.0", @@ -1082,6 +1084,85 @@ "node": ">=14" } }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "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-label": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.2.tgz", + "integrity": "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "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-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "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.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.34.8", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", @@ -1411,7 +1492,7 @@ "version": "19.0.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1421,7 +1502,7 @@ "version": "19.0.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" @@ -2055,7 +2136,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/data-view-buffer": { diff --git a/skillswap/package.json b/skillswap/package.json index ce8f678..1af2cca 100644 --- a/skillswap/package.json +++ b/skillswap/package.json @@ -10,6 +10,8 @@ "preview": "vite preview" }, "dependencies": { + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-slot": "^1.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.475.0", diff --git a/skillswap/src/App.jsx b/skillswap/src/App.jsx index 1f1b34b..a40828d 100644 --- a/skillswap/src/App.jsx +++ b/skillswap/src/App.jsx @@ -1,3 +1,9 @@ +import { LoginForm } from './components/login-form'; + export default function App() { - return <div>App</div>; + return ( + <div> + <LoginForm /> + </div> + ); } diff --git a/skillswap/src/app/login/page.jsx b/skillswap/src/app/login/page.jsx new file mode 100644 index 0000000..979614e --- /dev/null +++ b/skillswap/src/app/login/page.jsx @@ -0,0 +1,12 @@ +import { LoginForm } from "@/components/login-form" + +export default function LoginPage() { + return ( + (<div + className="flex min-h-svh flex-col items-center justify-center bg-muted p-6 md:p-10"> + <div className="w-full max-w-sm md:max-w-3xl"> + <LoginForm /> + </div> + </div>) + ); +} diff --git a/skillswap/src/components/login-form.jsx b/skillswap/src/components/login-form.jsx new file mode 100644 index 0000000..3187263 --- /dev/null +++ b/skillswap/src/components/login-form.jsx @@ -0,0 +1,101 @@ +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; + +export function LoginForm({ className, ...props }) { + return ( + <div className={cn('flex flex-col gap-6', className)} {...props}> + <Card className='overflow-hidden'> + <CardContent className='grid p-0 md:grid-cols-2'> + <form className='p-6 md:p-8'> + <div className='flex flex-col gap-6'> + <div className='flex flex-col items-center text-center'> + <h1 className='text-2xl font-bold'>Welcome back</h1> + <p className='text-balance text-muted-foreground'> + Login to your Acme Inc account + </p> + </div> + <div className='grid gap-2'> + <Label htmlFor='email'>Email</Label> + <Input + id='email' + type='email' + placeholder='m@example.com' + required + /> + </div> + <div className='grid gap-2'> + <div className='flex items-center'> + <Label htmlFor='password'>Password</Label> + <a + href='#' + className='ml-auto text-sm underline-offset-2 hover:underline' + > + Forgot your password? + </a> + </div> + <Input id='password' type='password' required /> + </div> + <Button type='submit' className='w-full'> + Login + </Button> + <div className='relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border'> + <span className='relative z-10 bg-background px-2 text-muted-foreground'> + Or continue with + </span> + </div> + <div className='grid grid-cols-3 gap-4'> + <Button variant='outline' className='w-full'> + <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'> + <path + d='M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701' + fill='currentColor' + /> + </svg> + <span className='sr-only'>Login with Apple</span> + </Button> + <Button variant='outline' className='w-full'> + <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'> + <path + d='M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z' + fill='currentColor' + /> + </svg> + <span className='sr-only'>Login with Google</span> + </Button> + <Button variant='outline' className='w-full'> + <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'> + <path + d='M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z' + fill='currentColor' + /> + </svg> + <span className='sr-only'>Login with Meta</span> + </Button> + </div> + <div className='text-center text-sm'> + Don't have an account?{' '} + <a href='#' className='underline underline-offset-4'> + Sign up + </a> + </div> + </div> + </form> + <div className='relative hidden bg-muted md:block'> + <img + src='/placeholder.svg' + alt='Image' + className='absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale' + /> + </div> + </CardContent> + </Card> + <div className='text-balance text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 hover:[&_a]:text-primary'> + By clicking continue, you agree to our <a href='#'>Terms of Service</a>{' '} + and <a href='#'>Privacy Policy</a>. + </div> + </div> + ); +} diff --git a/skillswap/src/components/ui/button.jsx b/skillswap/src/components/ui/button.jsx new file mode 100644 index 0000000..bf3d2ef --- /dev/null +++ b/skillswap/src/components/ui/button.jsx @@ -0,0 +1,48 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva } from "class-variance-authority"; + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "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: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + (<Comp + className={cn(buttonVariants({ variant, size, className }))} + ref={ref} + {...props} />) + ); +}) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/skillswap/src/components/ui/card.jsx b/skillswap/src/components/ui/card.jsx new file mode 100644 index 0000000..2985cca --- /dev/null +++ b/skillswap/src/components/ui/card.jsx @@ -0,0 +1,50 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef(({ className, ...props }, ref) => ( + <div + ref={ref} + className={cn("rounded-xl border bg-card text-card-foreground shadow", className)} + {...props} /> +)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef(({ className, ...props }, ref) => ( + <div + ref={ref} + className={cn("flex flex-col space-y-1.5 p-6", className)} + {...props} /> +)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef(({ className, ...props }, ref) => ( + <div + ref={ref} + className={cn("font-semibold leading-none tracking-tight", className)} + {...props} /> +)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef(({ className, ...props }, ref) => ( + <div + ref={ref} + className={cn("text-sm text-muted-foreground", className)} + {...props} /> +)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef(({ className, ...props }, ref) => ( + <div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> +)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef(({ className, ...props }, ref) => ( + <div + ref={ref} + className={cn("flex items-center p-6 pt-0", className)} + {...props} /> +)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/skillswap/src/components/ui/input.jsx b/skillswap/src/components/ui/input.jsx new file mode 100644 index 0000000..41ec05e --- /dev/null +++ b/skillswap/src/components/ui/input.jsx @@ -0,0 +1,19 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Input = React.forwardRef(({ className, type, ...props }, ref) => { + return ( + (<input + type={type} + className={cn( + "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", + className + )} + ref={ref} + {...props} />) + ); +}) +Input.displayName = "Input" + +export { Input } diff --git a/skillswap/src/components/ui/label.jsx b/skillswap/src/components/ui/label.jsx new file mode 100644 index 0000000..a1f4099 --- /dev/null +++ b/skillswap/src/components/ui/label.jsx @@ -0,0 +1,16 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva } from "class-variance-authority"; + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef(({ className, ...props }, ref) => ( + <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} /> +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } -- GitLab