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&apos;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