diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml
index 25e15b0c..af7e2b84 100644
--- a/.github/workflows/coding-style.yml
+++ b/.github/workflows/coding-style.yml
@@ -25,18 +25,13 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Setup Node
- uses: actions/setup-node@v4
+ - name: Setup Biome
+ uses: biomejs/setup-biome@v2
with:
- node-version: 18.x
- cache: yarn
- cache-dependency-path: front/yarn.lock
+ version: latest
- - name: Install dependencies
- run: yarn install --immutable
-
- - name: Lint
- run: yarn lint && yarn format
+ - name: Run Biome
+ run: biome ci .
scanner:
name: "Lint scanner/autosync"
diff --git a/front/.eslintrc.json b/front/.eslintrc.json
deleted file mode 100755
index e7c4afae..00000000
--- a/front/.eslintrc.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
- "extends": ["next/core-web-vitals", "prettier"],
- "plugins": ["header"],
- "settings": {
- "next": {
- "rootDir": "apps/web/"
- }
- },
- "rules": {
- "@next/next/no-img-element": "off",
- "header/header": [
- "error",
- "block",
- [
- "",
- " * Kyoo - A portable and vast media library solution.",
- " * Copyright (c) Kyoo.",
- " *",
- " * See AUTHORS.md and LICENSE file in the project root for full license information.",
- " *",
- " * Kyoo is free software: you can redistribute it and/or modify",
- " * it under the terms of the GNU General Public License as published by",
- " * the Free Software Foundation, either version 3 of the License, or",
- " * any later version.",
- " *",
- " * Kyoo is distributed in the hope that it will be useful,",
- " * but WITHOUT ANY WARRANTY; without even the implied warranty of",
- " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
- " * GNU General Public License for more details.",
- " *",
- " * You should have received a copy of the GNU General Public License",
- " * along with Kyoo. If not, see .",
- " "
- ],
- 2
- ]
- }
-}
diff --git a/front/.prettierignore b/front/.prettierignore
deleted file mode 100644
index 61c3bc75..00000000
--- a/front/.prettierignore
+++ /dev/null
@@ -1 +0,0 @@
-.yarn
diff --git a/front/apps/mobile/app.config.js b/front/apps/mobile/app.config.js
index a8ee7317..b0abf42a 100644
--- a/front/apps/mobile/app.config.js
+++ b/front/apps/mobile/app.config.js
@@ -42,9 +42,6 @@ const config = {
icon: "./assets/icon.png",
userInterfaceStyle: "automatic",
splash,
- updates: {
- fallbackToCacheTimeout: 0,
- },
assetBundlePatterns: ["**/*"],
ios: {
supportsTablet: true,
@@ -59,6 +56,7 @@ const config = {
},
updates: {
url: "https://u.expo.dev/55de6b52-c649-4a15-9a45-569ff5ed036c",
+ fallbackToCacheTimeout: 0,
},
runtimeVersion: {
policy: "sdkVersion",
diff --git a/front/apps/mobile/app/(app)/(tabs)/_layout.tsx b/front/apps/mobile/app/(app)/(tabs)/_layout.tsx
index 814bb0aa..9f6f1d21 100644
--- a/front/apps/mobile/app/(app)/(tabs)/_layout.tsx
+++ b/front/apps/mobile/app/(app)/(tabs)/_layout.tsx
@@ -19,10 +19,10 @@
*/
import { Icon } from "@kyoo/primitives";
-import { Tabs } from "expo-router";
-import Home from "@material-symbols/svg-400/rounded/home-fill.svg";
import Browse from "@material-symbols/svg-400/rounded/browse-fill.svg";
import Downloading from "@material-symbols/svg-400/rounded/downloading-fill.svg";
+import Home from "@material-symbols/svg-400/rounded/home-fill.svg";
+import { Tabs } from "expo-router";
export default function TabsLayout() {
return (
diff --git a/front/apps/mobile/app/(public)/_layout.tsx b/front/apps/mobile/app/(public)/_layout.tsx
index cb81a20c..90dfb3de 100644
--- a/front/apps/mobile/app/(public)/_layout.tsx
+++ b/front/apps/mobile/app/(public)/_layout.tsx
@@ -18,7 +18,7 @@
* along with Kyoo. If not, see .
*/
-import { Account, ConnectionErrorContext, useAccount } from "@kyoo/models";
+import { type Account, ConnectionErrorContext, useAccount } from "@kyoo/models";
import { NavbarProfile, NavbarTitle } from "@kyoo/ui";
import { Redirect, Stack } from "expo-router";
import { useContext, useRef } from "react";
@@ -30,7 +30,7 @@ export default function PublicLayout() {
const { error } = useContext(ConnectionErrorContext);
const oldAccount = useRef(account);
- if (account && !error && account != oldAccount.current) return ;
+ if (account && !error && account !== oldAccount.current) return ;
oldAccount.current = account;
return (
diff --git a/front/apps/mobile/app/_layout.tsx b/front/apps/mobile/app/_layout.tsx
index 304d291e..e43a8d28 100644
--- a/front/apps/mobile/app/_layout.tsx
+++ b/front/apps/mobile/app/_layout.tsx
@@ -20,39 +20,39 @@
import "react-native-reanimated";
-import { PortalProvider } from "@gorhom/portal";
-import { SnackbarProvider, ThemeSelector } from "@kyoo/primitives";
-import { DownloadProvider } from "@kyoo/ui";
-import { AccountProvider, createQueryClient, storage, useUserTheme } from "@kyoo/models";
-import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
-import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
-import i18next from "i18next";
-import { Slot } from "expo-router";
-import { getLocales } from "expo-localization";
-import * as SplashScreen from "expo-splash-screen";
import {
- useFonts,
Poppins_300Light,
Poppins_400Regular,
Poppins_900Black,
+ useFonts,
} from "@expo-google-fonts/poppins";
-import { ReactNode, useEffect, useState } from "react";
-import { useColorScheme } from "react-native";
-import { initReactI18next } from "react-i18next";
-import { ThemeProvider as RNThemeProvider } from "@react-navigation/native";
-import "intl-pluralrules";
-import "@formatjs/intl-locale/polyfill";
-import "@formatjs/intl-displaynames/polyfill";
import "@formatjs/intl-displaynames/locale-data/en";
import "@formatjs/intl-displaynames/locale-data/fr";
+import "@formatjs/intl-displaynames/polyfill";
+import "@formatjs/intl-locale/polyfill";
+import { PortalProvider } from "@gorhom/portal";
+import { AccountProvider, createQueryClient, storage, useUserTheme } from "@kyoo/models";
+import { SnackbarProvider, ThemeSelector } from "@kyoo/primitives";
+import { DownloadProvider } from "@kyoo/ui";
+import { ThemeProvider as RNThemeProvider } from "@react-navigation/native";
+import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
+import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
+import { getLocales } from "expo-localization";
+import { Slot } from "expo-router";
+import * as SplashScreen from "expo-splash-screen";
+import i18next from "i18next";
+import "intl-pluralrules";
+import { type ReactNode, useEffect, useState } from "react";
+import { initReactI18next } from "react-i18next";
+import { useColorScheme } from "react-native";
+import NetInfo from "@react-native-community/netinfo";
+import { onlineManager } from "@tanstack/react-query";
+import { useTheme } from "yoshiki/native";
// TODO: use a backend to load jsons.
import en from "../../../translations/en.json";
import fr from "../../../translations/fr.json";
import zh from "../../../translations/zh.json";
-import { useTheme } from "yoshiki/native";
-import NetInfo from "@react-native-community/netinfo";
-import { onlineManager } from "@tanstack/react-query";
onlineManager.setEventListener((setOnline) => {
return NetInfo.addEventListener((state) => {
diff --git a/front/apps/mobile/app/utils.tsx b/front/apps/mobile/app/utils.tsx
index d63e4332..85725f30 100644
--- a/front/apps/mobile/app/utils.tsx
+++ b/front/apps/mobile/app/utils.tsx
@@ -18,14 +18,14 @@
* along with Kyoo. If not, see .
*/
-import { Stack, useLocalSearchParams } from "expo-router";
-import { ComponentType, useEffect } from "react";
-import { StatusBar, StatusBarProps } from "react-native";
-import * as ScreenOrientation from "expo-screen-orientation";
-import * as NavigationBar from "expo-navigation-bar";
-import arrayShuffle from "array-shuffle";
-import { QueryPage, useHasPermission } from "@kyoo/models";
+import { type QueryPage, useHasPermission } from "@kyoo/models";
import { Unauthorized } from "@kyoo/ui";
+import arrayShuffle from "array-shuffle";
+import * as NavigationBar from "expo-navigation-bar";
+import { Stack, useLocalSearchParams } from "expo-router";
+import * as ScreenOrientation from "expo-screen-orientation";
+import { type ComponentType, useEffect } from "react";
+import { StatusBar, type StatusBarProps } from "react-native";
const FullscreenProvider = () => {
useEffect(() => {
diff --git a/front/apps/mobile/metro.config.js b/front/apps/mobile/metro.config.js
index e1528c71..9ba7f3d0 100644
--- a/front/apps/mobile/metro.config.js
+++ b/front/apps/mobile/metro.config.js
@@ -19,7 +19,7 @@
*/
const { getDefaultConfig } = require("expo/metro-config");
-const path = require("path");
+const path = require("node:path");
const projectRoot = __dirname;
const defaultConfig = getDefaultConfig(projectRoot);
diff --git a/front/apps/mobile/tsconfig.json b/front/apps/mobile/tsconfig.json
index be0953e7..c4a1038b 100644
--- a/front/apps/mobile/tsconfig.json
+++ b/front/apps/mobile/tsconfig.json
@@ -1,6 +1,6 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
- "strict": true,
- },
+ "strict": true
+ }
}
diff --git a/front/apps/web/next.config.js b/front/apps/web/next.config.js
index 366daaeb..a97614aa 100755
--- a/front/apps/web/next.config.js
+++ b/front/apps/web/next.config.js
@@ -18,7 +18,7 @@
* along with Kyoo. If not, see .
*/
-const path = require("path");
+const path = require("node:path");
const CopyPlugin = require("copy-webpack-plugin");
const DefinePlugin = require("webpack").DefinePlugin;
diff --git a/front/apps/web/package.json b/front/apps/web/package.json
index acd29d76..8388d969 100644
--- a/front/apps/web/package.json
+++ b/front/apps/web/package.json
@@ -2,9 +2,7 @@
"name": "web",
"version": "0.1.0",
"private": true,
- "sideEffects": [
- "./src/polyfill.ts"
- ],
+ "sideEffects": ["./src/polyfill.ts"],
"scripts": {
"dev": "next dev",
"build": "next build",
@@ -56,8 +54,6 @@
"@types/react": "18.2.48",
"@types/react-dom": "18.2.18",
"copy-webpack-plugin": "^12.0.2",
- "eslint": "^8.56.0",
- "eslint-config-next": "14.1.0",
"react-native": "0.73.2",
"typescript": "^5.3.3",
"webpack": "^5.90.0"
diff --git a/front/apps/web/src/i18n-d.ts b/front/apps/web/src/i18n-d.ts
index eeee0be0..46c3f118 100644
--- a/front/apps/web/src/i18n-d.ts
+++ b/front/apps/web/src/i18n-d.ts
@@ -19,7 +19,7 @@
*/
import "i18next";
-import en from "../../../translations/en.json";
+import type en from "../../../translations/en.json";
declare module "i18next" {
interface CustomTypeOptions {
diff --git a/front/apps/web/src/i18n.tsx b/front/apps/web/src/i18n.tsx
index fb144e0a..418e6604 100644
--- a/front/apps/web/src/i18n.tsx
+++ b/front/apps/web/src/i18n.tsx
@@ -18,10 +18,10 @@
* along with Kyoo. If not, see .
*/
-import { ComponentType, useMemo } from "react";
-import i18next, { InitOptions } from "i18next";
+import i18next, { type InitOptions } from "i18next";
+import type { AppContext, AppInitialProps, AppProps } from "next/app";
+import { type ComponentType, useMemo } from "react";
import { I18nextProvider } from "react-i18next";
-import { AppContext, AppInitialProps, type AppProps } from "next/app";
import en from "../../../translations/en.json";
import fr from "../../../translations/fr.json";
@@ -40,18 +40,15 @@ export const withTranslations = (
};
const AppWithTranslations = (props: AppProps) => {
- const li18n = useMemo(
- () =>
- typeof window === "undefined"
- ? i18n
- : (i18next.init({
- ...commonOptions,
- lng: props.pageProps.__lang,
- resources: props.pageProps.__resources,
- }),
- i18next),
- [props.pageProps.__lang, props.pageProps.__resources],
- );
+ const li18n = useMemo(() => {
+ if (typeof window === "undefined") return i18n;
+ i18next.init({
+ ...commonOptions,
+ lng: props.pageProps.__lang,
+ resources: props.pageProps.__resources,
+ });
+ return i18next;
+ }, [props.pageProps.__lang, props.pageProps.__resources]);
return (
diff --git a/front/apps/web/src/pages/_app.tsx b/front/apps/web/src/pages/_app.tsx
index 1744cd7c..7b3c156c 100755
--- a/front/apps/web/src/pages/_app.tsx
+++ b/front/apps/web/src/pages/_app.tsx
@@ -20,42 +20,42 @@
import "../polyfill";
-import { HydrationBoundary, QueryClientProvider, dehydrate } from "@tanstack/react-query";
-import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
-import {
- HiddenIfNoJs,
- TouchOnlyCss,
- SkeletonCss,
- ThemeSelector,
- SnackbarProvider,
-} from "@kyoo/primitives";
-import { WebTooltip } from "@kyoo/primitives/src/tooltip.web";
+import { PortalProvider } from "@gorhom/portal";
import {
AccountP,
AccountProvider,
ConnectionErrorContext,
+ type QueryIdentifier,
+ type QueryPage,
+ ServerInfoP,
+ UserP,
createQueryClient,
fetchQuery,
getTokenWJ,
- QueryIdentifier,
- QueryPage,
- ServerInfoP,
setSsrApiUrl,
- UserP,
useUserTheme,
} from "@kyoo/models";
-import { ComponentType, useContext, useState } from "react";
-import NextApp, { AppContext, type AppProps } from "next/app";
-import { Poppins } from "next/font/google";
-import { useTheme, useMobileHover, useStyleRegistry, StyleRegistryProvider } from "yoshiki/web";
-import superjson from "superjson";
-import Head from "next/head";
-import { withTranslations } from "../i18n";
-import arrayShuffle from "array-shuffle";
-import { Tooltip } from "react-tooltip";
import { getCurrentAccount, readCookie, updateAccount } from "@kyoo/models/src/account-internal";
-import { PortalProvider } from "@gorhom/portal";
+import {
+ HiddenIfNoJs,
+ SkeletonCss,
+ SnackbarProvider,
+ ThemeSelector,
+ TouchOnlyCss,
+} from "@kyoo/primitives";
+import { WebTooltip } from "@kyoo/primitives/src/tooltip.web";
import { ConnectionError } from "@kyoo/ui";
+import { HydrationBoundary, QueryClientProvider, dehydrate } from "@tanstack/react-query";
+import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
+import arrayShuffle from "array-shuffle";
+import NextApp, { type AppContext, type AppProps } from "next/app";
+import { Poppins } from "next/font/google";
+import Head from "next/head";
+import { type ComponentType, useContext, useState } from "react";
+import { Tooltip } from "react-tooltip";
+import superjson from "superjson";
+import { StyleRegistryProvider, useMobileHover, useStyleRegistry, useTheme } from "yoshiki/web";
+import { withTranslations } from "../i18n";
const font = Poppins({ weight: ["300", "400", "900"], subsets: ["latin"], display: "swap" });
@@ -114,7 +114,6 @@ const GlobalCssTheme = () => {
const YoshikiDebug = ({ children }: { children: JSX.Element }) => {
if (typeof window === "undefined") return children;
- // eslint-disable-next-line react-hooks/rules-of-hooks
const registry = useStyleRegistry();
return {children};
};
diff --git a/front/apps/web/src/pages/_document.tsx b/front/apps/web/src/pages/_document.tsx
index 68ef3fc9..7583f1dd 100644
--- a/front/apps/web/src/pages/_document.tsx
+++ b/front/apps/web/src/pages/_document.tsx
@@ -18,9 +18,9 @@
* along with Kyoo. If not, see .
*/
+import { type DocumentContext, Head, Html, Main, NextScript } from "next/document";
import { AppRegistry } from "react-native";
-import { Html, Main, Head, NextScript, DocumentContext } from "next/document";
-import { createStyleRegistry, StyleRegistryProvider } from "yoshiki/web";
+import { StyleRegistryProvider, createStyleRegistry } from "yoshiki/web";
export const style = `
/**
diff --git a/front/apps/web/src/router.tsx b/front/apps/web/src/router.tsx
index 63642990..dee9eb60 100644
--- a/front/apps/web/src/router.tsx
+++ b/front/apps/web/src/router.tsx
@@ -18,10 +18,10 @@
* along with Kyoo. If not, see .
*/
-import { QueryPage, useHasPermission } from "@kyoo/models";
+import { type QueryPage, useHasPermission } from "@kyoo/models";
import { Unauthorized } from "@kyoo/ui";
import { useRouter } from "next/router";
-import { ComponentType } from "react";
+import type { ComponentType } from "react";
export const withRoute = (
Component: ComponentType,
diff --git a/front/apps/web/tsconfig.json b/front/apps/web/tsconfig.json
index ce0fd788..0cd6e2a1 100755
--- a/front/apps/web/tsconfig.json
+++ b/front/apps/web/tsconfig.json
@@ -16,9 +16,9 @@
"incremental": true,
"baseUrl": ".",
"paths": {
- "~/*": ["src/*"],
- },
+ "~/*": ["src/*"]
+ }
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
- "exclude": ["node_modules"],
+ "exclude": ["node_modules"]
}
diff --git a/front/biome.json b/front/biome.json
new file mode 100644
index 00000000..5dda7011
--- /dev/null
+++ b/front/biome.json
@@ -0,0 +1,51 @@
+{
+ "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
+ "formatter": {
+ "enabled": true,
+ "formatWithErrors": false,
+ "indentStyle": "tab",
+ "indentWidth": 2,
+ "lineEnding": "lf",
+ "lineWidth": 100,
+ "attributePosition": "auto",
+ "ignore": ["**/.yarn/**", "**/.next/**", "**/.expo/**", "**/next-env.d.ts"]
+ },
+ "organizeImports": {
+ "enabled": true
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true,
+ "style": {
+ "noNonNullAssertion": "off",
+ "noParameterAssign": "off",
+ "useEnumInitializers": "off"
+ },
+ "suspicious": {
+ "noExplicitAny": "off",
+ "noArrayIndexKey": "off"
+ },
+ "security": {
+ "noDangerouslySetInnerHtml": "off"
+ },
+ "complexity": {
+ "noBannedTypes": "off"
+ }
+ },
+ "ignore": ["**/.yarn/**", "**/.next/**", "**/.expo/**", "**/next-env.d.ts"]
+ },
+ "javascript": {
+ "formatter": {
+ "jsxQuoteStyle": "double",
+ "quoteProperties": "asNeeded",
+ "trailingComma": "all",
+ "semicolons": "always",
+ "arrowParentheses": "always",
+ "bracketSpacing": true,
+ "bracketSameLine": false,
+ "quoteStyle": "double",
+ "attributePosition": "auto"
+ }
+ }
+}
diff --git a/front/package.json b/front/package.json
index af68b360..346c815b 100644
--- a/front/package.json
+++ b/front/package.json
@@ -10,28 +10,13 @@
"build:mobile:apk": "yarn workspace mobile build:apk",
"build:mobile:dev": "yarn workspace mobile build:dev",
"update": "yarn workspace mobile update",
- "lint": "eslint .",
- "format": "prettier -c .",
- "format:fix": "prettier -w ."
- },
- "eslintIgnore": [
- "next-env.d.ts"
- ],
- "workspaces": [
- "apps/*",
- "packages/*"
- ],
- "prettier": {
- "useTabs": true,
- "printWidth": 100,
- "trailingComma": "all"
+ "lint": "biome lint .",
+ "format": "biome format .",
+ "format:fix": "biome format . --write"
},
+ "workspaces": ["apps/*", "packages/*"],
"devDependencies": {
- "eslint": "8.56.0",
- "eslint-config-next": "14.1.0",
- "eslint-config-prettier": "^9.1.0",
- "eslint-plugin-header": "^3.1.1",
- "prettier": "^3.2.4",
+ "@biomejs/biome": "1.7.3",
"typescript": "5.3.3"
},
"packageManager": "yarn@3.2.4"
diff --git a/front/packages/models/src/account-internal.ts b/front/packages/models/src/account-internal.ts
index c8a859c1..6d5ca73e 100644
--- a/front/packages/models/src/account-internal.ts
+++ b/front/packages/models/src/account-internal.ts
@@ -18,10 +18,10 @@
* along with Kyoo. If not, see .
*/
-import { ZodTypeAny, z } from "zod";
-import { Account, AccountP } from "./accounts";
-import { MMKV } from "react-native-mmkv";
import { Platform } from "react-native";
+import { MMKV } from "react-native-mmkv";
+import { type ZodTypeAny, z } from "zod";
+import { type Account, AccountP } from "./accounts";
export const storage = new MMKV();
@@ -49,8 +49,8 @@ export const setCookie = (key: string, val?: unknown) => {
const d = new Date();
// A year
d.setTime(d.getTime() + 365 * 24 * 60 * 60 * 1000);
- const expires = value ? "expires=" + d.toUTCString() : "expires=Thu, 01 Jan 1970 00:00:01 GMT";
- document.cookie = key + "=" + value + ";" + expires + ";path=/;samesite=strict";
+ const expires = value ? `expires=${d.toUTCString()}` : "expires=Thu, 01 Jan 1970 00:00:01 GMT";
+ document.cookie = `${key}=${value};${expires};path=/;samesite=strict`;
return null;
};
@@ -65,10 +65,10 @@ export const readCookie = (
const ca = decodedCookie.split(";");
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
- while (c.charAt(0) == " ") {
+ while (c.charAt(0) === " ") {
c = c.substring(1);
}
- if (c.indexOf(name) == 0) {
+ if (c.indexOf(name) === 0) {
const str = c.substring(name.length, c.length);
return parser ? parser.parse(JSON.parse(str)) : str;
}
@@ -85,7 +85,7 @@ export const addAccount = (account: Account) => {
const accounts = readAccounts();
// Prevent the user from adding the same account twice.
- if (accounts.find((x) => x.id == account.id)) {
+ if (accounts.find((x) => x.id === account.id)) {
updateAccount(account.id, account);
return;
}
@@ -106,7 +106,7 @@ export const removeAccounts = (filter: (acc: Account) => boolean) => {
export const updateAccount = (id: string, account: Account) => {
const accounts = readAccounts();
- const idx = accounts.findIndex((x) => x.id == id);
+ const idx = accounts.findIndex((x) => x.id === id);
if (idx === -1) return;
const selected = account.selected;
diff --git a/front/packages/models/src/accounts.tsx b/front/packages/models/src/accounts.tsx
index 668710f9..5dbb98d4 100644
--- a/front/packages/models/src/accounts.tsx
+++ b/front/packages/models/src/accounts.tsx
@@ -18,17 +18,25 @@
* along with Kyoo. If not, see .
*/
-import { ReactNode, createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
-import { ServerInfoP, User, UserP } from "./resources";
-import { z } from "zod";
-import { zdate } from "./utils";
-import { removeAccounts, setCookie, updateAccount } from "./account-internal";
-import { useMMKVString } from "react-native-mmkv";
-import { Platform } from "react-native";
import { useQueryClient } from "@tanstack/react-query";
import { atom, getDefaultStore, useAtomValue, useSetAtom } from "jotai";
+import {
+ type ReactNode,
+ createContext,
+ useContext,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { Platform } from "react-native";
+import { useMMKVString } from "react-native-mmkv";
+import { z } from "zod";
+import { removeAccounts, setCookie, updateAccount } from "./account-internal";
+import type { KyooErrors } from "./kyoo-errors";
import { useFetch } from "./query";
-import { KyooErrors } from "./kyoo-errors";
+import { ServerInfoP, type User, UserP } from "./resources";
+import { zdate } from "./utils";
export const TokenP = z.object({
token_type: z.literal("Bearer"),
@@ -72,7 +80,6 @@ export const ConnectionErrorContext = createContext<{
setError: (error: KyooErrors) => void;
}>({ error: null, loading: true, setError: () => {} });
-/* eslint-disable react-hooks/rules-of-hooks */
export const AccountProvider = ({
children,
ssrAccount,
@@ -115,7 +122,7 @@ export const AccountProvider = ({
acc?.map((account) => ({
...account,
select: () => updateAccount(account.id, { ...account, selected: true }),
- remove: () => removeAccounts((x) => x.id == x.id),
+ remove: () => removeAccounts((x) => x.id === account.id),
})) ?? [],
[acc],
);
@@ -151,6 +158,7 @@ export const AccountProvider = ({
useEffect(() => {
// if the user change account (or connect/disconnect), reset query cache.
if (
+ // biome-ignore lint/suspicious/noDoubleEquals: id can be an id, null or undefined
selected?.id != oldSelected.current?.id ||
(userIsError && selected?.token.access_token !== oldSelected.current?.token)
) {
diff --git a/front/packages/models/src/login.ts b/front/packages/models/src/login.ts
index e9799a12..e846d74d 100644
--- a/front/packages/models/src/login.ts
+++ b/front/packages/models/src/login.ts
@@ -18,13 +18,13 @@
* along with Kyoo. If not, see .
*/
-import { queryFn } from "./query";
-import { KyooErrors } from "./kyoo-errors";
-import { Account, Token, TokenP, getCurrentApiUrl } from "./accounts";
-import { UserP } from "./resources";
-import { addAccount, getCurrentAccount, removeAccounts, updateAccount } from "./account-internal";
-import { Platform } from "react-native";
import { useEffect, useRef, useState } from "react";
+import { Platform } from "react-native";
+import { addAccount, getCurrentAccount, removeAccounts, updateAccount } from "./account-internal";
+import { type Account, type Token, TokenP, getCurrentApiUrl } from "./accounts";
+import type { KyooErrors } from "./kyoo-errors";
+import { queryFn } from "./query";
+import { UserP } from "./resources";
type Result =
| { ok: true; value: A; error?: undefined }
@@ -94,7 +94,7 @@ let running: ReturnType | null = null;
export const getTokenWJ = async (
acc?: Account | null,
- forceRefresh: boolean = false,
+ forceRefresh = false,
): Promise => {
if (acc === undefined) acc = getCurrentAccount();
if (!acc) return [null, null, null] as const;
@@ -150,6 +150,7 @@ export const useToken = () => {
account ? `${account.token.token_type} ${account.token.access_token}` : null,
);
+ // biome-ignore lint/correctness/useExhaustiveDependencies: Refresh token when account change
useEffect(() => {
async function run() {
const nToken = await getTokenWJ();
diff --git a/front/packages/models/src/query.tsx b/front/packages/models/src/query.tsx
index e7311a0b..c39978c6 100644
--- a/front/packages/models/src/query.tsx
+++ b/front/packages/models/src/query.tsx
@@ -18,18 +18,18 @@
* along with Kyoo. If not, see .
*/
-import { ComponentType, ReactElement } from "react";
import {
QueryClient,
- QueryFunctionContext,
+ type QueryFunctionContext,
useInfiniteQuery,
useQuery,
} from "@tanstack/react-query";
-import { z } from "zod";
-import { KyooErrors } from "./kyoo-errors";
-import { Page, Paged } from "./page";
-import { getToken, getTokenWJ } from "./login";
+import type { ComponentType, ReactElement } from "react";
+import type { z } from "zod";
import { getCurrentApiUrl } from ".";
+import type { KyooErrors } from "./kyoo-errors";
+import { getToken, getTokenWJ } from "./login";
+import { type Page, Paged } from "./page";
export let lastUsedUrl: string = null!;
@@ -66,7 +66,7 @@ export const queryFn = async (
.join("/")
.replace("//", "/")
.replace("/?", "?");
- let resp;
+ let resp: Response;
try {
resp = await fetch(path, {
method: context.method,
@@ -97,11 +97,11 @@ export const queryFn = async (
if (resp.status === 403 && iToken === undefined && token) {
const [newToken, _, error] = await getTokenWJ(undefined, true);
if (newToken) return await queryFn(context, type, newToken);
- else console.error("refresh error while retrying a forbidden", error);
+ console.error("refresh error while retrying a forbidden", error);
}
if (!resp.ok) {
const error = await resp.text();
- let data;
+ let data: Record;
try {
data = JSON.parse(error);
} catch (e) {
@@ -122,7 +122,7 @@ export const queryFn = async (
if ("plainText" in context && context.plainText) return (await resp.text()) as unknown;
- let data;
+ let data: Record;
try {
data = await resp.json();
} catch (e) {
@@ -204,11 +204,10 @@ export const toQueryKey = (query: {
query.options?.apiUrl,
...query.path,
query.params
- ? "?" +
- Object.entries(query.params)
+ ? `?${Object.entries(query.params)
.filter(([_, v]) => v !== undefined)
.map(([k, v]) => `${k}=${Array.isArray(v) ? v.join(",") : v}`)
- .join("&")
+ .join("&")}`
: null,
].filter((x) => x);
};
@@ -267,12 +266,11 @@ export const fetchQuery = async (queries: QueryIdentifier[], authToken?: string
queryFn: (ctx) => queryFn(ctx, Paged(query.parser), authToken),
initialPageParam: undefined,
});
- } else {
- return client.prefetchQuery({
- queryKey: toQueryKey(query),
- queryFn: (ctx) => queryFn(ctx, query.parser, authToken),
- });
}
+ return client.prefetchQuery({
+ queryKey: toQueryKey(query),
+ queryFn: (ctx) => queryFn(ctx, query.parser, authToken),
+ });
}),
);
return client;
diff --git a/front/packages/models/src/resources/episode.base.ts b/front/packages/models/src/resources/episode.base.ts
index 4908c773..aa9280c6 100644
--- a/front/packages/models/src/resources/episode.base.ts
+++ b/front/packages/models/src/resources/episode.base.ts
@@ -19,9 +19,9 @@
*/
import { z } from "zod";
-import { zdate } from "../utils";
import { ImagesP, imageFn } from "../traits";
import { ResourceP } from "../traits/resource";
+import { zdate } from "../utils";
export const BaseEpisodeP = ResourceP("episode")
.merge(ImagesP)
diff --git a/front/packages/models/src/resources/episode.ts b/front/packages/models/src/resources/episode.ts
index 5aa19dde..bac4ee07 100644
--- a/front/packages/models/src/resources/episode.ts
+++ b/front/packages/models/src/resources/episode.ts
@@ -19,8 +19,8 @@
*/
import { z } from "zod";
-import { ShowP } from "./show";
import { BaseEpisodeP } from "./episode.base";
+import { ShowP } from "./show";
import { WatchStatusP } from "./watch-status";
export const EpisodeP = BaseEpisodeP.and(
diff --git a/front/packages/models/src/resources/movie.ts b/front/packages/models/src/resources/movie.ts
index 7554df1a..74108101 100644
--- a/front/packages/models/src/resources/movie.ts
+++ b/front/packages/models/src/resources/movie.ts
@@ -19,13 +19,13 @@
*/
import { z } from "zod";
-import { zdate } from "../utils";
import { ImagesP, ResourceP, imageFn } from "../traits";
-import { Genre } from "./genre";
-import { StudioP } from "./studio";
-import { Status } from "./show";
+import { zdate } from "../utils";
import { CollectionP } from "./collection";
+import { Genre } from "./genre";
import { MetadataP } from "./metadata";
+import { Status } from "./show";
+import { StudioP } from "./studio";
import { WatchStatusP } from "./watch-status";
export const MovieP = ResourceP("movie")
diff --git a/front/packages/models/src/resources/news.ts b/front/packages/models/src/resources/news.ts
index 30f724a7..0e04ab27 100644
--- a/front/packages/models/src/resources/news.ts
+++ b/front/packages/models/src/resources/news.ts
@@ -19,8 +19,8 @@
*/
import { z } from "zod";
-import { MovieP } from "./movie";
import { EpisodeP } from "./episode";
+import { MovieP } from "./movie";
export const NewsP = z.union([
/*
diff --git a/front/packages/models/src/resources/season.ts b/front/packages/models/src/resources/season.ts
index 9c5c1fa2..6229d56d 100644
--- a/front/packages/models/src/resources/season.ts
+++ b/front/packages/models/src/resources/season.ts
@@ -19,8 +19,8 @@
*/
import { z } from "zod";
-import { zdate } from "../utils";
import { ImagesP, ResourceP } from "../traits";
+import { zdate } from "../utils";
export const SeasonP = ResourceP("season").merge(ImagesP).extend({
/**
diff --git a/front/packages/models/src/resources/server-info.ts b/front/packages/models/src/resources/server-info.ts
index 7266e89d..370ba6d2 100644
--- a/front/packages/models/src/resources/server-info.ts
+++ b/front/packages/models/src/resources/server-info.ts
@@ -18,9 +18,9 @@
* along with Kyoo. If not, see .
*/
+import { Platform } from "react-native";
import { z } from "zod";
import { imageFn } from "..";
-import { Platform } from "react-native";
export const OidcInfoP = z.object({
/*
diff --git a/front/packages/models/src/resources/show.ts b/front/packages/models/src/resources/show.ts
index 8b8ee32c..c6b52c54 100644
--- a/front/packages/models/src/resources/show.ts
+++ b/front/packages/models/src/resources/show.ts
@@ -19,12 +19,12 @@
*/
import { z } from "zod";
-import { zdate } from "../utils";
import { ImagesP, ResourceP } from "../traits";
-import { Genre } from "./genre";
-import { StudioP } from "./studio";
+import { zdate } from "../utils";
import { BaseEpisodeP } from "./episode.base";
+import { Genre } from "./genre";
import { MetadataP } from "./metadata";
+import { StudioP } from "./studio";
import { ShowWatchStatusP } from "./watch-status";
/**
diff --git a/front/packages/models/src/resources/user.ts b/front/packages/models/src/resources/user.ts
index f17ea5d5..0f087a10 100644
--- a/front/packages/models/src/resources/user.ts
+++ b/front/packages/models/src/resources/user.ts
@@ -19,8 +19,8 @@
*/
import { z } from "zod";
-import { ResourceP } from "../traits/resource";
import { imageFn } from "../traits/images";
+import { ResourceP } from "../traits/resource";
export const UserP = ResourceP("user")
.extend({
diff --git a/front/packages/models/src/resources/watch-info.ts b/front/packages/models/src/resources/watch-info.ts
index de8d01d9..1fd8ce09 100644
--- a/front/packages/models/src/resources/watch-info.ts
+++ b/front/packages/models/src/resources/watch-info.ts
@@ -18,9 +18,9 @@
* along with Kyoo. If not, see .
*/
+import i18next from "i18next";
import { z } from "zod";
import { imageFn } from "../traits";
-import i18next from "i18next";
import { QualityP } from "./quality";
const getDisplayName = (sub: Track) => {
@@ -196,8 +196,9 @@ export const WatchInfoP = z
// from https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
const humanFileSize = (size: number): string => {
- var i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
+ const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
// @ts-ignore I'm not gonna fix stackoverflow's working code.
+ // biome-ignore lint: same as above
return (size / Math.pow(1024, i)).toFixed(2) * 1 + " " + ["B", "kB", "MB", "GB", "TB"][i];
};
diff --git a/front/packages/models/src/theme.ts b/front/packages/models/src/theme.ts
index 01523423..36ccb119 100644
--- a/front/packages/models/src/theme.ts
+++ b/front/packages/models/src/theme.ts
@@ -18,13 +18,12 @@
* along with Kyoo. If not, see .
*/
+import { Platform } from "react-native";
import { useMMKVString } from "react-native-mmkv";
import { setCookie, storage } from "./account-internal";
-import { Platform } from "react-native";
export const useUserTheme = (ssrTheme?: "light" | "dark" | "auto") => {
if (Platform.OS === "web" && typeof window === "undefined" && ssrTheme) return ssrTheme;
- // eslint-disable-next-line react-hooks/rules-of-hooks
const [value] = useMMKVString("theme", storage);
if (!value) return "auto";
return value as "light" | "dark" | "auto";
diff --git a/front/packages/models/src/utils.ts b/front/packages/models/src/utils.ts
index 3ec22907..6d3557de 100644
--- a/front/packages/models/src/utils.ts
+++ b/front/packages/models/src/utils.ts
@@ -19,10 +19,10 @@
*/
import { Platform } from "react-native";
-import { Movie, Show } from "./resources";
-import { z } from "zod";
import { useMMKVString } from "react-native-mmkv";
+import { z } from "zod";
import { storage } from "./account-internal";
+import type { Movie, Show } from "./resources";
export const zdate = z.coerce.date;
@@ -38,7 +38,8 @@ export const getDisplayDate = (data: Show | Movie) => {
return startAir.getFullYear().toString();
}
return startAir.getFullYear() + (endAir ? ` - ${endAir.getFullYear()}` : "");
- } else if (airDate) {
+ }
+ if (airDate) {
return airDate.getFullYear().toString();
}
};
diff --git a/front/packages/models/tsconfig.json b/front/packages/models/tsconfig.json
index b8a9f496..8376e1ef 100755
--- a/front/packages/models/tsconfig.json
+++ b/front/packages/models/tsconfig.json
@@ -18,9 +18,9 @@
"incremental": true,
"baseUrl": ".",
"paths": {
- "~/*": ["src/*"],
- },
+ "~/*": ["src/*"]
+ }
},
"include": ["**/*.ts", "**/*.tsx"],
- "exclude": ["node_modules"],
+ "exclude": ["node_modules"]
}
diff --git a/front/packages/primitives/src/alert.tsx b/front/packages/primitives/src/alert.tsx
index d87f147e..12e2210e 100644
--- a/front/packages/primitives/src/alert.tsx
+++ b/front/packages/primitives/src/alert.tsx
@@ -20,8 +20,8 @@
// Stolen from https://github.com/necolas/react-native-web/issues/1026#issuecomment-1458279681
-import { Alert as RNAlert, type AlertOptions, type AlertButton } from "react-native";
-import { type SweetAlertIcon } from "sweetalert2";
+import { type AlertButton, type AlertOptions, Alert as RNAlert } from "react-native";
+import type { SweetAlertIcon } from "sweetalert2";
export interface ExtendedAlertStatic {
alert: (
diff --git a/front/packages/primitives/src/alert.web.tsx b/front/packages/primitives/src/alert.web.tsx
index 6ba044e0..84c32ca0 100644
--- a/front/packages/primitives/src/alert.web.tsx
+++ b/front/packages/primitives/src/alert.web.tsx
@@ -20,9 +20,10 @@
// Stolen from https://github.com/necolas/react-native-web/issues/1026#issuecomment-1458279681
-import { type AlertButton, type AlertOptions } from "react-native";
+import type { AlertButton, AlertOptions } from "react-native";
import Swal, { type SweetAlertIcon } from "sweetalert2";
+// biome-ignore lint/complexity/noStaticOnlyClass: Compatibility with rn
export class Alert {
static alert(
title: string,
diff --git a/front/packages/primitives/src/avatar.tsx b/front/packages/primitives/src/avatar.tsx
index da72377d..a2bcea47 100644
--- a/front/packages/primitives/src/avatar.tsx
+++ b/front/packages/primitives/src/avatar.tsx
@@ -18,23 +18,22 @@
* along with Kyoo. If not, see .
*/
-import { View, ViewStyle, Image, ImageProps } from "react-native";
-import { useYoshiki, px, Stylable } from "yoshiki/native";
+import AccountCircle from "@material-symbols/svg-400/rounded/account_circle-fill.svg";
+import { type ComponentType, type RefAttributes, forwardRef } from "react";
+import { Image, type ImageProps, View, type ViewStyle } from "react-native";
+import { type Stylable, px, useYoshiki } from "yoshiki/native";
import { Icon } from "./icons";
import { P } from "./text";
-import AccountCircle from "@material-symbols/svg-400/rounded/account_circle-fill.svg";
-import { ComponentType, forwardRef, RefAttributes } from "react";
const stringToColor = (string: string) => {
let hash = 0;
- let i;
- for (i = 0; i < string.length; i += 1) {
+ for (let i = 0; i < string.length; i += 1) {
hash = string.charCodeAt(i) + ((hash << 5) - hash);
}
let color = "#";
- for (i = 0; i < 3; i += 1) {
+ for (let i = 0; i < 3; i += 1) {
const value = (hash >> (i * 8)) & 0xff;
color += `00${value.toString(16)}`.slice(-2);
}
diff --git a/front/packages/primitives/src/button.tsx b/front/packages/primitives/src/button.tsx
index 5f902793..bb63ca10 100644
--- a/front/packages/primitives/src/button.tsx
+++ b/front/packages/primitives/src/button.tsx
@@ -18,12 +18,12 @@
* along with Kyoo. If not, see .
*/
-import { ComponentType, ForwardedRef, ReactElement, forwardRef } from "react";
-import { Theme, useYoshiki } from "yoshiki/native";
+import { type ComponentType, type ForwardedRef, type ReactElement, forwardRef } from "react";
+import { type Falsy, type PressableProps, View } from "react-native";
+import { type Theme, useYoshiki } from "yoshiki/native";
import { PressableFeedback } from "./links";
import { P } from "./text";
import { ts } from "./utils";
-import { Falsy, PressableProps, View } from "react-native";
export const Button = forwardRef(function Button(
{
diff --git a/front/packages/primitives/src/chip.tsx b/front/packages/primitives/src/chip.tsx
index 1c757ce8..a4930fb7 100644
--- a/front/packages/primitives/src/chip.tsx
+++ b/front/packages/primitives/src/chip.tsx
@@ -18,12 +18,12 @@
* along with Kyoo. If not, see .
*/
-import { px, rem, Theme, useYoshiki } from "yoshiki/native";
+import type { TextProps } from "react-native";
+import { type Theme, px, rem, useYoshiki } from "yoshiki/native";
import { Link } from "./links";
+import { Skeleton } from "./skeleton";
import { P } from "./text";
import { capitalize, ts } from "./utils";
-import { TextProps } from "react-native";
-import { Skeleton } from "./skeleton";
export const Chip = ({
color,
@@ -49,7 +49,7 @@ export const Chip = ({
textProps ??= {};
- const sizeMult = size == "medium" ? 1 : size == "small" ? 0.5 : 1.5;
+ const sizeMult = size === "medium" ? 1 : size === "small" ? 0.5 : 1.5;
return (
.
*/
-import { ComponentType } from "react";
-import { View, ViewProps } from "react-native";
+import type { ComponentType } from "react";
+import { View, type ViewProps } from "react-native";
import { percent, px, useYoshiki } from "yoshiki/native";
-export const Container = ({
+export const Container = ({
as,
...props
}: { as?: ComponentType } & AsProps) => {
diff --git a/front/packages/primitives/src/divider.tsx b/front/packages/primitives/src/divider.tsx
index 8b37cdac..03176ce7 100644
--- a/front/packages/primitives/src/divider.tsx
+++ b/front/packages/primitives/src/divider.tsx
@@ -19,7 +19,7 @@
*/
import { HR as EHR } from "@expo/html-elements";
-import { px, Stylable, useYoshiki } from "yoshiki/native";
+import { type Stylable, px, useYoshiki } from "yoshiki/native";
import { ts } from "./utils";
export const HR = ({
diff --git a/front/packages/primitives/src/icons.tsx b/front/packages/primitives/src/icons.tsx
index a8cd8791..3c504c33 100644
--- a/front/packages/primitives/src/icons.tsx
+++ b/front/packages/primitives/src/icons.tsx
@@ -18,14 +18,15 @@
* along with Kyoo. If not, see .
*/
-import React, { ComponentProps, ComponentType, ForwardedRef, forwardRef } from "react";
-import { Platform, PressableProps } from "react-native";
-import { SvgProps } from "react-native-svg";
-import { YoshikiStyle } from "yoshiki";
-import { px, Stylable, Theme, useYoshiki } from "yoshiki/native";
+import type React from "react";
+import { type ComponentProps, type ComponentType, type ForwardedRef, forwardRef } from "react";
+import { Platform, type PressableProps } from "react-native";
+import type { SvgProps } from "react-native-svg";
+import type { YoshikiStyle } from "yoshiki";
+import { type Stylable, type Theme, px, useYoshiki } from "yoshiki/native";
import { PressableFeedback } from "./links";
-import { Breakpoint, focusReset, ts } from "./utils";
import { P } from "./text";
+import { type Breakpoint, focusReset, ts } from "./utils";
declare module "react" {
function forwardRef(
@@ -109,7 +110,7 @@ export const IconButton = forwardRef(function IconButton(
+export const IconFab = (
props: ComponentProps>,
) => {
const { css, theme } = useYoshiki();
diff --git a/front/packages/primitives/src/image/base-image.tsx b/front/packages/primitives/src/image/base-image.tsx
index d44dbc68..33f4b251 100644
--- a/front/packages/primitives/src/image/base-image.tsx
+++ b/front/packages/primitives/src/image/base-image.tsx
@@ -18,10 +18,10 @@
* along with Kyoo. If not, see .
*/
-import { KyooImage } from "@kyoo/models";
-import { ReactElement } from "react";
-import { ImageStyle } from "react-native";
-import { YoshikiStyle } from "yoshiki/src/type";
+import type { KyooImage } from "@kyoo/models";
+import type { ReactElement } from "react";
+import type { ImageStyle } from "react-native";
+import type { YoshikiStyle } from "yoshiki/src/type";
export type YoshikiEnhanced