Skip to content
Merged
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
5 changes: 5 additions & 0 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import PatientRoutes from './routes/PatientRoutes';
import PublicRoutes from './routes/PublicRoutes';
import getDashboardPath from './utility/getDashboardPath';
import QuickmedRoutes from './routes/QuickmedRoutes';
import QuickLabRoutes from './routes/QuickLabRoutes';

// App.jsx - Updated AppInner component
function AppInner() {
Expand Down Expand Up @@ -43,9 +44,13 @@ function AppInner() {
const renderRoutes = () => {
console.log('Auth state:', { isAuthenticated, isLoading, user, pathname: location.pathname });
const quickMedPath = location.pathname.startsWith('/quick-med');
const quickLabPath = location.pathname.startsWith('/quick-lab');
if (quickMedPath) {
return <QuickmedRoutes />;
}
if (quickLabPath) {
return <QuickLabRoutes />;
}
// For authenticated users
if (isAuthenticated && user) {
switch (user.role) {
Expand Down
51 changes: 51 additions & 0 deletions client/src/components/quicklab/DesktopNavbar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// DesktopNavbar.jsx
import { Search } from 'lucide-react';
import DarkModeToggle from '../ui/DarkModeToggle';

export default function DesktopNavbar({ searchQuery, setSearchQuery }) {
return (
<nav className="hidden lg:block fixed top-0 left-0 right-0 z-50 bg-white dark:bg-black border-b border-slate-200 dark:border-slate-800 shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
{/* Logo */}
<div className="flex items-center space-x-2">
<div className="text-2xl font-bold">
<span className="text-slate-900 dark:text-slate-50">Quick</span>
<span className="text-yellow-500 dark:text-yellow-400">Labs</span>
</div>
</div>

{/* Search Bar */}
<div className="flex-1 max-w-xl mx-8">
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Search className="h-5 w-5 text-slate-500 dark:text-slate-400" />
</div>
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search labs..."
className="block w-full pl-10 pr-3 py-2 border border-slate-300 dark:border-slate-700 rounded-lg bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-slate-50 placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-yellow-500 dark:focus:ring-yellow-400 focus:border-transparent transition-all"
/>
</div>
</div>

{/* Right Side Actions */}
<div className="flex items-center space-x-4">
{/* Dark Mode Toggle */}
<DarkModeToggle />

{/* Login/Register Buttons */}
<button className="px-4 py-2 text-slate-700 dark:text-slate-300 hover:text-yellow-600 dark:hover:text-yellow-400 font-medium transition-colors">
Login
</button>
<button className="px-4 py-2 text-slate-700 dark:text-slate-300 hover:text-yellow-600 dark:hover:text-yellow-400 font-medium transition-colors">
Register
</button>
</div>
</div>
</div>
</nav>
);
}
69 changes: 69 additions & 0 deletions client/src/components/quicklab/MobileNavbar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// MobileNavbar.jsx
import { Search, Home, BookOpen, User } from 'lucide-react';
import DarkModeToggle from '../ui/DarkModeToggle';

export default function MobileNavbar({ searchQuery, setSearchQuery }) {
return (
<>
{/* Top Bar - Search Only */}
<div className="lg:hidden fixed top-0 left-0 right-0 z-50 bg-white dark:bg-black border-b border-slate-200 dark:border-slate-800 shadow-sm">
<div className="px-4 py-3">
{/* Logo */}
<div className="flex items-center space-x-3 mb-2">
<div className="text-xl font-bold whitespace-nowrap">
<span className="text-slate-900 dark:text-slate-50">Quick</span>
<span className="text-yellow-500 dark:text-yellow-400">Labs</span>
</div>
</div>

<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Search className="h-5 w-5 text-slate-500 dark:text-slate-400" />
</div>
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search labs..."
className="block w-full pl-10 pr-3 py-2 border border-slate-300 dark:border-slate-700 rounded-lg bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-slate-50 placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-yellow-500 dark:focus:ring-yellow-400 focus:border-transparent transition-all"
/>
</div>
</div>
</div>

{/* Bottom Navigation Bar */}
<div className="lg:hidden fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-black border-t border-slate-200 dark:border-slate-800 shadow-lg">
<div className="flex items-center justify-around h-16 px-2">
{/* Home/Labs */}
<button className="flex flex-col items-center justify-center w-full h-full space-y-1 text-slate-500 dark:text-slate-400 hover:text-yellow-600 dark:hover:text-yellow-400 transition-colors active:scale-95">
<Home className="h-6 w-6" />
<span className="text-xs font-medium">Home</span>
</button>

{/* Labs */}
<button className="flex flex-col items-center justify-center w-full h-full space-y-1 text-slate-500 dark:text-slate-400 hover:text-yellow-600 dark:hover:text-yellow-400 transition-colors active:scale-95">
<BookOpen className="h-6 w-6" />
<span className="text-xs font-medium">Labs</span>
</button>

{/* Dark Mode Toggle */}
<div className="flex flex-col items-center justify-center w-full h-full">
<DarkModeToggle />
<span className="text-xs font-medium text-slate-500 dark:text-slate-400 mt-1">
Theme
</span>
</div>

{/* Login/Account */}
<button className="flex flex-col items-center justify-center w-full h-full space-y-1 text-slate-500 dark:text-slate-400 hover:text-yellow-600 dark:hover:text-yellow-400 transition-colors active:scale-95">
<User className="h-6 w-6" />
<span className="text-xs font-medium">Account</span>
</button>
</div>
</div>

{/* Spacer for fixed navbars */}
<div className="lg:hidden h-28" />
</>
);
}
18 changes: 18 additions & 0 deletions client/src/components/quicklab/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Navbar.jsx
import { useState } from 'react';
import DesktopNavbar from './DesktopNavbar';
import MobileNavbar from './MobileNavbar';

export default function Navbar() {
const [searchQuery, setSearchQuery] = useState('');

return (
<>
{/* Desktop Navbar - Hidden on mobile */}
<DesktopNavbar searchQuery={searchQuery} setSearchQuery={setSearchQuery} />

{/* Mobile Navbar - Hidden on desktop */}
<MobileNavbar searchQuery={searchQuery} setSearchQuery={setSearchQuery} />
</>
);
}
79 changes: 79 additions & 0 deletions client/src/quicklab-theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// quicklab-theme.js

export const colors = {
primary: '#eab308', // yellow-500
primaryDark: '#ca8a04', // yellow-600
primaryLight: '#facc15', // yellow-400
secondary: '#1e293b', // black-800
accent: '#3b82f6', // blue-500
accentDark: '#2563eb', // blue-600
light: '#f8fafc', // slate-50
dark: '#000000', // pure black
darkSurface: '#0f172a', // slate-900
success: '#22c55e', // green (from QuickMed)
warning: '#f59e0b', // amber
danger: '#ef4444', // red
info: '#3b82f6', // blue
text: {
light: '#0f172a',
dark: '#f8fafc',
muted: {
light: '#64748b',
dark: '#94a3b8',
},
},
border: {
light: '#e2e8f0',
dark: '#1e293b',
},
};

export const spacing = {
xs: '0.25rem',
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
xl: '2rem',
'2xl': '3rem',
};

export const breakpoints = {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
};

export const shadows = {
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
yellow: '0 4px 14px 0 rgba(234, 179, 8, 0.39)', // yellow glow
blue: '0 4px 14px 0 rgba(59, 130, 246, 0.39)', // blue glow
};

export const transitions = {
fast: '150ms ease-in-out',
normal: '200ms ease-in-out',
slow: '300ms ease-in-out',
};

// Theme toggle functionality
export const initTheme = () => {
const theme = localStorage.getItem('quicklab-theme') || 'light';
document.documentElement.classList.toggle('dark', theme === 'dark');
return theme;
};

export const toggleTheme = () => {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('quicklab-theme', isDark ? 'dark' : 'light');
return isDark ? 'dark' : 'light';
};

export const setTheme = (theme) => {
document.documentElement.classList.toggle('dark', theme === 'dark');
localStorage.setItem('quicklab-theme', theme);
};
Comment on lines +64 to +79
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add SSR compatibility checks for document access.

The theme functions directly access document.documentElement and localStorage without checking if they exist. This will cause runtime errors in SSR environments (like Next.js or during server-side rendering).

Apply this diff to add environment checks:

 export const initTheme = () => {
+  if (typeof window === 'undefined') return 'light';
   const theme = localStorage.getItem('quicklab-theme') || 'light';
   document.documentElement.classList.toggle('dark', theme === 'dark');
   return theme;
 };

 export const toggleTheme = () => {
+  if (typeof window === 'undefined') return 'light';
   const isDark = document.documentElement.classList.toggle('dark');
   localStorage.setItem('quicklab-theme', isDark ? 'dark' : 'light');
   return isDark ? 'dark' : 'light';
 };

 export const setTheme = (theme) => {
+  if (typeof window === 'undefined') return;
   document.documentElement.classList.toggle('dark', theme === 'dark');
   localStorage.setItem('quicklab-theme', theme);
 };

This ensures the code won't break if the application later adopts SSR or is executed in a Node.js environment during build time.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const initTheme = () => {
const theme = localStorage.getItem('quicklab-theme') || 'light';
document.documentElement.classList.toggle('dark', theme === 'dark');
return theme;
};
export const toggleTheme = () => {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('quicklab-theme', isDark ? 'dark' : 'light');
return isDark ? 'dark' : 'light';
};
export const setTheme = (theme) => {
document.documentElement.classList.toggle('dark', theme === 'dark');
localStorage.setItem('quicklab-theme', theme);
};
export const initTheme = () => {
if (typeof window === 'undefined') return 'light';
const theme = localStorage.getItem('quicklab-theme') || 'light';
document.documentElement.classList.toggle('dark', theme === 'dark');
return theme;
};
export const toggleTheme = () => {
if (typeof window === 'undefined') return 'light';
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('quicklab-theme', isDark ? 'dark' : 'light');
return isDark ? 'dark' : 'light';
};
export const setTheme = (theme) => {
if (typeof window === 'undefined') return;
document.documentElement.classList.toggle('dark', theme === 'dark');
localStorage.setItem('quicklab-theme', theme);
};
🤖 Prompt for AI Agents
In client/src/quicklab-theme.js around lines 64 to 79, the functions access
document.documentElement and localStorage directly which breaks in SSR/node
environments; update initTheme, toggleTheme, and setTheme to first detect a
browser environment (e.g. if (typeof window === 'undefined' || typeof document
=== 'undefined')) and return sensible defaults or no-ops when absent, and guard
localStorage access similarly (or wrap in try/catch) so they only read/write
storage when available; ensure initTheme returns the chosen theme string even
when running on the server and toggleTheme/setTheme become no-ops or persist to
storage only in the browser.

135 changes: 135 additions & 0 deletions client/src/quicklab.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
@import 'tailwindcss';

/* Enable dark mode with class selector */
@custom-variant dark (&:where(.dark, .dark *));
Comment on lines +1 to +4
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check Tailwind CSS version in package.json
cat package.json | jq '.dependencies.tailwindcss, .devDependencies.tailwindcss' 2>/dev/null || cat package.json | grep -A 2 -B 2 'tailwindcss'

# Also check for tailwind.config file
echo "=== Tailwind Config Files ==="
fd -t f "tailwind.config" -x cat {} 2>/dev/null || echo "No tailwind.config file found"

Repository: genzz-dev/QuickClinic

Length of output: 228


🏁 Script executed:

#!/bin/bash
# Check for lock files
echo "=== Lock Files ==="
if [ -f "package-lock.json" ]; then
  echo "Found package-lock.json"
  grep -A 2 '"tailwindcss"' package-lock.json | head -20
elif [ -f "yarn.lock" ]; then
  echo "Found yarn.lock"
  grep -A 3 "tailwindcss" yarn.lock | head -20
elif [ -f "pnpm-lock.yaml" ]; then
  echo "Found pnpm-lock.yaml"
  grep -A 3 "tailwindcss" pnpm-lock.yaml | head -20
else
  echo "No lock file found"
fi

# Check full package.json structure
echo -e "\n=== Full package.json ==="
cat package.json

Repository: genzz-dev/QuickClinic

Length of output: 734


🏁 Script executed:

#!/bin/bash
# Check if tailwindcss appears in lock file at all
echo "=== Checking for tailwindcss in lock file ==="
if grep -q '"tailwindcss"' package-lock.json; then
  echo "Found tailwindcss in package-lock.json:"
  grep -A 10 '"tailwindcss"' package-lock.json | head -30
else
  echo "tailwindcss NOT found in package-lock.json"
fi

# Check framer-motion dependencies to see if it depends on tailwindcss
echo -e "\n=== Checking framer-motion dependencies ==="
grep -A 30 '"framer-motion"' package-lock.json | grep -E '(version|dependencies)' | head -20

Repository: genzz-dev/QuickClinic

Length of output: 276


🌐 Web query:

@custom-variant tailwindcss v4 syntax documentation

💡 Result:

Short answer — Tailwind v4 introduced the @custom-variant/@variant directive to declare custom variants in CSS. Example forms from the official docs:

  • Block form:
    @custom-variant theme-midnight {
    &:where([data-theme="midnight"] *) {
    @slot;
    }
    }
    (uses class like theme-midnight:bg-black). [1]

  • Shorthand single-rule form:
    @custom-variant theme-midnight (&:where([data-theme="midnight"] *)); [1]

  • v4/@variant examples (alternative syntax shown in v4 beta docs):
    @variant pointer-coarse (@media (pointer: coarse));
    @variant theme-midnight (&:where([data-theme="midnight"] *)); [2]

Notes: several v4 users reported large CSS/performance changes when migrating custom variants (see related GitHub discussion). [3]

Sources:
[1] Tailwind CSS — Adding custom styles / Adding custom variants (official docs).
[2] Tailwind CSS v4.0 Beta docs — Adding custom variants.
[3] tailwindlabs/tailwindcss GitHub issue “(V4) Performance issue (@custom-variant)”.


Tailwind CSS dependency is missing and v4 is required.

The project does not have tailwindcss listed in package.json. The syntax used—@import 'tailwindcss' and @custom-variant—requires Tailwind v4 or higher. Add tailwindcss v4+ to your dependencies:

"dependencies": {
  "tailwindcss": "^4.0.0"
}
🤖 Prompt for AI Agents
In client/src/quicklab.css around lines 1 to 4, the stylesheet uses Tailwind v4+
syntax (@import 'tailwindcss' and @custom-variant) but the project lacks the
tailwindcss dependency; update package.json to add "tailwindcss": "^4.0.0" (or
later), run your package manager install to fetch it, and ensure the project's
build/postcss configuration references Tailwind (e.g., include tailwind as a
PostCSS plugin or import path) so the @import and custom variant are resolved at
build time.


@theme {
/* QuickLab Light Mode Colors - Yellow/Gold Spectrum */
--color-lab-yellow-50: #fefce8;
--color-lab-yellow-100: #fef9c3;
--color-lab-yellow-200: #fef08a;
--color-lab-yellow-300: #fde047;
--color-lab-yellow-400: #facc15;
--color-lab-yellow-500: #eab308;
--color-lab-yellow-600: #ca8a04;
--color-lab-yellow-700: #a16207;
--color-lab-yellow-800: #854d0e;
--color-lab-yellow-900: #713f12;

/* QuickLab Black/Gray Spectrum */
--color-lab-black-50: #f8fafc;
--color-lab-black-100: #f1f5f9;
--color-lab-black-200: #e2e8f0;
--color-lab-black-300: #cbd5e1;
--color-lab-black-400: #94a3b8;
--color-lab-black-500: #64748b;
--color-lab-black-600: #475569;
--color-lab-black-700: #334155;
--color-lab-black-800: #1e293b;
--color-lab-black-900: #0f172a;

/* QuickLab Blue Accent */
--color-lab-blue-400: #60a5fa;
--color-lab-blue-500: #3b82f6;
--color-lab-blue-600: #2563eb;
--color-lab-blue-700: #1d4ed8;
}

/* Dark Mode Overrides */
@layer base {
.dark {
--color-lab-yellow-400: #facc15;
--color-lab-yellow-500: #facc15;
--color-lab-yellow-600: #eab308;
--color-lab-yellow-700: #ca8a04;
--color-lab-blue-500: #60a5fa;
--color-lab-blue-600: #3b82f6;
}
}

/* Custom Component Classes */
@layer components {
/* Primary Button */
.btn-quicklab-primary {
background-color: rgb(234 179 8); /* yellow-500 */
color: rgb(15 23 42); /* black-900 */
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
transition: background-color 0.2s;
font-weight: 600;
}

.dark .btn-quicklab-primary {
background-color: rgb(250 204 21); /* yellow-400 */
color: rgb(15 23 42); /* black-900 - stays dark */
}

.btn-quicklab-primary:hover {
background-color: rgb(202 138 4); /* yellow-600 */
}

.dark .btn-quicklab-primary:hover {
background-color: rgb(234 179 8); /* yellow-500 */
}

/* Secondary Button (Black) */
.btn-quicklab-secondary {
background-color: rgb(30 41 59); /* black-800 */
color: rgb(250 204 21); /* yellow-400 */
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
transition: all 0.2s;
font-weight: 600;
border: 2px solid rgb(234 179 8); /* yellow-500 */
}

.dark .btn-quicklab-secondary {
background-color: rgb(248 250 252); /* light in dark mode */
color: rgb(30 41 59); /* dark text */
border-color: rgb(250 204 21); /* yellow-400 */
}

.btn-quicklab-secondary:hover {
background-color: rgb(15 23 42); /* black-900 */
border-color: rgb(234 179 8); /* yellow-500 */
}

/* Card Component */
.card-quicklab {
background-color: rgb(248 250 252); /* surface light */
border: 1px solid rgb(226 232 240); /* border light */
border-radius: 0.75rem;
padding: 1.5rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
transition: all 0.2s;
}

.dark .card-quicklab {
background-color: rgb(15 23 42); /* surface dark */
border-color: rgb(30 41 59); /* border dark */
}

.card-quicklab:hover {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
border-color: rgb(234 179 8); /* yellow-500 */
}

.dark .card-quicklab:hover {
border-color: rgb(250 204 21); /* yellow-400 */
}

/* Badge Component */
.badge-quicklab {
background-color: rgb(254 249 195); /* yellow-100 */
color: rgb(133 77 14); /* yellow-800 */
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 600;
}

.dark .badge-quicklab {
background-color: rgb(113 63 18); /* yellow-900 */
color: rgb(253 224 71); /* yellow-300 */
}
}
13 changes: 13 additions & 0 deletions client/src/routes/QuickLabRoutes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Routes, Route } from 'react-router-dom';
import NotFoundPage from '../components/ui/NotFoundPage';
import Navbar from '../components/quicklab/Navbar';
export default function QuickLabRoutes() {
return (
<>
<Navbar />
<Routes>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</>
);
}
Loading