diff --git a/AGENTS.md b/AGENTS.md index 738a908..74afb49 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Стек -- Monorepo: `apps/web` + `packages/ui` (`@authportal/ui`) +- Monorepo: `apps/web` + `apps/api` (`@authportal/api`, Fastify + SQLite/better-sqlite3) + `packages/ui` (`@authportal/ui`) + `packages/db` + `packages/shared` - Vite + React + TanStack + shadcn **base-nova** + ReUI Frame - App Switcher: CFDM / vps / bgp / fw / **dns** (Technitium) - Auth: JWT fragment SSO для своих SPA **и** OIDC IdP для внешних RP (Technitium) diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js index ac1d0ec..888021f 100644 --- a/apps/web/eslint.config.js +++ b/apps/web/eslint.config.js @@ -6,7 +6,7 @@ import tseslint from 'typescript-eslint' import { defineConfig, globalIgnores } from 'eslint/config' export default defineConfig([ - globalIgnores(['dist', 'src/routeTree.gen.ts', 'src/components/blocks/**', 'src/components/reui/**']), + globalIgnores(['dist', 'src/routeTree.gen.ts', 'src/components/reui/**']), { files: ['**/*.{ts,tsx}'], extends: [ diff --git a/apps/web/src/components/blocks/auth-18/components/auth-logo.tsx b/apps/web/src/components/auth-logo.tsx similarity index 99% rename from apps/web/src/components/blocks/auth-18/components/auth-logo.tsx rename to apps/web/src/components/auth-logo.tsx index 832ecc3..2e0a510 100644 --- a/apps/web/src/components/blocks/auth-18/components/auth-logo.tsx +++ b/apps/web/src/components/auth-logo.tsx @@ -32,4 +32,4 @@ export function AuthLogo({ className }: { className?: string }) { ) -} \ No newline at end of file +} diff --git a/apps/web/src/components/blocks/auth-18/components/auth-grid-background.tsx b/apps/web/src/components/blocks/auth-18/components/auth-grid-background.tsx deleted file mode 100644 index 91f4ac8..0000000 --- a/apps/web/src/components/blocks/auth-18/components/auth-grid-background.tsx +++ /dev/null @@ -1,208 +0,0 @@ -"use client" - -import { - useCallback, - useEffect, - useId, - useRef, - useState, - type ComponentPropsWithoutRef, -} from "react" -import { motion } from "motion/react" - -import { cn } from "@authportal/ui/lib/utils" - -export interface AnimatedGridPatternProps extends ComponentPropsWithoutRef<"svg"> { - width?: number - height?: number - x?: number - y?: number - strokeDasharray?: number - numSquares?: number - maxOpacity?: number - duration?: number - repeatDelay?: number -} - -type Square = { - id: number - pos: [number, number] - iteration: number -} - -// Deterministic 0..1 hash so cell placement stays stable across renders without -// Math.random, so SSR and client agree and the no-randomness gate stays green. -function pseudoRandom(seed: number): number { - const value = Math.sin(seed * 12.9898) * 43758.5453 - return value - Math.floor(value) -} - -export function AnimatedGridPattern({ - width = 40, - height = 40, - x = -1, - y = -1, - strokeDasharray = 0, - numSquares = 30, - className, - maxOpacity = 0.1, - duration = 3, - repeatDelay = 1, - ...props -}: AnimatedGridPatternProps) { - const id = useId() - const containerRef = useRef(null) - const [dimensions, setDimensions] = useState({ width: 0, height: 0 }) - const [squares, setSquares] = useState>([]) - - const getPos = useCallback( - (seed: number): [number, number] => { - const cols = Math.max(1, Math.floor(dimensions.width / width)) - const rows = Math.max(1, Math.floor(dimensions.height / height)) - return [ - Math.floor(pseudoRandom(seed) * cols), - Math.floor(pseudoRandom(seed + 0.5) * rows), - ] - }, - [dimensions.height, dimensions.width, height, width] - ) - - const generateSquares = useCallback( - (count: number) => { - return Array.from({ length: count }, (_, index) => ({ - id: index, - pos: getPos(index + 1), - iteration: 0, - })) - }, - [getPos] - ) - - const updateSquarePosition = useCallback( - (squareId: number) => { - setSquares((currentSquares) => { - const current = currentSquares[squareId] - if (!current || current.id !== squareId) { - return currentSquares - } - - const nextSquares = currentSquares.slice() - const nextIteration = current.iteration + 1 - nextSquares[squareId] = { - ...current, - pos: getPos((squareId + 1) * 97 + nextIteration * 13), - iteration: nextIteration, - } - - return nextSquares - }) - }, - [getPos] - ) - - useEffect(() => { - if (dimensions.width && dimensions.height) { - setSquares(generateSquares(numSquares)) - } - }, [dimensions.width, dimensions.height, generateSquares, numSquares]) - - useEffect(() => { - const element = containerRef.current - if (!element) { - return - } - - const resizeObserver = new ResizeObserver((entries) => { - for (const entry of entries) { - setDimensions((currentDimensions) => { - const nextWidth = entry.contentRect.width - const nextHeight = entry.contentRect.height - - if ( - currentDimensions.width === nextWidth && - currentDimensions.height === nextHeight - ) { - return currentDimensions - } - - return { width: nextWidth, height: nextHeight } - }) - } - }) - - resizeObserver.observe(element) - - return () => { - resizeObserver.disconnect() - } - }, []) - - return ( - - ) -} - -export function AuthGridBackground() { - return ( - - ) -} \ No newline at end of file diff --git a/apps/web/src/components/blocks/auth-18/components/auth.tsx b/apps/web/src/components/blocks/auth-18/components/auth.tsx deleted file mode 100644 index d1d095c..0000000 --- a/apps/web/src/components/blocks/auth-18/components/auth.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { useState, type ComponentProps } from "react" - -import { - Avatar, - AvatarFallback, - AvatarImage, -} from "@authportal/ui/components/avatar" -import { Item, ItemMedia } from "@authportal/ui/components/item" -import { AuthGridBackground } from "./auth-grid-background" -import { AUTH18_TESTIMONIAL, AUTH18_TRUST_BRANDS } from "./data" -import { LoginForm } from "./login-form" -import { StarIcon } from "lucide-react" - -type FormSubmitHandler = NonNullable["onSubmit"]> -type FormSubmitEvent = Parameters[0] - -function SidebarBackground() { - return -} - -function Sidebar() { - return ( - - ) -} - -export function Auth() { - const [showPassword, setShowPassword] = useState(false) - - function handleSubmit(event: FormSubmitEvent) { - event.preventDefault() - } - - return ( -
- {/* Sidebar */} - - - {/* Form */} - setShowPassword((current) => !current)} - onSubmit={handleSubmit} - /> -
- ) -} \ No newline at end of file diff --git a/apps/web/src/components/blocks/auth-18/components/data.tsx b/apps/web/src/components/blocks/auth-18/components/data.tsx deleted file mode 100644 index f6cd82b..0000000 --- a/apps/web/src/components/blocks/auth-18/components/data.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import type { ReactNode } from "react" - -import { Apple } from "@authportal/ui/components/svgs/apple" -import { AppleDark } from "@authportal/ui/components/svgs/appleDark" -import { Google } from "@authportal/ui/components/svgs/google" -import { OpenaiWordmarkDark } from "@authportal/ui/components/svgs/openaiWordmarkDark" -import { OpenaiWordmarkLight } from "@authportal/ui/components/svgs/openaiWordmarkLight" -import { SlackWordmark } from "@authportal/ui/components/svgs/slackWordmark" -import { StripeWordmark } from "@authportal/ui/components/svgs/stripeWordmark" -import { SupabaseWordmarkDark } from "@authportal/ui/components/svgs/supabaseWordmarkDark" -import { SupabaseWordmarkLight } from "@authportal/ui/components/svgs/supabaseWordmarkLight" - -export type AuthProvider = { - id: string - label: string - logo: ReactNode -} - -export type TrustBrand = { - id: string - logo: ReactNode -} - -export type Testimonial = { - quote: string - name: string - role: string - company: string - avatar: string - fallback: string - stars: number -} - -function ThemeLogo({ light, dark }: { light: ReactNode; dark: ReactNode }) { - return ( - <> - - - - ) -} - -const providerLogoClassName = "size-4 shrink-0" - -export const AUTH18_PROVIDERS: AuthProvider[] = [ - { - id: "google", - label: "Google", - logo: