chore: bump dev packages, linting, logos (#521)

* chore: upgrade eslint to v9

* chore: update package dependencies in package.json and pnpm-lock.yaml

- Added `minimatch` and `serialize-javascript` dependencies with updated versions.
- Upgraded `ajv` to version 6.14.0.
- Removed outdated dependencies from pnpm-lock.yaml for better package management.

* feat: add Stripe logos to Companies section

- Introduced new company entry for Stripe in the Companies component, including both light and dark logo variants.
- Updated the Companies array to display 10 logos instead of 8.
- Adjusted TypeScript environment reference to point to the development types directory.
This commit is contained in:
Danny Avila
2026-03-02 18:18:50 -05:00
committed by GitHub
parent 28adf5e214
commit a0a74501c9
41 changed files with 635 additions and 1927 deletions

View File

@@ -1,249 +0,0 @@
const TAILWIND_CONFIG = {
extends: ['plugin:tailwindcss/recommended'],
rules: {
'tailwindcss/classnames-order': 'off', // conflicts with prettier-plugin-tailwindcss
'tailwindcss/enforces-negative-arbitrary-values': 'error',
'tailwindcss/enforces-shorthand': 'error',
'tailwindcss/migration-from-tailwind-2': 'error',
'tailwindcss/no-custom-classname': 'error',
},
}
/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
reportUnusedDisableDirectives: true,
ignorePatterns: ['next-env.d.ts'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@next/next/recommended',
'plugin:import/typescript',
'prettier',
],
overrides: [
// Rules for all files
{
files: '**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}',
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/typescript',
'prettier',
],
plugins: ['import', 'unicorn'],
rules: {
'prefer-object-has-own': 'error',
'logical-assignment-operators': ['error', 'always', { enforceForIfStatements: true }],
'no-else-return': ['error', { allowElseIf: false }],
'no-lonely-if': 'error',
'prefer-destructuring': ['error', { VariableDeclarator: { object: true } }],
'import/no-duplicates': 'error',
'no-negated-condition': 'off',
'unicorn/no-negated-condition': 'error',
'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
'object-shorthand': ['error', 'always'],
'unicorn/prefer-regexp-test': 'error',
'unicorn/no-array-for-each': 'error',
'unicorn/prefer-string-replace-all': 'error',
'@typescript-eslint/prefer-for-of': 'error',
"no-sharp-comments": "off",
"markdown/no-sharp-comments": "off",
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_' }],
// todo: enable
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
},
},
// Rules for React files
{
files: '{packages,examples,docs}/**',
extends: [
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
rules: {
'react/prop-types': 'off',
'react/no-unknown-property': ['error', { ignore: ['jsx'] }],
'react-hooks/exhaustive-deps': 'error',
'react/self-closing-comp': 'error',
'no-restricted-syntax': [
'error',
{
// ❌ useMemo(…, [])
selector:
'CallExpression[callee.name=useMemo][arguments.1.type=ArrayExpression][arguments.1.elements.length=0]',
message:
"`useMemo` with an empty dependency array can't provide a stable reference, use `useRef` instead.",
},
{
// ❌ z.object(…)
selector: 'MemberExpression[object.name=z] > .property[name=object]',
message: 'Use z.strictObject is more safe.',
},
],
'react/jsx-filename-extension': [
'error',
{ extensions: ['.tsx', '.jsx'], allow: 'as-needed' },
],
'react/jsx-curly-brace-presence': 'error',
'react/jsx-boolean-value': 'error',
},
settings: {
react: { version: 'detect' },
},
},
// Rules for TypeScript files
{
files: '**/*.{ts,tsx,cts,mts}',
extends: [
// TODO: fix errors
// 'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
parserOptions: {
project: ['tsconfig.json'],
tsconfigRootDir: __dirname
},
rules: {
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
// '@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/non-nullable-type-assertion-style': 'error',
'@typescript-eslint/prefer-optional-chain': 'error',
},
},
// ⚙️ nextra-theme-docs
{
...TAILWIND_CONFIG,
files: 'packages/nextra-theme-docs/**',
plugins: ['typescript-sort-keys'],
settings: {
tailwindcss: {
config: 'packages/nextra-theme-docs/tailwind.config.js',
callees: ['cn'],
whitelist: [
'nextra-breadcrumb',
'nextra-bleed',
'nextra-menu-desktop',
'nextra-menu-mobile',
],
},
},
rules: {
...TAILWIND_CONFIG.rules,
'no-restricted-imports': [
'error',
{
name: 'next/link',
message: 'Use local <Anchor /> instead',
},
],
},
},
// ⚙️ nextra-theme-blog
{
...TAILWIND_CONFIG,
files: 'packages/nextra-theme-blog/**',
settings: {
tailwindcss: {
config: 'packages/nextra-theme-blog/tailwind.config.js',
whitelist: ['subheading-', 'post-item', 'post-item-more'],
},
},
},
// ⚙️ nextra
{
...TAILWIND_CONFIG,
files: 'packages/nextra/**',
settings: {
tailwindcss: {
config: 'packages/nextra-theme-docs/tailwind.config.js',
callees: ['cn'],
whitelist: ['nextra-code-block', 'nextra-filetree'],
},
},
},
// ⚙️ Docs
{
...TAILWIND_CONFIG,
files: 'docs/**',
settings: {
tailwindcss: {
config: 'docs/tailwind.config.js',
callees: ['cn'],
whitelist: ['dash-ring', 'theme-1', 'theme-2', 'theme-3', 'theme-4'],
},
next: { rootDir: 'docs' },
},
},
// ⚙️ SWR-site example
{
...TAILWIND_CONFIG,
files: 'examples/swr-site/**',
settings: {
tailwindcss: {
config: 'examples/swr-site/tailwind.config.js',
},
next: { rootDir: 'examples/swr-site' },
},
},
// ⚙️ blog example
{
files: 'examples/blog/**',
settings: {
next: { rootDir: 'examples/blog' },
},
},
// ⚙️ docs example
{
files: 'examples/docs/**',
settings: {
next: { rootDir: 'examples/docs' },
},
},
{
files: [
'prettier.config.js',
'postcss.config.js',
'tailwind.config.js',
'next.config.js',
'next.config.mjs',
'.eslintrc.cjs',
],
env: {
node: true,
},
},
{
files: 'packages/{nextra,nextra-theme-docs,nextra-theme-blog}/**',
rules: {
// disable rule because we don't have pagesDir in above folders
'@next/next/no-html-link-for-pages': 'off',
},
},
{
files: 'packages/nextra/src/**',
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['fs', 'node:fs'],
message: 'Use `graceful-fs` instead',
},
],
},
],
},
},
{
files: ['**/*.d.ts'],
rules: {
'no-var': 'off',
},
},
],
}

