Skip to content

Commit

Permalink
refactor: Update landing page testimonials styling
Browse files Browse the repository at this point in the history
  • Loading branch information
doziestar committed Jul 4, 2024
1 parent c1c9852 commit ec35d5a
Show file tree
Hide file tree
Showing 10 changed files with 421 additions and 5 deletions.
5 changes: 5 additions & 0 deletions web/app/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import AuthPage from "@/components/AuthPage";

export default function Auth() {
return <AuthPage />;
}
12 changes: 12 additions & 0 deletions web/components/AuthPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";
import AuthForm from "@/components/forms/AuthForm";

const AuthPage: React.FC = () => {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-primary/20 to-secondary/20">
<AuthForm />
</div>
);
};

export default AuthPage;
189 changes: 189 additions & 0 deletions web/components/forms/AuthForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
"use client";

import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { useRouter } from "next/navigation";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { AlertCircle, Mail, Lock, User, ArrowRight } from "lucide-react";

const SleekAuthForm: React.FC = () => {
const [isSignUp, setIsSignUp] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [name, setName] = useState("");
const [error, setError] = useState("");
const router = useRouter();

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");

setError("Invalid email or password");
};

const toggleAuthMode = () => {
setIsSignUp(!isSignUp);
setError("");
};

const formVariants = {
hidden: { opacity: 0, y: 50 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.6, ease: "easeOut" },
},
exit: { opacity: 0, y: -50, transition: { duration: 0.4, ease: "easeIn" } },
};

const inputVariants = {
hidden: { opacity: 0, x: -50 },
visible: { opacity: 1, x: 0, transition: { duration: 0.5 } },
};

return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900">
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5 }}
className="w-full max-w-md relative"
>
<AnimatePresence mode="wait">
<motion.form
key={isSignUp ? "signup" : "signin"}
variants={formVariants}
initial="hidden"
animate="visible"
exit="exit"
onSubmit={handleSubmit}
className="bg-gray-800/30 backdrop-blur-xl p-8 rounded-lg shadow-xl border border-gray-700 overflow-hidden"
>
<h2 className="text-3xl font-bold mb-2 text-white">
{isSignUp ? "Create Account" : "Welcome Back"}
</h2>
<p className="text-gray-300 mb-6">
{isSignUp ? "Sign up to get started" : "Sign in to your account"}
</p>

<div className="space-y-4">
<AnimatePresence>
{isSignUp && (
<motion.div
variants={inputVariants}
initial="hidden"
animate="visible"
exit="hidden"
>
<Label htmlFor="name" className="text-gray-300 block mb-1">
Name
</Label>
<div className="relative">
<Input
id="name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
className="w-full pl-10 pr-3 py-2 bg-gray-700/50 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition duration-200"
placeholder="John Doe"
/>
<User
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
size={18}
/>
</div>
</motion.div>
)}
</AnimatePresence>

<motion.div variants={inputVariants}>
<Label htmlFor="email" className="text-gray-300 block mb-1">
Email
</Label>
<div className="relative">
<Input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full pl-10 pr-3 py-2 bg-gray-700/50 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition duration-200"
placeholder="john@example.com"
/>
<Mail
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
size={18}
/>
</div>
</motion.div>

<motion.div variants={inputVariants}>
<Label htmlFor="password" className="text-gray-300 block mb-1">
Password
</Label>
<div className="relative">
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="w-full pl-10 pr-3 py-2 bg-gray-700/50 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition duration-200"
placeholder="••••••••"
/>
<Lock
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
size={18}
/>
</div>
</motion.div>
</div>

<AnimatePresence>
{error && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="mt-4 p-2 bg-red-500/10 border border-red-500/50 rounded-md flex items-center text-red-400"
>
<AlertCircle size={18} className="mr-2" />
{error}
</motion.div>
)}
</AnimatePresence>

<motion.div whileHover={{ scale: 1.03 }} whileTap={{ scale: 0.98 }}>
<Button
type="submit"
className="w-full mt-6 bg-gradient-to-r from-purple-600 to-blue-600 text-white py-2 rounded-md hover:from-purple-700 hover:to-blue-700 transition duration-300 flex items-center justify-center group"
>
{isSignUp ? "Sign Up" : "Sign In"}
<ArrowRight
className="ml-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
size={18}
/>
</Button>
</motion.div>

<p className="mt-4 text-center text-gray-400">
{isSignUp ? "Already have an account?" : "Don't have an account?"}{" "}
<Button
variant="link"
onClick={toggleAuthMode}
className="text-purple-400 hover:text-purple-300 transition duration-200"
>
{isSignUp ? "Sign In" : "Sign Up"}
</Button>
</p>
</motion.form>
</AnimatePresence>
</motion.div>
</div>
);
};

export default SleekAuthForm;
4 changes: 2 additions & 2 deletions web/components/landing-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function LandingPage() {
</motion.p>
<motion.div variants={fadeIn} className="flex space-x-4">
<Link
href="#"
href="/auth"
className="inline-flex h-10 items-center justify-center rounded-md bg-primary px-6 py-2 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"
prefetch={false}
>
Expand Down Expand Up @@ -487,7 +487,7 @@ chart.save("sales_forecast.png")`}
{section.links.map((link, linkIndex) => (
<li key={linkIndex}>
<Link
href="#"
href="https://github.com/doziestar/datavinci"
className="text-muted-foreground hover:text-primary"
>
{link}
Expand Down
56 changes: 56 additions & 0 deletions web/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ 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 }
25 changes: 25 additions & 0 deletions web/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from "react"

import { cn } from "@/lib/utils"

export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"

export { Input }
26 changes: 26 additions & 0 deletions web/components/ui/label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client"

import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } 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<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName

export { Label }
4 changes: 4 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
},
"dependencies": {
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@tailwindcss/forms": "^0.5.7",
"@tauri-apps/api": "^1.5.6",
"@types/three": "^0.166.0",
"class-variance-authority": "^0.7.0",
Expand All @@ -22,6 +25,7 @@
"recharts": "^2.12.7",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"tailwindcss-gradients": "^3.0.0",
"three": "^0.166.1"
},
"devDependencies": {
Expand Down
15 changes: 14 additions & 1 deletion web/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const config: Config = {
animation: {
glow: "glow 3s linear infinite",
},
backdropFilter: {
none: "none",
blur: "blur(20px)",
},
},
colors: {
border: "hsl(var(--border))",
Expand Down Expand Up @@ -78,7 +82,16 @@ const config: Config = {
},
},
},
variants: {
extend: {
backdropFilter: ["responsive"],
},
},
},
plugins: [require("tailwindcss-animate")],
plugins: [
require("tailwindcss-animate"),
require("tailwindcss-gradients"),
require("@tailwindcss/forms"),
],
};
export default config;
Loading

0 comments on commit ec35d5a

Please sign in to comment.