Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
36 changes: 36 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
Binary file added frontend/app/assets/josh_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/app/favicon.ico
Binary file not shown.
26 changes: 26 additions & 0 deletions frontend/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@import "tailwindcss";

:root {
--background: #ffffff;
--foreground: #171717;
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}

body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
34 changes: 34 additions & 0 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});

const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});

export const metadata: Metadata = {
title: "Josh - API Dashboard",
description: "Unlock the power of AI with Josh",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}
183 changes: 183 additions & 0 deletions frontend/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
'use client';

import { motion } from 'framer-motion';
import Image from 'next/image';
import Link from 'next/link';
import { useState } from 'react';

export default function LoginPage() {
const [formData, setFormData] = useState({
email: '',
password: '',
});

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Handle login logic here
console.log('Login:', formData);
};

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};

return (
<div className="min-h-screen flex">
{/* Left Section - Orange Gradient Background */}
<motion.div
className="w-3/5 bg-gradient-to-b from-orange-600 to-orange-400 flex items-center justify-center relative"
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8, ease: "easeOut" }}
>
{/* Josh Logo */}
<motion.div
className="flex items-center justify-center"
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.3, duration: 0.6, ease: "easeOut" }}
>
<Image
src="/assets/josh_logo.png"
alt="Josh Logo"
width={200}
height={80}
className="object-contain"
/>
</motion.div>
</motion.div>

{/* Right Section - Login Form */}
<motion.div
className="w-3/5 bg-gray-50 flex flex-col justify-center p-12"
initial={{ opacity: 0, x: 50 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8, ease: "easeOut" }}
>
<motion.div
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.4, duration: 0.6 }}
>
<div className="max-w-md mx-auto">
{/* Header */}
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-gray-800 mb-2">Welcome Back</h1>
<p className="text-gray-600">Sign in to your Josh account</p>
</div>

{/* Login Form */}
<motion.form
onSubmit={handleSubmit}
className="space-y-6"
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.6, duration: 0.6 }}
>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-2">
Email Address
</label>
<motion.input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent transition-all duration-200"
placeholder="Enter your email"
whileFocus={{ scale: 1.02 }}
required
/>
</div>

<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-2">
Password
</label>
<motion.input
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleChange}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent transition-all duration-200"
placeholder="Enter your password"
whileFocus={{ scale: 1.02 }}
required
/>
</div>

<div className="flex items-center justify-between">
<label className="flex items-center">
<input type="checkbox" className="rounded border-gray-300 text-orange-500 focus:ring-orange-500" />
<span className="ml-2 text-sm text-gray-600">Remember me</span>
</label>
<Link href="/forgot-password" className="text-sm text-orange-500 hover:text-orange-600 transition-colors">
Forgot password?
</Link>
</div>

<motion.button
type="submit"
className="w-full bg-orange-500 text-white py-3 rounded-lg font-medium hover:bg-orange-600 transition-all duration-200"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
Sign In
</motion.button>
</motion.form>

{/* Divider */}
<motion.div
className="flex items-center my-8"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.8, duration: 0.6 }}
>
<div className="flex-1 border-t border-gray-300"></div>
<span className="px-4 text-sm text-gray-500">Or continue with</span>
<div className="flex-1 border-t border-gray-300"></div>
</motion.div>

{/* Social Login */}
<motion.div
className="grid grid-cols-2 gap-4"
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 1, duration: 0.6 }}
>
<motion.button
className="flex items-center justify-center gap-3 bg-white border border-gray-300 rounded-lg px-4 py-3 hover:bg-gray-50 transition-all duration-200"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>

Google
</motion.button>
Comment on lines +159 to +160
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete social login implementation. The button lacks an icon and is the only option in a grid-cols-2 layout, leaving empty space. Either add a second OAuth provider or change to grid-cols-1.

Copilot uses AI. Check for mistakes.

</motion.div>

{/* Sign Up Link */}
<motion.div
className="text-center mt-8"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.2, duration: 0.6 }}
>
<p className="text-gray-600">
Don't have an account?{' '}
<Link href="/signup" className="text-orange-500 hover:text-orange-600 font-medium transition-colors">
Sign up
</Link>
</p>
</motion.div>
</div>
</motion.div>
</motion.div>
</div>
);
}
Loading