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
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { useEffect, useState } from 'react'

import tubeImg from '../../../../../assets/tubeImg.png'
import blobImg from '../../../../../assets/blobImg.png'
import waveImg from '../../../../../assets/waveImg.png'
import dotShadowImg from '../../../../../assets/dotShadowImg.png'
import globeImg from '../../../../../assets/globeImg.png'
import linesImg from '../../../../../assets/linesImg.png'

import Card from '../../../../../shared/component/v1/Card'
import type { CardProps } from '../../../../../types/Interfaces'
import InlineLoader from '../Loader/InlineLoader'
import EmptyState from '../EmptyState'
import PageLayout from '../../../../../shared/component/v1/PageLayout'

import gsap from 'gsap'
import { ScrollTrigger, SplitText } from 'gsap/all'
import { useGSAP } from '@gsap/react'


gsap.registerPlugin(ScrollTrigger, SplitText)

const HomePage: React.FC = () => {
const [features, setFeatures] = useState<CardProps[]>([])
const [isLoading, setIsLoading] = useState(true)

// Simulate async fetch (realistic frontend pattern)
// Simulate async fetch
useEffect(() => {
const timer = setTimeout(() => {
setFeatures([
Expand Down Expand Up @@ -48,6 +53,7 @@ const HomePage: React.FC = () => {

useGSAP(() => {
const split = new SplitText('.about-para', { type: 'words' })

gsap.from(split.words, {
opacity: 0,
y: 20,
Expand All @@ -64,56 +70,67 @@ const HomePage: React.FC = () => {
}, [])

return (
<div className="relative min-h-screen w-full bg-[#1E1E1E] text-white overflow-hidden">
<div className="w-full h-screen relative">
<img
className="w-full h-full absolute top-0 left-0 object-cover object-[25%_55%]"
src={globeImg}
alt="bgImage"
/>
<div className="absolute top-0 left-0 z-20 w-full flex flex-col gap-y-75 items-center justify-center h-full">
<h1 className="font-Fontspring text-6xl mt-15">Local Mind</h1>
<p className="uppercase font-Satoshi font-bold leading-15 text-4xl w-1/2 text-center">
"Build For Speed. Designed For Intelligence."
</p>
<PageLayout
title="Local AI Chat"
subtitle="Interact with your local language models"
>
<div className="relative min-h-screen w-full bg-[#1E1E1E] text-white overflow-hidden">
{/* Hero Section */}
<div className="w-full h-screen relative">
<img
className="w-full h-full absolute top-0 left-0 object-cover object-[25%_55%]"
src={globeImg}
alt="Background globe"
/>

<div className="absolute top-0 left-0 z-20 w-full flex flex-col gap-y-10 items-center justify-center h-full">
<h1 className="font-Fontspring text-6xl mt-15">Local Mind</h1>
<p className="uppercase font-Satoshi font-bold leading-15 text-4xl w-1/2 text-center">
"Build For Speed. Designed For Intelligence."
</p>
</div>

<img src={linesImg} alt="" className="absolute w-full -bottom-15" />
</div>
<img src={linesImg} alt="" className="absolute w-full -bottom-15" />
</div>

{/* Features Section (Loading / Empty / Data) */}
<div className="py-20 px-10">
{isLoading && <InlineLoader />}
{/* Features Section */}
<div className="py-20 px-10">
{isLoading && <InlineLoader />}

{!isLoading && features.length === 0 && (
<EmptyState
title="No features found"
description="Features will appear here once available."
/>
)}
{!isLoading && features.length === 0 && (
<EmptyState
title="No features found"
description="Features will appear here once available."
/>
)}

{!isLoading && features.length > 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{features.map((feature, index) => (
<Card key={feature.title} {...feature} />
))}
</div>
)}
</div>
{!isLoading && features.length > 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{features.map((feature) => (
<Card key={feature.title} {...feature} />
))}
Comment on lines +109 to +111
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Using feature.title as a key for elements in a list can be problematic if titles are not guaranteed to be unique, which is often the case with dynamic data from an API. This can lead to incorrect rendering and state management issues in React.

A better practice is to use a stable, unique identifier for each item. Since the data is currently mocked, this is a good opportunity to add a unique id to each feature object.

Example with an id:

// In your feature data
{
  id: 'custom-models',
  title: 'Custom Models',
  desc: '...',
}

// In your component
{features.map((feature) => (
  <Card key={feature.id} {...feature} />
))}

This will make your component more robust and ready for real data.

</div>
)}
</div>

<div className="about w-full min-h-screen relative flex flex-col justify-center pt-10 font-Satoshi">
<h1 className="text-white text-center uppercase text-3xl tracking-wider font-bold">
About
</h1>
<p className="about-para w-2/3 mx-auto text-center text-xl mt-15">
LocalMind is a free, open-source platform made for students, developers, and anyone who
wants to use AI without paying expensive fees or worrying about usage limits.
</p>
<p className="about-para w-2/3 mx-auto text-center text-xl mt-15">
With LocalMind, you can run powerful AI models directly on your computer or connect to
cloud models using your own API key.
</p>
{/* About Section */}
<div className="about w-full min-h-screen relative flex flex-col justify-center pt-10 font-Satoshi">
<h1 className="text-white text-center uppercase text-3xl tracking-wider font-bold">
About
</h1>

<p className="about-para w-2/3 mx-auto text-center text-xl mt-15">
LocalMind is a free, open-source platform made for students, developers, and anyone who
wants to use AI without paying expensive fees or worrying about usage limits.
</p>

<p className="about-para w-2/3 mx-auto text-center text-xl mt-15">
With LocalMind, you can run powerful AI models directly on your computer or connect to
cloud models using your own API key.
</p>
</div>
</div>
</div>
</PageLayout>
)
}

Expand Down
4 changes: 3 additions & 1 deletion LocalMind-Frontend/src/shared/component/v1/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { NavLink } from 'react-router-dom'

const Navbar: React.FC = () => {
const navLinkClass = ({ isActive }: { isActive: boolean }) =>
isActive ? 'text-white line-through opacity-80' : 'text-white'
isActive
? 'text-blue-400 font-semibold underline underline-offset-8'
: 'text-white hover:text-blue-300 transition-colors'
Comment on lines 6 to +9
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The inline type ({ isActive }: { isActive: boolean }) is not fully correct and can be brittle. The className function on a NavLink receives an object with isActive, isPending, and isTransitioning properties.

For better type safety and maintainability, you should use the NavLinkProps type from react-router-dom.

  1. Import NavLinkProps:
    import { NavLink, type NavLinkProps } from 'react-router-dom'
  2. Use it to type your function:
    const navLinkClass: NavLinkProps['className'] = ({ isActive }) =>
      isActive
        ? 'text-blue-400 font-semibold underline underline-offset-8'
        : 'text-white hover:text-blue-300 transition-colors'

This will ensure your code is robust against future updates to react-router-dom.


return (
<div className="fixed top-2 left-1/2 z-30 flex gap-x-50 items-center justify-between -translate-x-1/2 bg-zinc-900/40 backdrop-blur-md px-5 py-2 rounded-full border border-zinc-500/50 text-white">
Expand Down
24 changes: 24 additions & 0 deletions LocalMind-Frontend/src/shared/component/v1/PageLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react";

type Props = {
title: string;
subtitle?: string;
children: React.ReactNode;
};

const PageLayout: React.FC<Props> = ({ title, subtitle, children }) => {
Comment on lines +3 to +9
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This is a great reusable component! To align it more with modern React best practices and improve clarity, I have a couple of suggestions:

  1. More Specific Prop Type Name: Rename Props to PageLayoutProps. This makes it clearer what the props are for, especially in larger files or when debugging.
  2. Avoid React.FC: The use of React.FC is discouraged in many modern codebases. It has some downsides, like implicitly providing children (which you've correctly typed explicitly, making it redundant) and not handling generic props well. A simpler function declaration is preferred.

Here's a suggested implementation:

Suggested change
type Props = {
title: string;
subtitle?: string;
children: React.ReactNode;
};
const PageLayout: React.FC<Props> = ({ title, subtitle, children }) => {
type PageLayoutProps = {
title: string;
subtitle?: string;
children: React.ReactNode;
};
const PageLayout = ({ title, subtitle, children }: PageLayoutProps) => {

return (
<div className="pt-24 px-8 max-w-7xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold text-white">{title}</h1>
{subtitle && (
<p className="text-zinc-400 mt-2">{subtitle}</p>
)}
</div>

{children}
</div>
);
};

export default PageLayout;