Added programatic dot pattern instead of background image

This commit is contained in:
Lucas Oliveira 2025-09-24 11:41:49 -03:00
parent 391a53af76
commit 004c955fba
5 changed files with 353 additions and 178 deletions

View file

@ -10,9 +10,13 @@
"cssVariables": true, "cssVariables": true,
"prefix": "" "prefix": ""
}, },
"iconLibrary": "lucide",
"aliases": { "aliases": {
"components": "components", "components": "components",
"utils": "lib/utils", "utils": "lib/utils",
"ui": "components/ui" "ui": "components/ui"
},
"registries": {
"@magicui": "https://magicui.design/r/{name}.json"
} }
} }

View file

@ -0,0 +1,158 @@
"use client";
import { motion } from "motion/react";
import type React from "react";
import { useEffect, useId, useRef, useState } from "react";
import { cn } from "@/lib/utils";
/**
* DotPattern Component Props
*
* @param {number} [width=16] - The horizontal spacing between dots
* @param {number} [height=16] - The vertical spacing between dots
* @param {number} [x=0] - The x-offset of the entire pattern
* @param {number} [y=0] - The y-offset of the entire pattern
* @param {number} [cx=1] - The x-offset of individual dots
* @param {number} [cy=1] - The y-offset of individual dots
* @param {number} [cr=1] - The radius of each dot
* @param {string} [className] - Additional CSS classes to apply to the SVG container
* @param {boolean} [glow=false] - Whether dots should have a glowing animation effect
*/
interface DotPatternProps extends React.SVGProps<SVGSVGElement> {
width?: number;
height?: number;
x?: number;
y?: number;
cx?: number;
cy?: number;
cr?: number;
className?: string;
glow?: boolean;
[key: string]: unknown;
}
/**
* DotPattern Component
*
* A React component that creates an animated or static dot pattern background using SVG.
* The pattern automatically adjusts to fill its container and can optionally display glowing dots.
*
* @component
*
* @see DotPatternProps for the props interface.
*
* @example
* // Basic usage
* <DotPattern />
*
* // With glowing effect and custom spacing
* <DotPattern
* width={20}
* height={20}
* glow={true}
* className="opacity-50"
* />
*
* @notes
* - The component is client-side only ("use client")
* - Automatically responds to container size changes
* - When glow is enabled, dots will animate with random delays and durations
* - Uses Motion for animations
* - Dots color can be controlled via the text color utility classes
*/
export function DotPattern({
width = 16,
height = 16,
x = 0,
y = 0,
cx = 1,
cy = 1,
cr = 1,
className,
glow = false,
...props
}: DotPatternProps) {
const id = useId();
const containerRef = useRef<SVGSVGElement>(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
useEffect(() => {
const updateDimensions = () => {
if (containerRef.current) {
const { width, height } = containerRef.current.getBoundingClientRect();
setDimensions({ width, height });
}
};
updateDimensions();
window.addEventListener("resize", updateDimensions);
return () => window.removeEventListener("resize", updateDimensions);
}, []);
const dots = Array.from(
{
length:
Math.ceil(dimensions.width / width) *
Math.ceil(dimensions.height / height),
},
(_, i) => {
const col = i % Math.ceil(dimensions.width / width);
const row = Math.floor(i / Math.ceil(dimensions.width / width));
return {
x: col * width + cx,
y: row * height + cy,
delay: Math.random() * 5,
duration: Math.random() * 3 + 2,
};
},
);
return (
<svg
ref={containerRef}
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 h-full w-full text-neutral-400/80",
className,
)}
{...props}
>
<defs>
<radialGradient id={`${id}-gradient`}>
<stop offset="0%" stopColor="currentColor" stopOpacity="1" />
<stop offset="100%" stopColor="currentColor" stopOpacity="0" />
</radialGradient>
</defs>
{dots.map((dot, index) => (
<motion.circle
key={`${dot.x}-${dot.y}`}
cx={dot.x}
cy={dot.y}
r={cr}
fill={glow ? `url(#${id}-gradient)` : "currentColor"}
initial={glow ? { opacity: 0.4, scale: 1 } : {}}
animate={
glow
? {
opacity: [0.4, 1, 0.4],
scale: [1, 1.5, 1],
}
: {}
}
transition={
glow
? {
duration: dot.duration,
repeat: Infinity,
repeatType: "reverse",
delay: dot.delay,
ease: "easeInOut",
}
: {}
}
/>
))}
</svg>
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

View file

@ -6,7 +6,9 @@ import { Suspense, useEffect } from "react";
import GoogleLogo from "@/components/logo/google-logo"; import GoogleLogo from "@/components/logo/google-logo";
import Logo from "@/components/logo/logo"; import Logo from "@/components/logo/logo";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { DotPattern } from "@/components/ui/dot-pattern";
import { useAuth } from "@/contexts/auth-context"; import { useAuth } from "@/contexts/auth-context";
import { cn } from "@/lib/utils";
import { useGetSettingsQuery } from "../api/queries/useGetSettingsQuery"; import { useGetSettingsQuery } from "../api/queries/useGetSettingsQuery";
function LoginPageContent() { function LoginPageContent() {
@ -53,15 +55,19 @@ function LoginPageContent() {
} }
return ( return (
<div <div className="min-h-dvh relative flex gap-4 flex-col items-center justify-center bg-background p-4">
className="min-h-dvh relative flex gap-4 flex-col items-center justify-center bg-background p-4" <DotPattern
style={{ width={24}
backgroundImage: "url('/images/background.png')", height={24}
backgroundSize: "cover", cx={1}
backgroundPosition: "center", cy={1}
}} cr={1}
> className={cn(
<div className="flex flex-col items-center justify-center gap-4"> "[mask-image:linear-gradient(to_bottom,white,transparent,transparent)]",
"text-input/70",
)}
/>
<div className="flex flex-col items-center justify-center gap-4 z-10">
<Logo className="fill-primary" width={32} height={28} /> <Logo className="fill-primary" width={32} height={28} />
<h1 className="text-2xl font-medium font-chivo">Welcome to OpenRAG</h1> <h1 className="text-2xl font-medium font-chivo">Welcome to OpenRAG</h1>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
@ -72,7 +78,7 @@ function LoginPageContent() {
Continue with Google Continue with Google
</Button> </Button>
</div> </div>
<div className="flex items-center justify-center gap-2 absolute bottom-6 text-xs text-muted-foreground"> <div className="flex items-center justify-center gap-2 absolute bottom-6 text-xs text-muted-foreground z-10">
<p className="text-accent-emerald-foreground">Systems Operational</p> <p className="text-accent-emerald-foreground">Systems Operational</p>
<p>Privacy Policy</p> <p>Privacy Policy</p>
</div> </div>

View file

@ -4,8 +4,8 @@ import { useRouter } from "next/navigation";
import { Suspense, useEffect, useState } from "react"; import { Suspense, useEffect, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { import {
type OnboardingVariables, type OnboardingVariables,
useOnboardingMutation, useOnboardingMutation,
} from "@/app/api/mutations/useOnboardingMutation"; } from "@/app/api/mutations/useOnboardingMutation";
import IBMLogo from "@/components/logo/ibm-logo"; import IBMLogo from "@/components/logo/ibm-logo";
import OllamaLogo from "@/components/logo/ollama-logo"; import OllamaLogo from "@/components/logo/ollama-logo";
@ -13,198 +13,205 @@ import OpenAILogo from "@/components/logo/openai-logo";
import { ProtectedRoute } from "@/components/protected-route"; import { ProtectedRoute } from "@/components/protected-route";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Card, Card,
CardContent, CardContent,
CardFooter, CardFooter,
CardHeader, CardHeader,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { DotPattern } from "@/components/ui/dot-pattern";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { useGetSettingsQuery } from "../api/queries/useGetSettingsQuery"; import { useGetSettingsQuery } from "../api/queries/useGetSettingsQuery";
import { IBMOnboarding } from "./components/ibm-onboarding"; import { IBMOnboarding } from "./components/ibm-onboarding";
import { OllamaOnboarding } from "./components/ollama-onboarding"; import { OllamaOnboarding } from "./components/ollama-onboarding";
import { OpenAIOnboarding } from "./components/openai-onboarding"; import { OpenAIOnboarding } from "./components/openai-onboarding";
function OnboardingPage() { function OnboardingPage() {
const { data: settingsDb, isLoading: isSettingsLoading } = const { data: settingsDb, isLoading: isSettingsLoading } =
useGetSettingsQuery(); useGetSettingsQuery();
const redirect = "/"; const redirect = "/";
const router = useRouter(); const router = useRouter();
// Redirect if already authenticated or in no-auth mode // Redirect if already authenticated or in no-auth mode
useEffect(() => { useEffect(() => {
if (!isSettingsLoading && settingsDb && settingsDb.edited) { if (!isSettingsLoading && settingsDb && settingsDb.edited) {
router.push(redirect); router.push(redirect);
} }
}, [isSettingsLoading, settingsDb, router]); }, [isSettingsLoading, settingsDb, router]);
const [modelProvider, setModelProvider] = useState<string>("openai"); const [modelProvider, setModelProvider] = useState<string>("openai");
const [sampleDataset, setSampleDataset] = useState<boolean>(true); const [sampleDataset, setSampleDataset] = useState<boolean>(true);
const handleSetModelProvider = (provider: string) => { const handleSetModelProvider = (provider: string) => {
setModelProvider(provider); setModelProvider(provider);
setSettings({ setSettings({
model_provider: provider, model_provider: provider,
embedding_model: "", embedding_model: "",
llm_model: "", llm_model: "",
}); });
}; };
const [settings, setSettings] = useState<OnboardingVariables>({ const [settings, setSettings] = useState<OnboardingVariables>({
model_provider: modelProvider, model_provider: modelProvider,
embedding_model: "", embedding_model: "",
llm_model: "", llm_model: "",
}); });
// Mutations // Mutations
const onboardingMutation = useOnboardingMutation({ const onboardingMutation = useOnboardingMutation({
onSuccess: (data) => { onSuccess: (data) => {
toast.success("Onboarding completed successfully!"); toast.success("Onboarding completed successfully!");
console.log("Onboarding completed successfully", data); console.log("Onboarding completed successfully", data);
router.push(redirect); router.push(redirect);
}, },
onError: (error) => { onError: (error) => {
toast.error("Failed to complete onboarding", { toast.error("Failed to complete onboarding", {
description: error.message, description: error.message,
}); });
}, },
}); });
const handleComplete = () => { const handleComplete = () => {
if ( if (
!settings.model_provider || !settings.model_provider ||
!settings.llm_model || !settings.llm_model ||
!settings.embedding_model !settings.embedding_model
) { ) {
toast.error("Please complete all required fields"); toast.error("Please complete all required fields");
return; return;
} }
// Prepare onboarding data // Prepare onboarding data
const onboardingData: OnboardingVariables = { const onboardingData: OnboardingVariables = {
model_provider: settings.model_provider, model_provider: settings.model_provider,
llm_model: settings.llm_model, llm_model: settings.llm_model,
embedding_model: settings.embedding_model, embedding_model: settings.embedding_model,
sample_data: sampleDataset, sample_data: sampleDataset,
}; };
// Add API key if available // Add API key if available
if (settings.api_key) { if (settings.api_key) {
onboardingData.api_key = settings.api_key; onboardingData.api_key = settings.api_key;
} }
// Add endpoint if available // Add endpoint if available
if (settings.endpoint) { if (settings.endpoint) {
onboardingData.endpoint = settings.endpoint; onboardingData.endpoint = settings.endpoint;
} }
// Add project_id if available // Add project_id if available
if (settings.project_id) { if (settings.project_id) {
onboardingData.project_id = settings.project_id; onboardingData.project_id = settings.project_id;
} }
onboardingMutation.mutate(onboardingData); onboardingMutation.mutate(onboardingData);
}; };
const isComplete = !!settings.llm_model && !!settings.embedding_model; const isComplete = !!settings.llm_model && !!settings.embedding_model;
return ( return (
<div <div className="min-h-dvh w-full flex gap-5 flex-col items-center justify-center bg-background relative p-4">
className="min-h-dvh w-full flex gap-5 flex-col items-center justify-center bg-background p-4" <DotPattern
style={{ width={24}
backgroundImage: "url('/images/background.png')", height={24}
backgroundSize: "cover", cx={1}
backgroundPosition: "center", cy={1}
}} cr={1}
> className={cn(
<div className="flex flex-col items-center gap-5 min-h-[550px] w-full"> "[mask-image:linear-gradient(to_bottom,white,transparent,transparent)]",
<div className="flex flex-col items-center justify-center gap-4"> "text-input/70",
<h1 className="text-2xl font-medium font-chivo"> )}
Configure your models />
</h1>
<p className="text-sm text-muted-foreground">[description of task]</p> <div className="flex flex-col items-center gap-5 min-h-[550px] w-full z-10">
</div> <div className="flex flex-col items-center justify-center gap-4">
<Card className="w-full max-w-[580px]"> <h1 className="text-2xl font-medium font-chivo">
<Tabs Configure your models
defaultValue={modelProvider} </h1>
onValueChange={handleSetModelProvider} <p className="text-sm text-muted-foreground">[description of task]</p>
> </div>
<CardHeader> <Card className="w-full max-w-[580px]">
<TabsList> <Tabs
<TabsTrigger value="openai"> defaultValue={modelProvider}
<OpenAILogo className="w-4 h-4" /> onValueChange={handleSetModelProvider}
OpenAI >
</TabsTrigger> <CardHeader>
<TabsTrigger value="watsonx"> <TabsList>
<IBMLogo className="w-4 h-4" /> <TabsTrigger value="openai">
IBM <OpenAILogo className="w-4 h-4" />
</TabsTrigger> OpenAI
<TabsTrigger value="ollama"> </TabsTrigger>
<OllamaLogo className="w-4 h-4" /> <TabsTrigger value="watsonx">
Ollama <IBMLogo className="w-4 h-4" />
</TabsTrigger> IBM
</TabsList> </TabsTrigger>
</CardHeader> <TabsTrigger value="ollama">
<CardContent> <OllamaLogo className="w-4 h-4" />
<TabsContent value="openai"> Ollama
<OpenAIOnboarding </TabsTrigger>
setSettings={setSettings} </TabsList>
sampleDataset={sampleDataset} </CardHeader>
setSampleDataset={setSampleDataset} <CardContent>
/> <TabsContent value="openai">
</TabsContent> <OpenAIOnboarding
<TabsContent value="watsonx"> setSettings={setSettings}
<IBMOnboarding sampleDataset={sampleDataset}
setSettings={setSettings} setSampleDataset={setSampleDataset}
sampleDataset={sampleDataset} />
setSampleDataset={setSampleDataset} </TabsContent>
/> <TabsContent value="watsonx">
</TabsContent> <IBMOnboarding
<TabsContent value="ollama"> setSettings={setSettings}
<OllamaOnboarding sampleDataset={sampleDataset}
setSettings={setSettings} setSampleDataset={setSampleDataset}
sampleDataset={sampleDataset} />
setSampleDataset={setSampleDataset} </TabsContent>
/> <TabsContent value="ollama">
</TabsContent> <OllamaOnboarding
</CardContent> setSettings={setSettings}
</Tabs> sampleDataset={sampleDataset}
<CardFooter className="flex justify-end"> setSampleDataset={setSampleDataset}
<Tooltip> />
<TooltipTrigger asChild> </TabsContent>
<Button </CardContent>
size="sm" </Tabs>
onClick={handleComplete} <CardFooter className="flex justify-end">
disabled={!isComplete} <Tooltip>
loading={onboardingMutation.isPending} <TooltipTrigger asChild>
> <Button
Complete size="sm"
</Button> onClick={handleComplete}
</TooltipTrigger> disabled={!isComplete}
<TooltipContent> loading={onboardingMutation.isPending}
{!isComplete ? "Please fill in all required fields" : ""} >
</TooltipContent> Complete
</Tooltip> </Button>
</CardFooter> </TooltipTrigger>
</Card> <TooltipContent>
</div> {!isComplete ? "Please fill in all required fields" : ""}
</div> </TooltipContent>
); </Tooltip>
</CardFooter>
</Card>
</div>
</div>
);
} }
export default function ProtectedOnboardingPage() { export default function ProtectedOnboardingPage() {
return ( return (
<ProtectedRoute> <ProtectedRoute>
<Suspense fallback={<div>Loading onboarding...</div>}> <Suspense fallback={<div>Loading onboarding...</div>}>
<OnboardingPage /> <OnboardingPage />
</Suspense> </Suspense>
</ProtectedRoute> </ProtectedRoute>
); );
} }