View File

@@ -46,7 +46,7 @@ export function ChangelogFeed({ entries }: { entries: ChangelogEntry[] }) {
<section aria-label="Changelog entries" className="relative">
{/* Timeline line */}
<div className="absolute left-[7px] top-2 bottom-2 w-px bg-border" aria-hidden="true" />
<div className="absolute left-[7px] inset-y-2 w-px bg-border" aria-hidden="true" />
<ol className="space-y-6">
{filtered.map((entry) => (

View File

@@ -162,6 +162,14 @@ const companies = [
height: 'h-14',
imgHeight: 56,
},
{
name: 'Stripe',
logoLight: '/images/logos/Stripe wordmark - Slate.svg',
logoDark: '/images/logos/Stripe wordmark - White.svg',
isSvg: true,
height: 'h-10',
imgHeight: 40,
},
]
/* ---------------------------------------------------------------------------

View File

@@ -29,7 +29,7 @@ const AuthorCard: React.FC<{ author: AuthorMetadata }> = ({ author }) => {
return (
<Link href={`/authors/${author.authorid}`}>
<div className="flex flex-col items-center gap-4 bg-gray-200 bg-opacity-20 rounded-lg p-6 h-full group">
<div className="flex flex-col items-center gap-4 bg-gray-200/20 rounded-lg p-6 h-full group">
<div className="relative overflow-hidden rounded-full">
<Image
width={200}
@@ -41,7 +41,7 @@ const AuthorCard: React.FC<{ author: AuthorMetadata }> = ({ author }) => {
/>
</div>
<h2 className="font-bold text-xl">{author.name}</h2>
<p className="text-sm text-center text-gray-600 flex-grow">{author.subtitle}</p>
<p className="text-sm text-center text-gray-600 grow">{author.subtitle}</p>
<div className="flex flex-wrap gap-4 justify-center mt-2">
{isClient &&
socialsEntries.map(([key, value]) => (
@@ -57,7 +57,7 @@ const AuthorCard: React.FC<{ author: AuthorMetadata }> = ({ author }) => {
>
<SocialIcon
url={value}
className="absolute inset-0 w-full h-full transform scale-100 transition-transform opacity-100 hover:scale-90"
className="absolute inset-0 size-full scale-100 transition-transform opacity-100 hover:scale-90"
bgColor="#9B9B9B80"
fgColor="background"
style={{ width: '2em', height: '2em' }}

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import { getPagesUnderRoute } from 'nextra/context'
import { type Page } from 'nextra'
import { SocialIcon } from 'react-social-icons'
@@ -38,12 +38,6 @@ const AuthorProfile: React.FC<AuthorProfileProps> = ({ authorId }) => {
(a, b) => new Date(b.frontMatter.date).getTime() - new Date(a.frontMatter.date).getTime(),
)
if (!author) {
return <div>Author not found!</div>
}
const socialsEntries = Object.entries(author.socials ?? {}).filter(([, value]) => !!value)
// State to track whether the component is rendered on the client side
const [isClient, setIsClient] = useState(false)
@@ -51,6 +45,12 @@ const AuthorProfile: React.FC<AuthorProfileProps> = ({ authorId }) => {
setIsClient(true)
}, [])
if (!author) {
return <div>Author not found!</div>
}
const socialsEntries = Object.entries(author.socials ?? {}).filter(([, value]) => !!value)
return (
<>
<section className="max-w-4xl mx-auto flex flex-col md:flex-row gap-8 mt-12 mb-24 md:mb-32">
@@ -71,7 +71,7 @@ const AuthorProfile: React.FC<AuthorProfileProps> = ({ authorId }) => {
height={512}
src={author.ogImage}
alt={author.name}
className="rounded-box w-[12rem] md:w-[16rem] h-[12rem] md:h-[16rem] rounded-square"
className="rounded-box size-[12rem] md:size-[16rem] rounded-square"
style={{ borderRadius: '20px', objectFit: 'cover' }}
/>
@@ -90,7 +90,7 @@ const AuthorProfile: React.FC<AuthorProfileProps> = ({ authorId }) => {
>
<SocialIcon
url={value}
className="absolute inset-0 w-full h-full transform scale-100 transition-transform opacity-100 hover:scale-90"
className="absolute inset-0 size-full scale-100 transition-transform opacity-100 hover:scale-90"
bgColor="#9B9B9B80"
fgColor="background"
// fallback={{ path: 'M32 2 A30 30 0 0 1 62 32 A30 30 0 0 1 32 62 A30 30 0 0 1 2 32 A30 30 0 0 1 32 2 Z' }}
@@ -112,7 +112,7 @@ const AuthorProfile: React.FC<AuthorProfileProps> = ({ authorId }) => {
/>
))}
</div>
<div style={{ marginTop: '75px' }}></div>
<div style={{ marginTop: '75px' }} />
<div>
<Cards num={3}>
<Cards.Card title="Blog" href="/blog" icon={<Blog />} image>

View File

@@ -48,8 +48,8 @@ export function Banner({ storageKey }: BannerProps) {
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>

View File

@@ -101,32 +101,32 @@ const menuItems: {
const socialLinks = [
{
title: 'GitHub',
icon: <Github className="h-4 w-4" aria-hidden="true" />,
icon: <Github className="size-4" aria-hidden="true" />,
href: 'https://github.librechat.ai/',
},
{
title: 'Discord',
icon: <Discord className="h-4 w-4" aria-hidden="true" />,
icon: <Discord className="size-4" aria-hidden="true" />,
href: 'https://discord.librechat.ai/',
},
{
title: 'LinkedIn',
icon: <Linkedin className="h-4 w-4" aria-hidden="true" />,
icon: <Linkedin className="size-4" aria-hidden="true" />,
href: 'https://linkedin.librechat.ai/',
},
{
title: 'X',
icon: <X className="h-4 w-4" aria-hidden="true" />,
icon: <X className="size-4" aria-hidden="true" />,
href: 'https://x.com/LibreChatAI',
},
{
title: 'YouTube',
icon: <Youtube className="h-4 w-4" aria-hidden="true" />,
icon: <Youtube className="size-4" aria-hidden="true" />,
href: 'https://www.youtube.com/@LibreChat',
},
{
title: 'Email',
icon: <Mail className="h-4 w-4" aria-hidden="true" />,
icon: <Mail className="size-4" aria-hidden="true" />,
href: 'mailto:contact@librechat.ai',
},
]
@@ -162,7 +162,7 @@ const FooterMenu = () => {
key={link.title}
href={link.href}
aria-label={link.title}
className="flex items-center justify-center h-9 w-9 rounded-full text-neutral-500 dark:text-neutral-300 transition-colors duration-200 hover:text-primary hover:bg-neutral-100 dark:hover:bg-neutral-800"
className="flex items-center justify-center size-9 rounded-full text-neutral-500 dark:text-neutral-300 transition-colors duration-200 hover:text-primary hover:bg-neutral-100 dark:hover:bg-neutral-800"
>
{link.icon}
</Link>

View File

@@ -1,5 +1,5 @@
import validator from 'validator'
import React, { useState } from 'react'
import { useState } from 'react'
import toast, { Toaster } from 'react-hot-toast'
import style from './newsletterform.module.css'

View File

@@ -72,8 +72,8 @@ export const Video = ({
}
}}
>
<div className="p-3 md:p-6 rounded-full bg-black group-hover:ring-8 ring-black/20 bg-opacity-75 hover:bg-opacity-90 transition flex">
<Play className="h-6 w-6 text-white" />
<div className="p-3 md:p-6 rounded-full bg-black/75 group-hover:ring-8 ring-black/20 hover:bg-black/90 transition flex">
<Play className="size-6 text-white" />
</div>
<div className="mt-3 md:mt-6 md:opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<span className="flex gap-2 text-xs md:text-sm font-semibold bg-black/90 text-white py-1 px-3 rounded-full">

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'
import { useState, useEffect } from 'react'
import Image from 'next/image'
import { useRouter } from 'next/router'
import type { Page, MdxFile } from 'nextra'
@@ -46,22 +46,18 @@ const BlogCard = ({
return (
<div className="bg-popover rounded-lg shadow-md overflow-hidden blog-card">
<div
className="relative h-52 md:h-64 mb-1 overflow-hidden transform scale-100 transition-transform hover:scale-105 cursor-pointer"
className="relative h-52 md:h-64 mb-1 overflow-hidden scale-100 transition-transform hover:scale-105 cursor-pointer"
onClick={handleCardClick}
style={{ transformOrigin: 'bottom center' }}
>
{page.frontMatter?.ogVideo ? (
<Video
src={page.frontMatter.ogVideo}
gifStyle
className="object-cover w-full h-full mt-0"
/>
<Video src={page.frontMatter.ogVideo} gifStyle className="object-cover size-full mt-0" />
) : page.frontMatter?.ogImage ? (
<Image
src={page.frontMatter.gif ?? page.frontMatter.ogImage}
width={1200}
height={675}
className="object-cover absolute top-0 left-0 w-full h-full"
className="object-cover absolute top-0 left-0 size-full"
alt={page.frontMatter?.title ?? 'Blog post image'}
unoptimized={
page.frontMatter.gif !== undefined || page.frontMatter.ogImage?.endsWith('.gif')
@@ -74,7 +70,7 @@ const BlogCard = ({
{page.frontMatter?.tags?.map((tag) => (
<span
key={tag}
className={`cursor-pointer text-xs py-1 px-2 bg-background/80 shadow-md rounded-md ml-1 mr-1 ${
className={`cursor-pointer text-xs py-1 px-2 bg-background/80 shadow-md rounded-md mx-1 ${
selectedTags.includes(tag) ? 'bg-gray-700/20' : ''
}`}
onClick={() => handleTagClick(tag)}
@@ -84,13 +80,13 @@ const BlogCard = ({
))}
</div>
{/* Modified title and description to be clickable */}
<div className="mb-2 ml-1 mr-1 cursor-pointer" onClick={handleCardClick}>
<div className="mb-2 mx-1 cursor-pointer" onClick={handleCardClick}>
<h2 className="font-mono text-xl mb-2 font-bold">
{page.meta?.title || page.frontMatter?.title || page.name}
</h2>
<div>{truncateDescription(page.frontMatter?.description || '')}</div>
</div>
<div className="flex items-center justify-between absolute bottom-4 left-4 right-4">
<div className="flex items-center justify-between absolute bottom-4 inset-x-4">
<Author authorid={page.frontMatter?.authorid} />
<span className="text-sm opacity-60">{page.frontMatter?.date}</span>
</div>

View File

@@ -38,7 +38,7 @@ export const BlogHeader = () => {
<div style={{ textAlign: 'right' }}>
<Author authorid={authorid} />
</div>
<br></br>
<br />
{ogVideo ? (
<Video src={ogVideo} gifStyle />
) : ogImage ? (
@@ -54,7 +54,7 @@ export const BlogHeader = () => {
}
/>
) : null}
<div className="mt-6 md:mt-4"></div>
<div className="mt-6 md:mt-4" />
</div>
</div>
)

View File

@@ -78,7 +78,7 @@ export function Callout({
{title ? (
<span className={styles['title-text']}>{title}</span>
) : (
<span className={styles['title-spacer']} style={{ flexGrow: 1 }}></span>
<span className={styles['title-spacer']} style={{ flexGrow: 1 }} />
)}
{collapsible && <span className={styles.arrow}>{isCollapsed ? '▷' : '▽'}</span>}
</div>

View File

@@ -51,10 +51,18 @@ const Carousel = ({ children, ...props }) => {
</div>
{showControls && (
<div className={styles.glide__arrows} data-glide-el="controls">
<button className={`${styles.glide__arrow} glide__arrow--left}`} data-glide-dir="<" aria-label="Previous slide">
<button
className={`${styles.glide__arrow} glide__arrow--left}`}
data-glide-dir="<"
aria-label="Previous slide"
>
</button>
<button className={`${styles.glide__arrow} glide__arrow--right}`} data-glide-dir=">" aria-label="Next slide">
<button
className={`${styles.glide__arrow} glide__arrow--right}`}
data-glide-dir=">"
aria-label="Next slide"
>
</button>
</div>
@@ -62,7 +70,11 @@ const Carousel = ({ children, ...props }) => {
{showBullets && (
<div className={styles.glide__bullets} data-glide-el="controls[nav]">
{React.Children.map(children, (_, index) => (
<button className={styles.glide__bullets} data-glide-dir={`=${index}`} aria-label={`Go to slide ${index + 1}`}></button>
<button
className={styles.glide__bullets}
data-glide-dir={`=${index}`}
aria-label={`Go to slide ${index + 1}`}
/>
))}
</div>
)}

View File

@@ -49,8 +49,8 @@ export default function Changelog({ className }: { className?: string }) {
<div className="w-px bg-secondary" />
</div>
<div className="relative flex h-6 w-6 flex-none items-center justify-center bg-background">
<div className="h-1.5 w-1.5 rounded-full bg-secondary ring-1 ring-primary/80 opacity-60 group-hover:opacity-100" />
<div className="relative flex size-6 flex-none items-center justify-center bg-background">
<div className="size-1.5 rounded-full bg-secondary ring-1 ring-primary/80 opacity-60 group-hover:opacity-100" />
</div>
<p className="flex-auto py-0.5 text-sm leading-5 text-primary/70 opacity-80 group-hover:opacity-100">
<span className="font-medium text-primary">{activityItem.title}</span>{' '}
@@ -75,12 +75,12 @@ export default function Changelog({ className }: { className?: string }) {
role="button" // Added role for the link acting as a button
aria-label="Read the full changelog" // Added aria-label for the link
>
<div className="h-6 absolute left-0 top-0 flex w-6 justify-center">
<div className="size-6 absolute left-0 top-0 flex justify-center">
<div className="w-px bg-secondary" />
</div>
<div className="relative flex h-6 w-6 flex-none items-center justify-center bg-background">
<div className="h-1.5 w-1.5 rounded-full bg-secondary ring-1 ring-primary/80 opacity-60 group-hover:opacity-100" />
<div className="relative flex size-6 flex-none items-center justify-center bg-background">
<div className="size-1.5 rounded-full bg-secondary ring-1 ring-primary/80 opacity-60 group-hover:opacity-100" />
</div>
<p className="flex-auto py-0.5 text-sm leading-5 text-primary/60 opacity-80 group-hover:opacity-100">
<span className="font-medium text-primary">Read the full changelog ...</span> {null}

View File

@@ -1,138 +0,0 @@
import React, { useMemo } from 'react'
import Image from 'next/image'
interface Company {
name: string
logo: string
logoDark?: string
logoColor?: string
url?: string
}
const companies: Company[] = [
{
name: 'Shopify',
logo: '/images/logos/Shopify_light.svg',
logoDark: '/images/logos/Shopify_dark.svg',
},
{
name: 'ClickHouse',
logo: '/images/logos/ClickHouse_light.svg',
logoDark: '/images/logos/ClickHouse_dark.svg',
},
{
name: 'Boston University',
logo: '/images/logos/BostonUniversity_light.png',
logoDark: '/images/logos/BostonUniversity_dark.png',
logoColor: '/images/logos/BostonUniversity_color.png',
},
{
name: 'Daimler Truck',
logo: '/images/logos/DaimlerTruck_light.svg',
logoDark: '/images/logos/DaimlerTruck_dark.svg',
},
]
/** Company logos section showcasing companies that use LibreChat. */
export const Companies: React.FC = React.memo(() => {
const logosToShow = useMemo(
() => Array.from({ length: 8 }, (_, i) => companies[i % companies.length]),
[],
)
return (
<section className="py-20">
<div className="w-full px-4 sm:px-6 lg:px-8">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Trusted by companies worldwide
</h2>
<p className="mt-4 text-lg text-muted-foreground">
Join thousands of organizations using LibreChat
</p>
</div>
<div className="flex flex-wrap items-center justify-center gap-8 md:gap-12">
{logosToShow.map((company, index) => {
const isFirstFewLogos = index < 4
const key = `${company.name}-${index}`
return (
<div
key={key}
className={`flex items-center justify-center px-4 py-2 ${company.logoColor ? 'group' : ''}`}
>
{company.logoColor ? (
<div className="relative">
<Image
src={company.logo}
alt={`${company.name} logo`}
className="dark:hidden group-hover:opacity-0 transition-opacity duration-300 h-10 w-auto object-contain"
width={120}
height={60}
sizes="120px"
unoptimized={company.logo.endsWith('.svg')}
priority={isFirstFewLogos}
loading={isFirstFewLogos ? 'eager' : 'lazy'}
/>
<Image
src={company.logoDark || company.logo}
alt={`${company.name} logo`}
className="hidden dark:block group-hover:opacity-0 transition-opacity duration-300 h-10 w-auto object-contain"
width={120}
height={60}
sizes="120px"
unoptimized={(company.logoDark || company.logo).endsWith('.svg')}
priority={isFirstFewLogos}
loading={isFirstFewLogos ? 'eager' : 'lazy'}
/>
<Image
src={company.logoColor}
alt={`${company.name} logo`}
className="absolute top-0 left-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 h-10 w-auto object-contain"
width={120}
height={60}
sizes="120px"
unoptimized={company.logoColor.endsWith('.svg')}
priority={isFirstFewLogos}
loading={isFirstFewLogos ? 'eager' : 'lazy'}
/>
</div>
) : (
<>
<Image
src={company.logo}
alt={`${company.name} logo`}
className={`h-10 w-auto object-contain ${company.logoDark ? 'dark:hidden' : ''}`}
width={120}
height={60}
sizes="120px"
unoptimized={company.logo.endsWith('.svg')}
priority={isFirstFewLogos}
loading={isFirstFewLogos ? 'eager' : 'lazy'}
/>
{company.logoDark && (
<Image
src={company.logoDark}
alt={`${company.name} logo`}
className="hidden dark:block h-10 w-auto object-contain"
width={120}
height={60}
sizes="120px"
unoptimized={company.logoDark.endsWith('.svg')}
priority={isFirstFewLogos}
loading={isFirstFewLogos ? 'eager' : 'lazy'}
/>
)}
</>
)}
</div>
)
})}
</div>
</div>
</section>
)
})
Companies.displayName = 'Companies'
export default Companies

View File

@@ -22,7 +22,7 @@ const HeroTitle = React.memo(() => (
{HERO_TITLE.firstPart}{' '}
<span className="relative inline-block text-foreground">
{HERO_TITLE.highlight}
<div className="absolute -bottom-1 left-0 right-0">
<div className="absolute -bottom-1 inset-x-0">
<div className="bg-gradient-to-r from-transparent via-muted-foreground to-transparent h-[2px] blur-sm" />
<div className="bg-gradient-to-r from-transparent via-muted-foreground to-transparent h-px" />
</div>

View File

@@ -1,5 +1,3 @@
import * as React from 'react'
function RepoOfTheDay() {
return (
<svg
@@ -9,7 +7,7 @@ function RepoOfTheDay() {
width="250"
xmlns="http://www.w3.org/2000/svg"
>
<rect fill="#111111" height="53" rx="10" width="249" x="0.5" y="0.5"></rect>
<rect fill="#111111" height="53" rx="10" width="249" x="0.5" y="0.5" />
<foreignObject
height="17"
style={{
@@ -38,7 +36,7 @@ function RepoOfTheDay() {
<path
d="M70.71,40.31C75.74,44.3,80,37.86,80,37.86s-5.64-2.17-8.55,0.61c0.59-1.62,1.02-3.31,1.28-5.01 c4.08,2.16,6.44-2.95,6.44-2.95s-4.41-0.97-6.26,1.4c0.08-0.91,0.12-1.82,0.1-2.73c-0.01-0.36-0.02-0.73-0.05-1.09 c2.96-3.68-1.73-6.99-1.73-6.99s-2.14,5.09,0.98,7.09c0.02,0.33,0.03,0.66,0.03,1c0.01,0.76-0.03,1.52-0.1,2.27 c-0.85-2.69-4.91-3.69-4.91-3.69s-0.13,5.78,4.68,5.48c-0.28,1.69-0.73,3.35-1.34,4.95c-0.19-4.03-5.79-6.33-5.79-6.33 s-1.33,7.55,5.01,8.16c-0.38,0.8-0.8,1.57-1.25,2.32c-0.56,0.95-1.21,1.84-1.89,2.71c0.97-3.99-3.96-7.72-3.96-7.72 s-3.18,6.94,2.73,9.15c-0.38,0.43-0.8,0.81-1.2,1.21c-0.21,0.2-0.43,0.38-0.64,0.58l-0.32,0.29c-0.11,0.09-0.22,0.18-0.33,0.27 l-0.67,0.54l-0.7,0.51c-0.08,0.05-0.16,0.11-0.23,0.16c1.62-3.42-2.07-7.77-2.07-7.77s-4.21,5.55,0.49,8.78 c-1.34,0.79-2.74,1.45-4.2,1.98c1.91-2.59-0.23-6.89-0.23-6.89s-4.66,3.77-1.52,7.46c-1.15,0.33-2.33,0.57-3.51,0.74 c1.46-1.68,0.55-4.83,0.55-4.83s-3.7,2.03-2.18,5c-0.52,0.03-1.05,0.07-1.57,0.06c-0.29,0-0.57,0.01-0.86,0l-0.86-0.04 c-0.85-0.06-1.7-0.15-2.54-0.28l0.68-0.27l0.42-0.17l0.41-0.19l0.82-0.38c0,0,0.01,0,0.01,0c0.39-0.18,0.55-0.65,0.37-1.03 c-0.18-0.39-0.65-0.55-1.03-0.37l-0.04,0.02l-0.77,0.37l-0.39,0.18l-0.39,0.16l-0.79,0.33l-0.8,0.29l-0.4,0.14l-0.41,0.12L40,53.6 l-0.51-0.15l-0.41-0.12l-0.4-0.14l-0.8-0.29l-0.79-0.33l-0.39-0.16l-0.39-0.18l-0.77-0.37l-0.04-0.02c0,0,0,0-0.01,0 c-0.39-0.18-0.85-0.01-1.03,0.38c-0.18,0.39-0.01,0.85,0.38,1.03l0.82,0.38l0.41,0.19l0.42,0.17l0.68,0.27 c-0.84,0.14-1.69,0.22-2.54,0.28l-0.86,0.04c-0.29,0.01-0.57,0-0.86,0c-0.53,0.01-1.05-0.03-1.57-0.06c1.51-2.98-2.18-5-2.18-5 s-0.92,3.15,0.55,4.83c-1.19-0.16-2.36-0.41-3.51-0.74c3.15-3.7-1.52-7.46-1.52-7.46s-2.14,4.31-0.23,6.89 c-1.46-0.53-2.86-1.19-4.2-1.98c4.7-3.22,0.49-8.78,0.49-8.78s-3.69,4.34-2.07,7.77c-0.08-0.05-0.16-0.1-0.23-0.16l-0.7-0.51 l-0.67-0.54c-0.11-0.09-0.23-0.18-0.33-0.27l-0.32-0.29c-0.21-0.19-0.43-0.38-0.64-0.58c-0.4-0.4-0.82-0.79-1.2-1.21 c5.91-2.21,2.73-9.15,2.73-9.15s-4.93,3.73-3.96,7.72c-0.68-0.86-1.33-1.76-1.89-2.71c-0.46-0.75-0.87-1.53-1.25-2.32 c6.33-0.61,5.01-8.16,5.01-8.16s-5.6,2.31-5.79,6.33c-0.61-1.6-1.06-3.26-1.34-4.95c4.81,0.3,4.68-5.48,4.68-5.48 s-4.05,0.99-4.91,3.69c-0.07-0.76-0.1-1.51-0.1-2.27c0-0.33,0.01-0.66,0.03-1c3.11-2.01,0.98-7.09,0.98-7.09s-4.69,3.31-1.73,6.99 C7,28.46,6.99,28.82,6.98,29.18c-0.02,0.91,0.01,1.82,0.1,2.73c-1.84-2.38-6.26-1.4-6.26-1.4s2.37,5.11,6.44,2.95 c0.26,1.71,0.69,3.39,1.28,5.01C5.64,35.69,0,37.86,0,37.86s4.26,6.43,9.29,2.45c0.39,0.87,0.83,1.72,1.31,2.54 c0.47,0.83,1.01,1.63,1.58,2.4C8.71,43.7,4.11,47,4.11,47s5.7,5.1,9.56,0.08c0.04,0.04,0.07,0.08,0.11,0.12 c0.39,0.45,0.82,0.87,1.24,1.3c0.21,0.21,0.44,0.41,0.66,0.61l0.33,0.3c0.11,0.1,0.23,0.19,0.34,0.29l0.69,0.57l0.23,0.17 c-3.34-0.34-6.58,3.29-6.58,3.29s6.19,3.47,8.69-1.83c1.2,0.75,2.47,1.41,3.78,1.96c-2.76,0.6-4.62,4.13-4.62,4.13 s5.89,1.62,6.98-3.26c1.03,0.32,2.07,0.58,3.13,0.78c-1.63,0.99-2.39,3.38-2.39,3.38s4.31,0.39,4.61-3.07 c0.07,0.01,0.14,0.02,0.21,0.02c0.6,0.04,1.2,0.1,1.8,0.09c0.3,0,0.6,0.02,0.9,0.01l0.9-0.03c1.2-0.07,2.41-0.18,3.59-0.42 l0.45-0.08c0.15-0.03,0.29-0.07,0.44-0.1L40,55.13l0.81,0.19c0.15,0.03,0.29,0.07,0.44,0.1l0.45,0.08c1.18,0.23,2.39,0.35,3.59,0.42 l0.9,0.03c0.3,0.01,0.6-0.01,0.9-0.01c0.6,0,1.2-0.06,1.8-0.09c0.07-0.01,0.14-0.02,0.21-0.02c0.31,3.45,4.61,3.07,4.61,3.07 s-0.76-2.39-2.39-3.38c1.06-0.2,2.11-0.46,3.13-0.78c1.09,4.88,6.98,3.26,6.98,3.26s-1.86-3.52-4.62-4.13 c1.31-0.55,2.57-1.21,3.78-1.96c2.5,5.3,8.69,1.83,8.69,1.83s-3.24-3.63-6.58-3.29l0.23-0.17l0.69-0.57 c0.11-0.1,0.23-0.19,0.34-0.29l0.33-0.3c0.22-0.2,0.45-0.4,0.66-0.61c0.42-0.43,0.85-0.84,1.24-1.3c0.03-0.04,0.07-0.08,0.11-0.12 C70.19,52.1,75.89,47,75.89,47s-4.6-3.31-8.08-1.75c0.57-0.77,1.11-1.56,1.58-2.4C69.88,42.03,70.31,41.18,70.71,40.31z"
fill="#ffffff"
></path>
/>
</svg>
<foreignObject
height="35"

View File

@@ -1,5 +1,3 @@
import * as React from 'react'
function RossIndex() {
return (
<svg

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import { useState } from 'react'
import useCredentialsGenerator from './credentialsGenerator' // Adjust the path based on your project structure
const CredsGenerator = () => {
@@ -42,7 +42,13 @@ const CredsGenerator = () => {
<div>
<p style={{ fontSize: '0.8rem', marginBottom: '5px', marginTop: '10px' }}>CREDS_KEY</p>
<div className="input-container">
<input type="text" value={credentials?.CREDS_KEY || ''} placeholder="" readOnly aria-label="CREDS_KEY value" />
<input
type="text"
value={credentials?.CREDS_KEY || ''}
placeholder=""
readOnly
aria-label="CREDS_KEY value"
/>
<button
className="copy-button"
onClick={() => handleCopy(credentials?.CREDS_KEY)}
@@ -56,7 +62,13 @@ const CredsGenerator = () => {
<div>
<p style={{ fontSize: '0.8rem', marginBottom: '5px', marginTop: '10px' }}>CREDS_IV</p>
<div className="input-container">
<input type="text" value={credentials?.CREDS_IV || ''} placeholder="" readOnly aria-label="CREDS_IV value" />
<input
type="text"
value={credentials?.CREDS_IV || ''}
placeholder=""
readOnly
aria-label="CREDS_IV value"
/>
<button
className="copy-button"
onClick={() => handleCopy(credentials?.CREDS_IV)}
@@ -70,7 +82,13 @@ const CredsGenerator = () => {
<div>
<p style={{ fontSize: '0.8rem', marginBottom: '5px', marginTop: '10px' }}>JWT_SECRET</p>
<div className="input-container">
<input type="text" value={credentials?.JWT_SECRET || ''} placeholder="" readOnly aria-label="JWT_SECRET value" />
<input
type="text"
value={credentials?.JWT_SECRET || ''}
placeholder=""
readOnly
aria-label="JWT_SECRET value"
/>
<button
className="copy-button"
onClick={() => handleCopy(credentials?.JWT_SECRET)}
@@ -108,7 +126,13 @@ const CredsGenerator = () => {
MEILI_MASTER_KEY
</p>
<div className="input-container">
<input type="text" value={credentials?.MEILI_KEY || ''} placeholder="" readOnly aria-label="MEILI_MASTER_KEY value" />
<input
type="text"
value={credentials?.MEILI_KEY || ''}
placeholder=""
readOnly
aria-label="MEILI_MASTER_KEY value"
/>
<button
className="copy-button"
onClick={() => handleCopy(credentials?.MEILI_KEY)}

View File

@@ -36,7 +36,7 @@ const AccordionTrigger = React.forwardRef<
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
<ChevronDown className="size-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))

View File

@@ -12,7 +12,7 @@ const Avatar = React.forwardRef<
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}
className={cn('relative flex size-10 shrink-0 overflow-hidden rounded-full', className)}
{...props}
/>
))
@@ -24,7 +24,7 @@ const AvatarImage = React.forwardRef<
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn('aspect-square h-full w-full', className)}
className={cn('aspect-square size-full', className)}
{...props}
/>
))
@@ -36,10 +36,7 @@ const AvatarFallback = React.forwardRef<
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
'flex h-full w-full items-center justify-center rounded-full bg-muted',
className,
)}
className={cn('flex size-full items-center justify-center rounded-full bg-muted', className)}
{...props}
/>
))

View File

@@ -44,7 +44,7 @@ const DialogContent = React.forwardRef<
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<X className="size-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>

View File

@@ -36,7 +36,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
<ChevronRight className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
@@ -110,9 +110,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<span className="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
<Check className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
@@ -135,9 +135,9 @@ const DropdownMenuRadioItem = React.forwardRef<
value={value}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<span className="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
<Circle className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}

195
eslint.config.mjs Normal file
View File

@@ -0,0 +1,195 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { includeIgnoreFile } from '@eslint/compat';
import js from '@eslint/js';
import nextPlugin from '@next/eslint-plugin-next';
import eslintConfigPrettier from 'eslint-config-prettier';
import importPlugin from 'eslint-plugin-import';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import tailwindcss from 'eslint-plugin-tailwindcss';
import unicorn from 'eslint-plugin-unicorn';
import globals from 'globals';
import tseslint from 'typescript-eslint';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default tseslint.config(
// ── 1. Global ignores ──────────────────────────────────────────────
includeIgnoreFile(path.resolve(__dirname, '.gitignore')),
{ ignores: ['content/**', '.source/**'] },
// ── 2. Base configs ────────────────────────────────────────────────
js.configs.recommended,
...tseslint.configs.recommended,
// ── 3. All JS / TS files — shared rules ────────────────────────────
{
files: ['**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}'],
plugins: {
import: importPlugin,
unicorn,
},
rules: {
'prefer-object-has-own': 'error',
'logical-assignment-operators': ['error', 'always', { enforceForIfStatements: true }],
'no-else-return': ['error', { allowElseIf: false }],
'no-lonely-if': 'error',
'prefer-destructuring': ['error', { VariableDeclarator: { object: true } }],
'import/no-duplicates': 'error',
'no-negated-condition': 'off',
'unicorn/no-negated-condition': 'error',
'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
'object-shorthand': ['error', 'always'],
'unicorn/prefer-regexp-test': 'error',
'unicorn/no-array-for-each': 'error',
'unicorn/prefer-string-replace-all': 'error',
'@typescript-eslint/prefer-for-of': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_' },
],
// todo: enable
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
},
},
// ── 4. React files ─────────────────────────────────────────────────
{
files: ['app/**/*.{tsx,jsx}', 'components/**/*.{tsx,jsx}', 'lib/**/*.{tsx,jsx}'],
...react.configs.flat.recommended,
...react.configs.flat['jsx-runtime'],
plugins: {
...react.configs.flat.recommended.plugins,
'react-hooks': reactHooks,
},
settings: { react: { version: 'detect' } },
rules: {
...react.configs.flat.recommended.rules,
...react.configs.flat['jsx-runtime'].rules,
...reactHooks.configs['recommended-latest'].rules,
'react/prop-types': 'off',
'react/no-unknown-property': ['error', { ignore: ['jsx'] }],
'react-hooks/exhaustive-deps': 'error',
'react/self-closing-comp': 'error',
'no-restricted-syntax': [
'error',
{
selector:
'CallExpression[callee.name=useMemo][arguments.1.type=ArrayExpression][arguments.1.elements.length=0]',
message:
"`useMemo` with an empty dependency array can't provide a stable reference, use `useRef` instead.",
},
{
selector: 'MemberExpression[object.name=z] > .property[name=object]',
message: 'Use z.strictObject is more safe.',
},
],
'react/jsx-filename-extension': [
'error',
{ extensions: ['.tsx', '.jsx'], allow: 'as-needed' },
],
'react/jsx-curly-brace-presence': 'error',
'react/jsx-boolean-value': 'error',
},
},
// ── 5. Next.js rules ───────────────────────────────────────────────
{
files: ['app/**/*.{tsx,jsx}', 'components/**/*.{tsx,jsx}', 'lib/**/*.{tsx,jsx}'],
plugins: { '@next/next': nextPlugin },
rules: {
...nextPlugin.configs.recommended.rules,
...nextPlugin.configs['core-web-vitals'].rules,
},
},
// ── 6. TypeScript type-checked ─────────────────────────────────────
{
files: ['**/*.{ts,tsx,cts,mts}'],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: __dirname,
},
},
rules: {
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/non-nullable-type-assertion-style': 'error',
'@typescript-eslint/prefer-optional-chain': 'error',
},
},
// ── 7. Tailwind CSS ────────────────────────────────────────────────
{
files: ['app/**/*.{tsx,jsx}', 'components/**/*.{tsx,jsx}', 'lib/**/*.{tsx,jsx}'],
...tailwindcss.configs['flat/recommended'][0],
settings: {
tailwindcss: {
callees: ['cn'],
whitelist: [
'dash-ring',
'theme-\\d+',
'nextra-[\\w-]+',
'blog-card',
'glide(__.+)?',
'credentials-\\w+',
'input-container',
'copy-button',
'generate-button',
'tooltip',
'card',
'cards',
'file',
'file-tree',
'folder',
'carousel',
'frame',
'btn(-.+)?',
'rounded-box',
'rounded-square',
'bg-blackish',
'clickhouse-highlight',
'text-base-content\\/\\d+',
'origin-\\(.+\\)',
'max-h-\\(.+\\)',
'subheading-\\w*',
'post-item(-.+)?',
],
},
},
rules: {
'tailwindcss/classnames-order': 'off',
'tailwindcss/enforces-negative-arbitrary-values': 'error',
'tailwindcss/enforces-shorthand': 'error',
'tailwindcss/migration-from-tailwind-2': 'error',
'tailwindcss/no-custom-classname': 'error',
},
},
// ── 8. Node / CJS config files ─────────────────────────────────────
{
files: [
'*.config.{js,cjs,mjs,ts}',
'postcss.config.*',
'prettier.config.*',
'.prettierrc.{js,cjs}',
'**/*.cjs',
],
languageOptions: {
globals: globals.node,
},
},
// ── 9. Declaration files ───────────────────────────────────────────
{
files: ['**/*.d.ts'],
rules: { 'no-var': 'off' },
},
// ── 10. Prettier (must be last) ────────────────────────────────────
eslintConfigPrettier,
);

View File

@@ -137,7 +137,7 @@ function CardCompat({
}) {
const content = (
<div className="flex flex-col gap-2">
{icon && <div className="[&>svg]:h-6 [&>svg]:w-6">{icon}</div>}
{icon && <div className="[&>svg]:size-6">{icon}</div>}
{title && <h3 className="font-semibold text-fd-foreground">{title}</h3>}
{children && <div className="text-sm text-fd-muted-foreground">{children}</div>}
</div>

View File

@@ -6,7 +6,6 @@
* Nextra's components use compound patterns (e.g., Cards.Card, Tabs.Tab,
* FileTree.File) which are replicated here using Object.assign.
*/
import React from 'react'
import type { ReactNode } from 'react'
interface ChildrenProps {
@@ -40,7 +39,7 @@ function CardComponent({
className="nextra-card flex items-center gap-3 rounded-lg border border-gray-200 dark:border-gray-800 p-4 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800/50"
{...props}
>
{icon && <span className="shrink-0 [&>svg]:h-6 [&>svg]:w-6">{icon}</span>}
{icon && <span className="shrink-0 [&>svg]:size-6">{icon}</span>}
{title && <h3 className="m-0 text-base font-medium">{title}</h3>}
{children}
</div>

View File

@@ -4,7 +4,6 @@
* to automatically provide components that Nextra used to inject
* via theme.config.tsx's `components` property.
*/
import React from 'react'
import type { ReactNode, ComponentType } from 'react'
interface ChildrenProps {

View File

@@ -7,7 +7,6 @@
* This shim is temporary and will be removed once all pages/ content
* is migrated to the Fumadocs app/ directory.
*/
/* eslint-env node */
module.exports = function metaLoader() {
return `
export default function MetaPlaceholder() { return null; }

View File

@@ -3,7 +3,6 @@
* Provides stub implementations so existing pages/ components
* can resolve their imports during the migration to Fumadocs.
*/
import React from 'react'
import type { ReactNode } from 'react'
interface ChildrenProps {

2
next-env.d.ts vendored
View File

@@ -1,7 +1,7 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -1,5 +1,3 @@
/* eslint-disable no-undef */
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: 'https://www.librechat.ai',

View File

@@ -9,7 +9,7 @@
"postbuild": "node scripts/clean-cache.ts",
"start": "next start -p 3333",
"analyze": "cross-env ANALYZE=true next build",
"lint": "eslint --cache --ignore-path .gitignore --max-warnings 0 .",
"lint": "eslint --cache --max-warnings 0 .",
"lint:prettier": "prettier --cache --check --ignore-path .gitignore --ignore-path .prettierignore .",
"prettier": "bun run lint:prettier --write",
"prepare": "husky"
@@ -65,29 +65,31 @@
"zod": "^4.3.6"
},
"devDependencies": {
"@eslint/compat": "^1",
"@eslint/js": "^9",
"@next/bundle-analyzer": "^15.1.4",
"@next/eslint-plugin-next": "^15",
"@types/mdx": "^2.0.13",
"@playwright/test": "^1.58.2",
"@types/node": "18.16.0",
"@types/react": "^18.3.12",
"@typescript-eslint/eslint-plugin": "^8.48.1",
"@typescript-eslint/parser": "^8.48.1",
"autoprefixer": "^10.4.19",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"eslint-config-next": "15.1.4",
"eslint": "^9",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-mdx": "^3.1.5",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-import": "^2",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^5",
"eslint-plugin-tailwindcss": "^3.18.2",
"eslint-plugin-unicorn": "^53.0.0",
"eslint-plugin-unicorn": "^56",
"globals": "^15",
"husky": "^9.0.11",
"lint-staged": "^16.2.7",
"playwright": "^1.58.2",
"postcss": "^8.4.38",
"prettier": "^3.2.5",
"typescript": "^5.4.5",
"typescript-eslint": "^8",
"webpack": "^5.104.1"
},
"optionalDependencies": {
@@ -98,7 +100,9 @@
"next": ">=15.5.10",
"preact": ">=10.28.2",
"webpack": ">=5.104.1",
"diff": ">=5.2.2"
"diff": ">=5.2.2",
"minimatch": ">=3.1.4",
"serialize-javascript": ">=7.0.3"
}
},
"nextBundleAnalysis": {

1745
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
<svg width="360" height="151" viewBox="0 0 360 151" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M360 78.2001C360 52.6001 347.6 32.4001 323.9 32.4001C300.1 32.4001 285.7 52.6001 285.7 78.0001C285.7 108.1 302.7 123.3 327.1 123.3C339 123.3 348 120.6 354.8 116.8V96.8001C348 100.2 340.2 102.3 330.3 102.3C320.6 102.3 312 98.9002 310.9 87.1002H359.8C359.8 85.8002 360 80.6002 360 78.2001ZM310.6 68.7001C310.6 57.4002 317.5 52.7001 323.8 52.7001C329.9 52.7001 336.4 57.4002 336.4 68.7001H310.6Z" fill="#061B31"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M247.1 32.4001C237.3 32.4001 231 37.0001 227.5 40.2001L226.2 34.0001H204.2V150.6L229.2 145.3L229.3 117C232.9 119.6 238.2 123.3 247 123.3C264.9 123.3 281.2 108.9 281.2 77.2001C281.1 48.2001 264.6 32.4001 247.1 32.4001ZM241.1 101.3C235.2 101.3 231.7 99.2001 229.3 96.6002L229.2 59.5001C231.8 56.6001 235.4 54.6002 241.1 54.6002C250.2 54.6002 256.5 64.8001 256.5 77.9001C256.5 91.3001 250.3 101.3 241.1 101.3Z" fill="#061B31"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M169.8 26.5L194.9 21.1V0.800049L169.8 6.10005V26.5Z" fill="#061B31"/>
<path d="M194.9 34.1001H169.8V121.6H194.9V34.1001Z" fill="#061B31"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M142.9 41.5001L141.3 34.1001H119.7V121.6H144.7V62.3001C150.6 54.6001 160.6 56.0001 163.7 57.1001V34.1001C160.5 32.9001 148.8 30.7001 142.9 41.5001Z" fill="#061B31"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.8999 12.4001L68.4999 17.6001L68.3999 97.7001C68.3999 112.5 79.4999 123.4 94.2999 123.4C102.5 123.4 108.5 121.9 111.8 120.1V99.8001C108.6 101.1 92.7999 105.7 92.7999 90.9001V55.4001H111.8V34.1002H92.7999L92.8999 12.4001Z" fill="#061B31"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.3 59.5001C25.3 55.6001 28.5 54.1002 33.8 54.1002C41.4 54.1002 51 56.4001 58.6 60.5001V37.0001C50.3 33.7001 42.1 32.4001 33.8 32.4001C13.5 32.4001 0 43.0001 0 60.7001C0 88.3001 38 83.9001 38 95.8001C38 100.4 34 101.9 28.4 101.9C20.1 101.9 9.5 98.5002 1.1 93.9002V117.7C10.4 121.7 19.8 123.4 28.4 123.4C49.2 123.4 63.5 113.1 63.5 95.2001C63.4 65.4001 25.3 70.7001 25.3 59.5001Z" fill="#061B31"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,9 @@
<svg width="360" height="151" viewBox="0 0 360 151" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M360 78.0002C360 52.4002 347.6 32.2002 323.9 32.2002C300.1 32.2002 285.7 52.4002 285.7 77.8002C285.7 107.9 302.7 123.1 327.1 123.1C339 123.1 348 120.4 354.8 116.6V96.6002C348 100 340.2 102.1 330.3 102.1C320.6 102.1 312 98.7002 310.9 86.9002H359.8C359.8 85.6002 360 80.4002 360 78.0002ZM310.6 68.5002C310.6 57.2002 317.5 52.5002 323.8 52.5002C329.9 52.5002 336.4 57.2002 336.4 68.5002H310.6Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M247.1 32.2002C237.3 32.2002 231 36.8002 227.5 40.0002L226.2 33.8002H204.2V150.4L229.2 145.1L229.3 116.8C232.9 119.4 238.2 123.1 247 123.1C264.9 123.1 281.2 108.7 281.2 77.0002C281.1 48.0002 264.6 32.2002 247.1 32.2002ZM241.1 101.1C235.2 101.1 231.7 99.0002 229.3 96.4002L229.2 59.3002C231.8 56.4002 235.4 54.4002 241.1 54.4002C250.2 54.4002 256.5 64.6002 256.5 77.7002C256.5 91.1002 250.3 101.1 241.1 101.1Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M169.8 26.3001L194.9 20.9001V0.600098L169.8 5.9001V26.3001Z" fill="white"/>
<path d="M194.9 33.9001H169.8V121.4H194.9V33.9001Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M142.9 41.3001L141.3 33.9001H119.7V121.4H144.7V62.1001C150.6 54.4001 160.6 55.8001 163.7 56.9001V33.9001C160.5 32.7001 148.8 30.5001 142.9 41.3001Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.8999 12.2002L68.4999 17.4002L68.3999 97.5002C68.3999 112.3 79.4999 123.2 94.2999 123.2C102.5 123.2 108.5 121.7 111.8 119.9V99.6002C108.6 100.9 92.7999 105.5 92.7999 90.7002V55.2002H111.8V33.9002H92.7999L92.8999 12.2002Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.3 59.3002C25.3 55.4002 28.5 53.9002 33.8 53.9002C41.4 53.9002 51 56.2002 58.6 60.3002V36.8002C50.3 33.5002 42.1 32.2002 33.8 32.2002C13.5 32.2002 0 42.8002 0 60.5002C0 88.1002 38 83.7002 38 95.6002C38 100.2 34 101.7 28.4 101.7C20.1 101.7 9.5 98.3002 1.1 93.7002V117.5C10.4 121.5 19.8 123.2 28.4 123.2C49.2 123.2 63.5 112.9 63.5 95.0002C63.4 65.2002 25.3 70.5002 25.3 59.3002Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-require-imports */
const { execSync } = require('child_process')
const os = require('os')

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { createPreset } = require('fumadocs-ui/tailwind-plugin')
/**
@@ -87,6 +86,6 @@ module.exports = {
},
},
},
// eslint-disable-next-line @typescript-eslint/no-require-imports
plugins: [require('tailwindcss-animate')],
}