feat: Implement AI Chat Agent, Email Notifications, and UI enhancements
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-12-12 08:35:48 +01:00
parent ccfb674318
commit 5d8976b1cd
58 changed files with 7867 additions and 844 deletions
+97 -56
View File
@@ -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>