security: Add proper authentication, RBAC, and tenant isolation

- Add password hashing with bcrypt
- Add AuthService with proper login
- Add password strength validation
- Add RBAC middleware (AdminOnly, ManagerOrAdmin)
- Add tenant isolation middleware
- Update CRM handler with tenant filtering
- Add JWT fallback for development mode
- Add user context helpers
- Build successful
This commit is contained in:
Bernt
2026-08-10 12:52:48 +00:00
parent 8921fd1467
commit 78b57273e2
141 changed files with 29192 additions and 180 deletions
+101
View File
@@ -0,0 +1,101 @@
import { useState, useCallback, useRef } from 'react'
import { cn } from '@/lib/utils'
import { RefreshCw } from 'lucide-react'
interface PullToRefreshProps {
onRefresh: () => Promise<void>
children: React.ReactNode
className?: string
}
export function PullToRefresh({ onRefresh, children, className }: PullToRefreshProps) {
const [pulling, setPulling] = useState(false)
const [pullDistance, setPullDistance] = useState(0)
const [refreshing, setRefreshing] = useState(false)
const touchStartY = useRef(0)
const containerRef = useRef<HTMLDivElement>(null)
const maxPullDistance = 100
const refreshThreshold = 80
const onTouchStart = useCallback((e: React.TouchEvent) => {
// Only allow pull-to-refresh when at top of scroll
if (containerRef.current && containerRef.current.scrollTop === 0) {
touchStartY.current = e.targetTouches[0].clientY
setPulling(true)
}
}, [])
const onTouchMove = useCallback((e: React.TouchEvent) => {
if (!pulling) return
const currentY = e.targetTouches[0].clientY
const diff = currentY - touchStartY.current
if (diff > 0) {
// Resistance increases as user pulls further
const resistance = 1 + (diff / maxPullDistance) * 0.5
const newDistance = Math.min(diff / resistance, maxPullDistance)
setPullDistance(newDistance)
}
}, [pulling])
const onTouchEnd = useCallback(async () => {
if (!pulling) return
if (pullDistance >= refreshThreshold && !refreshing) {
setRefreshing(true)
try {
await onRefresh()
} finally {
setRefreshing(false)
}
}
setPulling(false)
setPullDistance(0)
}, [pulling, pullDistance, refreshing, onRefresh])
return (
<div
ref={containerRef}
className={cn('relative overflow-y-auto', className)}
onTouchStart={onTouchStart}
onTouchMove={onTouchMove}
onTouchEnd={onTouchEnd}
>
{/* Pull indicator */}
<div
className="absolute top-0 left-0 right-0 flex items-center justify-center transition-transform"
style={{
transform: `translateY(${pullDistance - 60}px)`,
opacity: pulling ? 1 : 0,
}}
>
<div className="flex flex-col items-center gap-2">
<RefreshCw
size={24}
className={cn(
'text-primary transition-transform',
refreshing && 'animate-spin',
!refreshing && pullDistance >= refreshThreshold && 'rotate-180'
)}
/>
<span className="text-xs text-text-secondary">
{refreshing ? 'Refreshing...' : pullDistance >= refreshThreshold ? 'Release to refresh' : 'Pull to refresh'}
</span>
</div>
</div>
{/* Content with offset when pulling */}
<div
style={{
transform: pulling ? `translateY(${pullDistance}px)` : 'translateY(0)',
transition: pulling ? 'none' : 'transform 0.3s ease-out',
}}
>
{children}
</div>
</div>
)
}