feat: Add AGENTS.md and update login with Lucide icons, add API proxy config
This commit is contained in:
parent
6a270e181c
commit
0f8ec8362b
168
AGENTS.md
Normal file
168
AGENTS.md
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
# AGENTS.md - Coding Guidelines for CLQMS Frontend
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
SvelteKit frontend for Clinical Laboratory Quality Management System (CLQMS). Uses Svelte 5 runes, TailwindCSS 4, DaisyUI, and Lucide icons.
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Production build
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Preview production build
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# Sync SvelteKit (runs automatically on install)
|
||||||
|
npm run prepare
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
**No test framework configured yet.** When adding tests:
|
||||||
|
- Use Vitest for unit tests (recommended with SvelteKit)
|
||||||
|
- Use Playwright for E2E tests
|
||||||
|
- Run single test: `vitest run src/path/to/test.js`
|
||||||
|
- Run tests in watch mode: `vitest`
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
|
||||||
|
### JavaScript/TypeScript
|
||||||
|
|
||||||
|
- **ES Modules**: Always use `import`/`export` (type: "module" in package.json)
|
||||||
|
- **Semicolons**: Use semicolons consistently
|
||||||
|
- **Quotes**: Use single quotes for strings
|
||||||
|
- **Indentation**: 2 spaces
|
||||||
|
- **Trailing commas**: Use in multi-line objects/arrays
|
||||||
|
- **JSDoc**: Document all exported functions with JSDoc comments
|
||||||
|
|
||||||
|
### Svelte Components
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script>
|
||||||
|
// 1. Imports first - group by: Svelte, $lib, external
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { auth } from '$lib/stores/auth.js';
|
||||||
|
import { login } from '$lib/api/auth.js';
|
||||||
|
import { Icon } from 'lucide-svelte';
|
||||||
|
|
||||||
|
// 2. Props (Svelte 5 runes)
|
||||||
|
let { children, data } = $props();
|
||||||
|
|
||||||
|
// 3. State
|
||||||
|
let loading = $state(false);
|
||||||
|
let error = $state('');
|
||||||
|
|
||||||
|
// 4. Derived state (if needed)
|
||||||
|
let isValid = $derived(username.length > 0);
|
||||||
|
|
||||||
|
// 5. Effects
|
||||||
|
$effect(() => {
|
||||||
|
// side effects
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. Functions
|
||||||
|
function handleSubmit() {
|
||||||
|
// implementation
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
|
||||||
|
- **Components**: PascalCase (`LoginForm.svelte`)
|
||||||
|
- **Files/Routes**: lowercase with hyphens (`+page.svelte`, `user-profile/`)
|
||||||
|
- **Variables**: camelCase (`isLoading`, `userName`)
|
||||||
|
- **Constants**: UPPER_SNAKE_CASE (`API_URL`, `STORAGE_KEY`)
|
||||||
|
- **Stores**: camelCase, descriptive (`auth`, `userStore`)
|
||||||
|
- **Event handlers**: prefix with `handle` (`handleSubmit`, `handleClick`)
|
||||||
|
|
||||||
|
### Imports Order
|
||||||
|
|
||||||
|
1. Svelte imports (`svelte`, `$app/*`)
|
||||||
|
2. $lib aliases (`$lib/stores/*`, `$lib/api/*`, `$lib/components/*`)
|
||||||
|
3. External libraries (`lucide-svelte`)
|
||||||
|
4. Relative imports (minimize these, prefer `$lib`)
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
lib/
|
||||||
|
api/ # API client and endpoints
|
||||||
|
stores/ # Svelte stores
|
||||||
|
components/ # Reusable components
|
||||||
|
assets/ # Static assets
|
||||||
|
routes/ # SvelteKit routes
|
||||||
|
(app)/ # Route groups
|
||||||
|
login/
|
||||||
|
dashboard/
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Client Patterns
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/lib/api/client.js - Base client handles auth
|
||||||
|
import { apiClient, get, post, put, del } from '$lib/api/client.js';
|
||||||
|
|
||||||
|
// src/lib/api/feature.js - Feature-specific endpoints
|
||||||
|
export async function getItem(id) {
|
||||||
|
return get(`/api/items/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createItem(data) {
|
||||||
|
return post('/api/items', data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// API errors are thrown with message
|
||||||
|
try {
|
||||||
|
const result = await api.login(username, password);
|
||||||
|
} catch (err) {
|
||||||
|
error = err.message || 'An unexpected error occurred';
|
||||||
|
console.error('Login failed:', err);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Styling with Tailwind & DaisyUI
|
||||||
|
|
||||||
|
- Use Tailwind utility classes
|
||||||
|
- DaisyUI components: `btn`, `card`, `alert`, `input`, `navbar`
|
||||||
|
- Color scheme: `primary` (emerald), `base-100`, `base-200`
|
||||||
|
- Custom colors in `app.css` with `@theme`
|
||||||
|
|
||||||
|
### Authentication Patterns
|
||||||
|
|
||||||
|
- Auth state in `$lib/stores/auth.js`
|
||||||
|
- Check auth in layout `onMount`
|
||||||
|
- Redirect to `/login` if unauthenticated
|
||||||
|
- API client auto-redirects on 401
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const API_URL = import.meta.env.VITE_API_URL || '';
|
||||||
|
```
|
||||||
|
|
||||||
|
### LocalStorage
|
||||||
|
|
||||||
|
- Only access in browser: check `browser` from `$app/environment`
|
||||||
|
- Use descriptive keys: `clqms_username`, `clqms_remember`
|
||||||
|
|
||||||
|
## Proxy Configuration
|
||||||
|
|
||||||
|
API requests to `/api` are proxied to `http://localhost:8000` in dev.
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- No ESLint or Prettier configured yet - add if needed
|
||||||
|
- No test framework configured yet
|
||||||
|
- Uses Svelte 5 runes: `$props`, `$state`, `$derived`, `$effect`
|
||||||
|
- SvelteKit file-based routing with `+page.svelte`, `+layout.svelte`
|
||||||
11
docs/templates/dashboard.html
vendored
11
docs/templates/dashboard.html
vendored
@ -291,6 +291,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="footer footer-center p-4 bg-base-200 text-base-content border-t border-base-300">
|
||||||
|
<div class="flex items-center justify-center gap-2 text-sm">
|
||||||
|
<span class="font-semibold text-emerald-600">CLQMS</span>
|
||||||
|
<span class="text-gray-500">|</span>
|
||||||
|
<span class="text-gray-600">Clinical Laboratory Quality Management System</span>
|
||||||
|
<span class="text-gray-500">|</span>
|
||||||
|
<span class="text-gray-500">© 2026</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -20,5 +20,8 @@
|
|||||||
"svelte": "^5.49.2",
|
"svelte": "^5.49.2",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"vite": "^7.3.1"
|
"vite": "^7.3.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lucide-svelte": "^0.563.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@ -7,6 +7,10 @@ settings:
|
|||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
|
dependencies:
|
||||||
|
lucide-svelte:
|
||||||
|
specifier: ^0.563.0
|
||||||
|
version: 0.563.0(svelte@5.50.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@sveltejs/adapter-auto':
|
'@sveltejs/adapter-auto':
|
||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
@ -682,6 +686,11 @@ packages:
|
|||||||
locate-character@3.0.0:
|
locate-character@3.0.0:
|
||||||
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
|
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
|
||||||
|
|
||||||
|
lucide-svelte@0.563.0:
|
||||||
|
resolution: {integrity: sha512-pjZKw7TpQcamfQrx7YdbOHgmrcNeKiGGMD0tKZQaVktwSsbqw28CsKc2Q97ttwjytiCWkJyOa8ij2Q+Og0nPfQ==}
|
||||||
|
peerDependencies:
|
||||||
|
svelte: ^3 || ^4 || ^5.0.0-next.42
|
||||||
|
|
||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
@ -1261,6 +1270,10 @@ snapshots:
|
|||||||
|
|
||||||
locate-character@3.0.0: {}
|
locate-character@3.0.0: {}
|
||||||
|
|
||||||
|
lucide-svelte@0.563.0(svelte@5.50.0):
|
||||||
|
dependencies:
|
||||||
|
svelte: 5.50.0
|
||||||
|
|
||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|||||||
@ -10,8 +10,8 @@ import { post } from './client.js';
|
|||||||
* @param {string} password
|
* @param {string} password
|
||||||
* @returns {Promise<{token: string, user: Object}>}
|
* @returns {Promise<{token: string, user: Object}>}
|
||||||
*/
|
*/
|
||||||
export async function login(email, password) {
|
export async function login(username, password) {
|
||||||
return post('/api/auth/login', { email, password });
|
return post('/api/auth/login', { username, password });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { auth } from '$lib/stores/auth.js';
|
import { auth } from '$lib/stores/auth.js';
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
|
const API_URL = import.meta.env.VITE_API_URL || '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base API client with JWT handling
|
* Base API client with JWT handling
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import { auth } from '$lib/stores/auth.js';
|
import { auth } from '$lib/stores/auth.js';
|
||||||
import { login } from '$lib/api/auth.js';
|
import { login } from '$lib/api/auth.js';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { User, Lock, Eye, EyeOff, LogIn } from 'lucide-svelte';
|
||||||
|
|
||||||
let username = '';
|
let username = '';
|
||||||
let password = '';
|
let password = '';
|
||||||
@ -40,7 +41,7 @@
|
|||||||
usernameError = 'Username must be at least 3 characters';
|
usernameError = 'Username must be at least 3 characters';
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
if (!password) {
|
if (!password) {
|
||||||
passwordError = 'Password is required';
|
passwordError = 'Password is required';
|
||||||
isValid = false;
|
isValid = false;
|
||||||
@ -48,7 +49,7 @@
|
|||||||
passwordError = 'Password must be at least 6 characters';
|
passwordError = 'Password must be at least 6 characters';
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +107,7 @@
|
|||||||
<!-- Username Field -->
|
<!-- Username Field -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="input w-full input-bordered bg-white border-gray-300 flex items-center gap-2 {usernameError ? 'border-red-500' : ''}">
|
<label class="input w-full input-bordered bg-white border-gray-300 flex items-center gap-2 {usernameError ? 'border-red-500' : ''}">
|
||||||
<span class="label"><i class="fa-solid fa-user text-emerald-500"></i></span>
|
<span class="label"><User class="w-5 h-5 text-emerald-500" /></span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="username"
|
id="username"
|
||||||
@ -126,7 +127,7 @@
|
|||||||
<!-- Password Field -->
|
<!-- Password Field -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="input w-full input-bordered bg-white border-gray-300 flex items-center gap-2 {passwordError ? 'border-red-500' : ''}">
|
<label class="input w-full input-bordered bg-white border-gray-300 flex items-center gap-2 {passwordError ? 'border-red-500' : ''}">
|
||||||
<span class="label"><i class="fa-solid fa-lock text-emerald-500"></i></span>
|
<span class="label"><Lock class="w-5 h-5 text-emerald-500" /></span>
|
||||||
<input
|
<input
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showPassword ? 'text' : 'password'}
|
||||||
id="password"
|
id="password"
|
||||||
@ -138,7 +139,11 @@
|
|||||||
class="grow text-gray-900 placeholder-gray-400"
|
class="grow text-gray-900 placeholder-gray-400"
|
||||||
/>
|
/>
|
||||||
<button type="button" class="btn btn-ghost btn-sm btn-circle" on:click={togglePassword} disabled={loading}>
|
<button type="button" class="btn btn-ghost btn-sm btn-circle" on:click={togglePassword} disabled={loading}>
|
||||||
<i class="fa-solid {showPassword ? 'fa-eye-slash' : 'fa-eye'} text-emerald-500"></i>
|
{#if showPassword}
|
||||||
|
<EyeOff class="w-5 h-5 text-emerald-500" />
|
||||||
|
{:else}
|
||||||
|
<Eye class="w-5 h-5 text-emerald-500" />
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
{#if passwordError}
|
{#if passwordError}
|
||||||
@ -169,7 +174,7 @@
|
|||||||
<span class="loading loading-spinner loading-sm mr-2"></span>
|
<span class="loading loading-spinner loading-sm mr-2"></span>
|
||||||
Signing in...
|
Signing in...
|
||||||
{:else}
|
{:else}
|
||||||
<i class="fa-solid fa-right-to-bracket mr-2"></i>
|
<LogIn class="w-5 h-5 mr-2" />
|
||||||
Sign In
|
Sign In
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -3,5 +3,13 @@ import { defineConfig } from 'vite';
|
|||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [tailwindcss(), sveltekit()]
|
plugins: [tailwindcss(), sveltekit()],
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user