feat: Implement AI Chat Agent, Email Notifications, and UI enhancements
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useLocation } from "wouter";
|
||||
import { useLocation, Link } from "wouter";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { insertUserSchema, InsertUser, loginSchema, registerSchema, LoginUser } from "@shared/schema";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@/lib/queryClient";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -22,12 +24,15 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { BrainCircuit } from "lucide-react";
|
||||
|
||||
export default function AuthPage() {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const queryClient = useQueryClient();
|
||||
const [activeTab, setActiveTab] = useState("login");
|
||||
|
||||
const { data: settings } = useQuery<{ registration_enabled: boolean }>({
|
||||
queryKey: ["/api/settings/public"],
|
||||
@@ -92,10 +97,9 @@ export default function AuthPage() {
|
||||
<div className="bg-white/10 p-4 rounded-2xl inline-block mb-4 backdrop-blur-sm">
|
||||
<BrainCircuit className="w-16 h-16 text-primary-foreground" />
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold tracking-tight">TaskFlow</h1>
|
||||
<h1 className="text-4xl font-bold tracking-tight">{t('auth.heroTitle')}</h1>
|
||||
<p className="text-lg text-zinc-400">
|
||||
Master your productivity with AI-driven task management, gamified
|
||||
achievements, and intelligent focus modes.
|
||||
{t('auth.heroSubtitle')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,52 +110,61 @@ export default function AuthPage() {
|
||||
<div className="lg:hidden mx-auto bg-primary/10 p-3 rounded-xl w-fit mb-2">
|
||||
<BrainCircuit className="w-8 h-8 text-primary" />
|
||||
</div>
|
||||
<CardTitle className="text-2xl font-bold">Welcome Back</CardTitle>
|
||||
<CardTitle className="text-2xl font-bold">{t('auth.welcomeBack')}</CardTitle>
|
||||
<CardDescription>
|
||||
Sign in to your account
|
||||
{settings?.registration_enabled && " or create a new one"} to get started
|
||||
{settings?.registration_enabled
|
||||
? t('auth.signInDesc')
|
||||
: t('auth.signInDescNoReg')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="login" className="space-y-6">
|
||||
<TabsList className={`grid w-full ${settings?.registration_enabled ? 'grid-cols-2' : 'grid-cols-1'}`}>
|
||||
<TabsTrigger value="login">Login</TabsTrigger>
|
||||
{settings?.registration_enabled && (
|
||||
<TabsTrigger value="register">Register</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="login">
|
||||
<AuthForm
|
||||
mode="login"
|
||||
onSubmit={(data) => loginMutation.mutate(data)}
|
||||
isLoading={loginMutation.isPending}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<div className="flex w-full mb-6 bg-zinc-100 dark:bg-zinc-800 p-1 rounded-lg">
|
||||
<button
|
||||
onClick={() => setActiveTab("login")}
|
||||
className={`flex-1 py-2 text-sm font-medium rounded-md transition-all ${activeTab === "login"
|
||||
? "bg-white dark:bg-zinc-950 shadow-sm text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
{t('auth.login')}
|
||||
</button>
|
||||
{settings?.registration_enabled && (
|
||||
<TabsContent value="register">
|
||||
<AuthForm
|
||||
mode="register"
|
||||
onSubmit={(data) => {
|
||||
registerMutation.mutate(data as InsertUser, {
|
||||
onError: (error) => {
|
||||
// We can't access form here directly easily without refactoring,
|
||||
// but we can pass a callback or handle it in AuthForm if we passed mutation there.
|
||||
// However, simpler is to catch it here if we want global toast.
|
||||
// The Requirements say "indicate failure".
|
||||
// To set FIELD errors, we must be inside the form submit context or have access to form methods.
|
||||
// Let's refactor AuthForm to handle the mutation itself or return the error?
|
||||
// Actually, simpler: pass the mutation TO AuthForm so it can handle onError.
|
||||
}
|
||||
})
|
||||
}}
|
||||
isLoading={registerMutation.isPending}
|
||||
registerMutation={registerMutation} // Pass mutation to handle errors inside
|
||||
/>
|
||||
</TabsContent>
|
||||
<button
|
||||
onClick={() => setActiveTab("register")}
|
||||
className={`flex-1 py-2 text-sm font-medium rounded-md transition-all ${activeTab === "register"
|
||||
? "bg-white dark:bg-zinc-950 shadow-sm text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
{t('auth.register')}
|
||||
</button>
|
||||
)}
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
{activeTab === "login" ? (
|
||||
<AuthForm
|
||||
mode="login"
|
||||
onSubmit={(data) => loginMutation.mutate(data)}
|
||||
isLoading={loginMutation.isPending}
|
||||
/>
|
||||
) : (
|
||||
settings?.registration_enabled ? (
|
||||
<AuthForm
|
||||
mode="register"
|
||||
onSubmit={(data) => {
|
||||
registerMutation.mutate(data as InsertUser, {
|
||||
onError: (error) => {
|
||||
// Handled in form
|
||||
}
|
||||
})
|
||||
}}
|
||||
isLoading={registerMutation.isPending}
|
||||
registerMutation={registerMutation}
|
||||
/>
|
||||
) : (
|
||||
<div className="p-4 text-center text-muted-foreground">{t('auth.registrationDisabled')}</div>
|
||||
)
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -170,8 +183,9 @@ function AuthForm({
|
||||
isLoading: boolean;
|
||||
registerMutation?: any; // Type accurately if possible, but 'any' for quick fix avoids generic complexities
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const form = useForm<InsertUser>({
|
||||
const form = useForm<any>({
|
||||
resolver: zodResolver(mode === "login" ? loginSchema : registerSchema),
|
||||
defaultValues: {
|
||||
username: "",
|
||||
@@ -212,9 +226,9 @@ function AuthForm({
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{mode === 'login' ? 'Username or Email' : 'Username'}</FormLabel>
|
||||
<FormLabel>{mode === 'login' ? t('auth.usernameOrEmail') : t('auth.username')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={mode === 'login' ? "Enter username or email" : "Choose a username"} {...field} />
|
||||
<Input placeholder={mode === 'login' ? t('auth.enterUsernameOrEmail') : t('auth.chooseUsername')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -227,9 +241,9 @@ function AuthForm({
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormLabel>{t('auth.email')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="email" placeholder="Enter your email" {...field} />
|
||||
<Input type="email" placeholder={t('auth.enterEmail')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -242,11 +256,11 @@ function AuthForm({
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormLabel>{t('auth.password')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Enter your password"
|
||||
placeholder={t('auth.enterPassword')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -254,14 +268,41 @@ function AuthForm({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{mode === "login" && (
|
||||
<div className="flex items-center justify-between">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="rememberMe"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center space-x-2 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal cursor-pointer">
|
||||
{t("auth.rememberMe")}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Link href="/forgot-password">
|
||||
<Button variant="link" className="px-0 font-normal mt-0 h-auto text-muted-foreground hover:text-primary" type="button">
|
||||
{t("auth.forgotPassword")}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<Button className="w-full" type="submit" disabled={isLoading}>
|
||||
{isLoading
|
||||
? mode === "login"
|
||||
? "Logging in..."
|
||||
: "Creating account..."
|
||||
? t('auth.loggingIn')
|
||||
: t('auth.creatingAccount')
|
||||
: mode === "login"
|
||||
? "Sign In"
|
||||
: "Create Account"}
|
||||
? t('auth.signIn')
|
||||
: t('auth.createAccount')}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
Reference in New Issue
Block a user