feat(frontend): adds typescript-eslint
This commit is contained in:
@@ -6,10 +6,12 @@ const authenticate = async (
|
||||
code: string,
|
||||
privateAccess: boolean,
|
||||
userKey: string,
|
||||
) => {
|
||||
): Promise<string> => {
|
||||
try {
|
||||
const fullUrl = `https://${HOST}/api/authenticate?code=${code}&private_access=${privateAccess}&user_key=${userKey}`;
|
||||
const result = await axios.post(fullUrl);
|
||||
const result = await axios.post<{ userId: string; needDowngrade: boolean }>(
|
||||
fullUrl,
|
||||
);
|
||||
const { userId, needDowngrade } = result.data;
|
||||
if (needDowngrade) {
|
||||
console.info(
|
||||
@@ -24,12 +26,17 @@ const authenticate = async (
|
||||
}
|
||||
};
|
||||
|
||||
interface UserMetaDataResponse {
|
||||
token: string;
|
||||
privateAccess: string;
|
||||
}
|
||||
|
||||
const getUserMetadata = async (
|
||||
userKey: string,
|
||||
): Promise<null | { token: string; privateAccess: string }> => {
|
||||
): Promise<null | UserMetaDataResponse> => {
|
||||
try {
|
||||
const fullUrl = `https://${HOST}/api/user-access?user_key=${userKey}`;
|
||||
const result = await axios.get(fullUrl);
|
||||
const result = await axios.get<UserMetaDataResponse>(fullUrl);
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -29,7 +29,7 @@ axios.get = cachedAxios.get.bind(cachedAxios);
|
||||
axios.post = cachedAxios.post.bind(cachedAxios);
|
||||
|
||||
export function clearAxiosCache(): void {
|
||||
cachedAxios.storage.clear?.();
|
||||
void cachedAxios.storage.clear?.();
|
||||
}
|
||||
|
||||
function createMockResponse<TData, TConfig>(
|
||||
@@ -54,7 +54,7 @@ function createMockResponse<TData, TConfig>(
|
||||
}
|
||||
|
||||
// store shouldMock outside React context so the interceptor can access it
|
||||
let shouldMock: boolean = false;
|
||||
let shouldMock = false;
|
||||
|
||||
export function setShouldMock(newShouldMock: boolean): void {
|
||||
shouldMock = newShouldMock;
|
||||
@@ -68,7 +68,17 @@ axios.defaults.adapter = async (config) => {
|
||||
return defaultAdapter(config);
|
||||
}
|
||||
|
||||
const params = config.data ? JSON.parse(config.data) : {};
|
||||
interface Params {
|
||||
query?: string;
|
||||
variables?: {
|
||||
login: string;
|
||||
repo: string;
|
||||
gistName: string;
|
||||
};
|
||||
}
|
||||
const params = (
|
||||
config.data ? JSON.parse(config.data as string) : {}
|
||||
) as Params;
|
||||
|
||||
if (
|
||||
config.url === "https://api.github.com/graphql" &&
|
||||
@@ -103,8 +113,9 @@ axios.defaults.adapter = async (config) => {
|
||||
if (
|
||||
config.url === "https://api.github.com/graphql" &&
|
||||
params.query?.includes("fragment RepoInfo on Repository {") &&
|
||||
params.variables?.login === "anuraghazra" &&
|
||||
params.variables?.repo === "github-readme-stats"
|
||||
params.variables &&
|
||||
params.variables.login === "anuraghazra" &&
|
||||
params.variables.repo === "github-readme-stats"
|
||||
) {
|
||||
return createMockResponse(repository, config);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ interface SvgInlineProps {
|
||||
forceLoading?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function SvgInline(props: SvgInlineProps): JSX.Element {
|
||||
const {
|
||||
url,
|
||||
@@ -30,7 +33,7 @@ export function SvgInline(props: SvgInlineProps): JSX.Element {
|
||||
forceLoading = false,
|
||||
} = props;
|
||||
|
||||
const [svg, setSvg] = useState(null);
|
||||
const [svg, setSvg] = useState<string | null>(null);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const userToken = useUserToken();
|
||||
@@ -49,7 +52,7 @@ export function SvgInline(props: SvgInlineProps): JSX.Element {
|
||||
|
||||
setLoaded(false);
|
||||
|
||||
let body;
|
||||
let body: string;
|
||||
let status;
|
||||
|
||||
if (isAuthenticated && (!userToken || userToken === "placeholderPAT")) {
|
||||
@@ -58,7 +61,7 @@ export function SvgInline(props: SvgInlineProps): JSX.Element {
|
||||
}
|
||||
|
||||
if (stage === 4 && !isAuthenticated) {
|
||||
let res = await axios.get(url);
|
||||
const res = await axios.get<string>(url);
|
||||
body = res.data;
|
||||
status = res.status;
|
||||
} else {
|
||||
@@ -67,8 +70,10 @@ export function SvgInline(props: SvgInlineProps): JSX.Element {
|
||||
url,
|
||||
});
|
||||
const res = createMockResponse();
|
||||
// will be solved by npm package
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
await router(req, res);
|
||||
body = res._getBody();
|
||||
body = res._getBody() as string;
|
||||
status = res._getStatusCode();
|
||||
}
|
||||
|
||||
@@ -83,7 +88,7 @@ export function SvgInline(props: SvgInlineProps): JSX.Element {
|
||||
setSvg(body);
|
||||
setLoaded(true);
|
||||
};
|
||||
loadSvg();
|
||||
void loadSvg();
|
||||
|
||||
return () => {
|
||||
isCurrent = false;
|
||||
|
||||
@@ -46,7 +46,7 @@ const options: Array<SelectOption> = [
|
||||
];
|
||||
|
||||
interface LanguagesLayoutSectionProps {
|
||||
selectedLanguageLayoutOption: SelectOption;
|
||||
selectedLanguageLayoutOption: SelectOption | undefined;
|
||||
onLanguageLayoutOptionChange: (option: SelectOption) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export function NumericSection({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
debounceTimeout.current = setTimeout(() => {
|
||||
debounceTimeout.current = window.setTimeout(() => {
|
||||
const maybeNumber = internalValue && parseInt(internalValue, 10);
|
||||
if (typeof maybeNumber !== "number" || Number.isNaN(maybeNumber)) {
|
||||
onValueChange(undefined);
|
||||
@@ -50,7 +50,7 @@ export function NumericSection({
|
||||
return () => {
|
||||
clearTimeout(debounceTimeout.current as number);
|
||||
};
|
||||
}, [internalValue]);
|
||||
}, [internalValue, onValueChange, value]);
|
||||
|
||||
useEffect(() => {
|
||||
setInternalValue(value?.toString());
|
||||
@@ -63,7 +63,9 @@ export function NumericSection({
|
||||
type="number"
|
||||
className="border border-gray-300 rounded px-2 py-1 mt-2 w-1/4"
|
||||
value={internalValue ?? ""}
|
||||
onChange={(e) => setInternalValue(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setInternalValue(e.target.value);
|
||||
}}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
|
||||
@@ -75,11 +75,13 @@ export function ProgressBar({
|
||||
return (
|
||||
<ProgressSection
|
||||
num={index}
|
||||
key={index}
|
||||
key={item} // each step should have a unique name
|
||||
item={item}
|
||||
passed={currItemIndex >= index}
|
||||
isActive={currItemIndex === index}
|
||||
onClick={() => onItemClick(index)}
|
||||
onClick={() => {
|
||||
onItemClick(index);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -89,9 +91,9 @@ export function ProgressBar({
|
||||
"text-gray-400 cursor-not-allowed": rightDisabled,
|
||||
"text-gray-700 cursor-pointer": !rightDisabled,
|
||||
})}
|
||||
onClick={() =>
|
||||
onItemClick(Math.min(currItemIndex + 1, items.length - 1))
|
||||
}
|
||||
onClick={() => {
|
||||
onItemClick(Math.min(currItemIndex + 1, items.length - 1));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ const options: Array<SelectOption> = [
|
||||
];
|
||||
|
||||
interface StatsRankSectionProps {
|
||||
selectedOption: SelectOption;
|
||||
selectedOption: SelectOption | undefined;
|
||||
onOptionChange: (option: SelectOption) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,17 +35,19 @@ export function TextSection({
|
||||
useEffect(() => {
|
||||
// Debounce onValueChange
|
||||
if (debounceTimeout.current) {
|
||||
clearTimeout(debounceTimeout.current);
|
||||
window.clearTimeout(debounceTimeout.current);
|
||||
}
|
||||
if (internalValue === value) {
|
||||
return undefined;
|
||||
}
|
||||
debounceTimeout.current = setTimeout(() => {
|
||||
debounceTimeout.current = window.setTimeout(() => {
|
||||
onValueChange(internalValue);
|
||||
}, 700);
|
||||
// return cleanup function:
|
||||
return () => clearTimeout(debounceTimeout.current as number);
|
||||
}, [internalValue]);
|
||||
return () => {
|
||||
window.clearTimeout(debounceTimeout.current as number);
|
||||
};
|
||||
}, [internalValue, onValueChange, value]);
|
||||
|
||||
return (
|
||||
<Section title={title}>
|
||||
@@ -57,7 +59,9 @@ export function TextSection({
|
||||
{ "cursor-not-allowed": disabled },
|
||||
)}
|
||||
value={internalValue}
|
||||
onChange={(e) => setInternalValue(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setInternalValue(e.target.value);
|
||||
}}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
onPaste={onPaste}
|
||||
|
||||
@@ -28,7 +28,7 @@ const options: Array<SelectOption> = [
|
||||
];
|
||||
|
||||
interface WakatimeLayoutSectionProps {
|
||||
selectedOption: SelectOption;
|
||||
selectedOption: SelectOption | undefined;
|
||||
onOptionChange: (option: SelectOption) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const PROD = false;
|
||||
const PROD = false as boolean;
|
||||
|
||||
export const USE_LOGGER = true;
|
||||
export const USE_LOGGER = true as boolean;
|
||||
|
||||
export const CLIENT_ID = "Ov23lilAc5biyyRY0K1u";
|
||||
|
||||
@@ -23,4 +23,4 @@ window.process = {
|
||||
FETCH_MULTI_PAGE_STARS: "10",
|
||||
PAT_1: "placeholderPAT", // so the backend's retryer.js sees there is 1 PAT and sets `RETRIES` accordingly
|
||||
},
|
||||
};
|
||||
} as (typeof window)["process"];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Safe browser stub for dotenv
|
||||
export function config(): {} {
|
||||
export function config(): { parsed: Record<string, never> } {
|
||||
return { parsed: {} };
|
||||
}
|
||||
|
||||
Vendored
+8
-6
@@ -1,12 +1,14 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
process: {
|
||||
env: {
|
||||
FETCH_MULTI_PAGE_STARS: string | undefined;
|
||||
PAT_1: string | undefined;
|
||||
};
|
||||
interface CustomProcess {
|
||||
env: {
|
||||
FETCH_MULTI_PAGE_STARS: string | undefined;
|
||||
PAT_1: string | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
interface Window {
|
||||
process?: CustomProcess;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
@@ -52,9 +52,9 @@ interface CreateMockResponseResult {
|
||||
}
|
||||
|
||||
export function createMockResponse(): CreateMockResponseResult {
|
||||
let statusCode = 200;
|
||||
const statusCode = 200;
|
||||
const headers: HeaderMap = {};
|
||||
let chunks: Array<unknown> = [];
|
||||
const chunks: Array<unknown> = [];
|
||||
|
||||
const res: CreateMockResponseResult = {
|
||||
statusCode,
|
||||
|
||||
@@ -25,12 +25,16 @@ const toMessage = (
|
||||
if (typeof input === "string") {
|
||||
return input;
|
||||
}
|
||||
if ("reason" in input && input.reason?.message) {
|
||||
return input.reason.message;
|
||||
type MaybeErrorReason = { message: string } | null;
|
||||
const reason = ("reason" in input ? input.reason : null) as MaybeErrorReason;
|
||||
if (reason?.message === "string") {
|
||||
return reason.message as string;
|
||||
}
|
||||
|
||||
if ("message" in input && input.message) {
|
||||
return input.message;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.stringify(input);
|
||||
} catch {
|
||||
@@ -80,7 +84,7 @@ export function AppTrends() {
|
||||
if (isAuthenticated && stage === 0) {
|
||||
setStage(1);
|
||||
}
|
||||
}, [isAuthenticated]);
|
||||
}, [isAuthenticated, stage]);
|
||||
|
||||
useEffect(() => {
|
||||
async function getPrivateAccess() {
|
||||
@@ -93,8 +97,8 @@ export function AppTrends() {
|
||||
}
|
||||
}
|
||||
}
|
||||
getPrivateAccess();
|
||||
}, [userKey]);
|
||||
void getPrivateAccess();
|
||||
}, [dispatch, userKey]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { JSX } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import BounceLoader from "react-spinners/BounceLoader";
|
||||
@@ -6,7 +6,7 @@ import axios from "axios";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { authenticate } from "../../api/user";
|
||||
import { login as _login } from "../../redux/slices/user";
|
||||
import { login } from "../../redux/slices/user";
|
||||
import {
|
||||
HOST,
|
||||
DEMO_USER,
|
||||
@@ -46,12 +46,8 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const login = (newUserId: string, userKey: string) => {
|
||||
dispatch(_login({ userId: newUserId, userKey }));
|
||||
};
|
||||
|
||||
// for stage two
|
||||
const [selectedUserId, setSelectedUserId] = useState(userId);
|
||||
const [selectedUserId, setSelectedUserId] = useState<string>(userId);
|
||||
const [repo, setRepo] = useState(DEMO_REPO);
|
||||
const [gist, setGist] = useState(DEMO_GIST);
|
||||
const [wakatimeUser, setWakatimeUser] = useState(DEMO_WAKATIME_USER);
|
||||
@@ -230,38 +226,34 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
|
||||
// for stage five
|
||||
const [gistUrl, setGistUrl] = useState("");
|
||||
|
||||
let guestHint;
|
||||
switch (selectedCard) {
|
||||
case CardType.STATS:
|
||||
case CardType.TOP_LANGS:
|
||||
guestHint = `username "${DEMO_USER}"`;
|
||||
break;
|
||||
case CardType.PIN:
|
||||
guestHint = `repo "${DEMO_REPO}"`;
|
||||
break;
|
||||
case CardType.GIST:
|
||||
guestHint = `Gist ID "${DEMO_GIST}"`;
|
||||
break;
|
||||
case CardType.WAKATIME:
|
||||
guestHint = `WakaTime username "${DEMO_WAKATIME_USER}"`;
|
||||
break;
|
||||
default:
|
||||
selectedCard satisfies never;
|
||||
}
|
||||
const guestHint = useMemo(() => {
|
||||
switch (selectedCard) {
|
||||
case CardType.STATS:
|
||||
case CardType.TOP_LANGS:
|
||||
return `username "${DEMO_USER}"`;
|
||||
case CardType.PIN:
|
||||
return `repo "${DEMO_REPO}"`;
|
||||
case CardType.GIST:
|
||||
return `Gist ID "${DEMO_GIST}"`;
|
||||
case CardType.WAKATIME:
|
||||
return `WakaTime username "${DEMO_WAKATIME_USER}"`;
|
||||
default:
|
||||
selectedCard satisfies never;
|
||||
return "";
|
||||
}
|
||||
}, [selectedCard]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchGistUrl = async (gistId: string) => {
|
||||
async function fetchGistURL() {
|
||||
try {
|
||||
const fullUrl = `https://api.github.com/gists/${gistId}`;
|
||||
const result = await axios.get(fullUrl);
|
||||
return result.data.html_url;
|
||||
const fullUrl = `https://api.github.com/gists/${gist}`;
|
||||
const result = await axios.get<{ html_url: string }>(fullUrl);
|
||||
setGistUrl(result.data.html_url);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
fetchGistUrl(gist).then(setGistUrl);
|
||||
}
|
||||
void fetchGistURL();
|
||||
}, [gist]);
|
||||
|
||||
const contentSectionRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -287,7 +279,7 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
|
||||
if (url.includes("code=")) {
|
||||
const tempPrivateAccess = url.includes("private");
|
||||
const newUrl = url.split("?code=", 2) as [string, string];
|
||||
const redirect = `${url.split(HOST)[0]}${HOST}/frontend`;
|
||||
const redirect = `${url.split(HOST)[0] as string}${HOST}/frontend`;
|
||||
window.history.pushState({}, "", redirect);
|
||||
setIsLoading(true);
|
||||
const userKey = uuidv4();
|
||||
@@ -296,13 +288,15 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
|
||||
tempPrivateAccess,
|
||||
userKey,
|
||||
);
|
||||
login(newUserId, userKey);
|
||||
|
||||
dispatch(login({ userId: newUserId, userKey }));
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
redirectCode();
|
||||
}, []);
|
||||
void redirectCode();
|
||||
}, [dispatch]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -377,7 +371,7 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
|
||||
)}
|
||||
{stage === 2 && (
|
||||
<CustomizeStage
|
||||
selectedCard={selectedCard || CardType.STATS}
|
||||
selectedCard={selectedCard}
|
||||
selectedStatsRank={selectedStatsRank}
|
||||
setSelectedStatsRank={setSelectedStatsRank}
|
||||
selectedLanguagesLayout={selectedLanguagesLayout}
|
||||
@@ -428,7 +422,6 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
|
||||
)}
|
||||
{stage === 4 && (
|
||||
<DisplayStage
|
||||
// eslint-disable-next-line consistent-return
|
||||
filename={(() => {
|
||||
switch (selectedCard) {
|
||||
case CardType.STATS:
|
||||
@@ -445,7 +438,6 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
|
||||
return "";
|
||||
}
|
||||
})()}
|
||||
// eslint-disable-next-line consistent-return
|
||||
link={(() => {
|
||||
switch (selectedCard) {
|
||||
case CardType.STATS:
|
||||
|
||||
@@ -101,7 +101,7 @@ export function CustomizeStage({
|
||||
fullSuffix,
|
||||
setStage,
|
||||
}: CustomizeStageProps): JSX.Element {
|
||||
const cardType = selectedCard || CardType.STATS;
|
||||
const cardType = selectedCard;
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
return (
|
||||
@@ -142,7 +142,7 @@ export function CustomizeStage({
|
||||
if (newValue.endsWith("/")) {
|
||||
newValue = newValue.slice(0, -1);
|
||||
}
|
||||
let parts = newValue.split("/");
|
||||
const parts = newValue.split("/");
|
||||
if (parts.length > 1) {
|
||||
newValue = parts.slice(-1).join("/");
|
||||
}
|
||||
@@ -186,7 +186,7 @@ export function CustomizeStage({
|
||||
if (newValue.endsWith("/")) {
|
||||
newValue = newValue.slice(0, -1);
|
||||
}
|
||||
let parts = newValue.split("/");
|
||||
const parts = newValue.split("/");
|
||||
if (parts.length > 2) {
|
||||
newValue = parts.slice(-2).join("/");
|
||||
}
|
||||
@@ -230,7 +230,7 @@ export function CustomizeStage({
|
||||
if (newValue.endsWith("/")) {
|
||||
newValue = newValue.slice(0, -1);
|
||||
}
|
||||
let parts = newValue.split("/");
|
||||
const parts = newValue.split("/");
|
||||
if (parts.length > 1) {
|
||||
newValue = parts.slice(-1).join("/");
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export function DisplayStage({
|
||||
};
|
||||
|
||||
const copyMarkdown = () => {
|
||||
navigator.clipboard.writeText(
|
||||
void navigator.clipboard.writeText(
|
||||
`[](${link})`,
|
||||
);
|
||||
toast.info("Copied to Clipboard!", {
|
||||
@@ -48,7 +48,7 @@ export function DisplayStage({
|
||||
};
|
||||
|
||||
const copyUrl = () => {
|
||||
navigator.clipboard.writeText(`https://${HOST}/api${themeSuffix}`);
|
||||
void navigator.clipboard.writeText(`https://${HOST}/api${themeSuffix}`);
|
||||
toast.info("Copied to Clipboard!", {
|
||||
position: "bottom-right",
|
||||
autoClose: 1500,
|
||||
@@ -70,11 +70,19 @@ export function DisplayStage({
|
||||
highlight: true,
|
||||
onClick: copyMarkdown,
|
||||
},
|
||||
{ title: "Copy URL", highlight: false, onClick: copyUrl },
|
||||
{ title: "Download PNG", highlight: false, onClick: downloadPNG },
|
||||
].map((item, index) => (
|
||||
{
|
||||
title: "Copy URL",
|
||||
highlight: false,
|
||||
onClick: copyUrl,
|
||||
},
|
||||
{
|
||||
title: "Download PNG",
|
||||
highlight: false,
|
||||
onClick: downloadPNG,
|
||||
},
|
||||
].map((item) => (
|
||||
<Button
|
||||
key={index}
|
||||
key={item.title}
|
||||
className={clsx("m-4 w-60 flex justify-center", {
|
||||
"bg-blue-500 hover:bg-blue-600 text-white": item.highlight,
|
||||
"bg-white hover:bg-gray-100 text-black": !item.highlight,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { JSX, RefObject } from "react";
|
||||
import clsx from "clsx";
|
||||
import { useDispatch } from "react-redux";
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
HOST,
|
||||
} from "../../../constants";
|
||||
import { FaGithub as GithubIcon } from "react-icons/fa";
|
||||
import { logout as _logout } from "../../../redux/slices/user";
|
||||
import { logout } from "../../../redux/slices/user";
|
||||
import {
|
||||
useIsAuthenticated,
|
||||
usePrivateAccess,
|
||||
@@ -25,6 +25,30 @@ import {
|
||||
} from "../../../redux/selectors/userSelectors";
|
||||
import { deleteAccount } from "../../../api/user";
|
||||
|
||||
function useOutsideAlerter(
|
||||
ref: RefObject<HTMLElement | null>,
|
||||
action: () => void,
|
||||
) {
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Alert if clicked on outside of element
|
||||
*/
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the event listener
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
|
||||
return () => {
|
||||
// Unbind the event listener on clean up
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [action, ref]);
|
||||
}
|
||||
|
||||
interface LoginStageProps {
|
||||
onContinueAsGuestClick: () => void;
|
||||
}
|
||||
@@ -33,16 +57,16 @@ export function LoginStage({
|
||||
onContinueAsGuestClick,
|
||||
}: LoginStageProps): JSX.Element {
|
||||
const userId = useUserId();
|
||||
const userKey = useUserKey();
|
||||
const userKey = useUserKey() as string;
|
||||
const privateAccess = usePrivateAccess();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
|
||||
const logout = () => {
|
||||
dispatch(_logout({ userKey: null }));
|
||||
};
|
||||
const handleLogout = useCallback(() => {
|
||||
dispatch(logout({ userKey: null }));
|
||||
}, [dispatch]);
|
||||
|
||||
const openDeleteModal = () => {
|
||||
setDeleteModal(true);
|
||||
@@ -52,37 +76,13 @@ export function LoginStage({
|
||||
setDeleteModal(false);
|
||||
};
|
||||
|
||||
function useOutsideAlerter(
|
||||
ref: RefObject<HTMLElement | null>,
|
||||
action: () => void,
|
||||
) {
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Alert if clicked on outside of element
|
||||
*/
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the event listener
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
|
||||
return () => {
|
||||
// Unbind the event listener on clean up
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [ref]);
|
||||
}
|
||||
|
||||
const wrapperRef = useRef(null);
|
||||
useOutsideAlerter(wrapperRef, closeDeleteModal);
|
||||
|
||||
const deleteAccountHandler = async () => {
|
||||
const success = await deleteAccount(userId as string, userKey as string);
|
||||
const handleAccountDelete = async () => {
|
||||
const success = await deleteAccount(userId as string, userKey);
|
||||
if (success) {
|
||||
logout();
|
||||
handleLogout();
|
||||
window.location.href = `https://github.com/settings/connections/applications/${CLIENT_ID}`;
|
||||
}
|
||||
};
|
||||
@@ -171,7 +171,7 @@ export function LoginStage({
|
||||
<div className="mt-6 flex items-center gap-4">
|
||||
<Button
|
||||
className="h-12 flex justify-center items-center w-[320px] text-black border border-black bg-white hover:bg-gray-100"
|
||||
onClick={logout}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<span className="xl:text-lg">Log Out</span>
|
||||
</Button>
|
||||
@@ -244,6 +244,7 @@ export function LoginStage({
|
||||
|
||||
return (
|
||||
<div
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={index}
|
||||
style={{
|
||||
left: `${x}%`,
|
||||
@@ -282,13 +283,16 @@ export function LoginStage({
|
||||
<div className="flex flex-wrap">
|
||||
<Button
|
||||
className="bg-blue-500 hover:bg-blue-600 text-white rounded-[0.25rem]"
|
||||
onClick={() => setDeleteModal(false)}
|
||||
onClick={() => {
|
||||
setDeleteModal(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="bg-gray-200 hover:bg-gray-300 ml-auto rounded-[0.25rem] text-red-600 border-2"
|
||||
onClick={deleteAccountHandler}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onClick={handleAccountDelete}
|
||||
>
|
||||
Delete Account
|
||||
</Button>
|
||||
|
||||
@@ -67,10 +67,10 @@ export function SelectCardStage({
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-wrap">
|
||||
{options.map((card, index) => (
|
||||
{options.map((card) => (
|
||||
<button
|
||||
className="p-2 lg:p-4"
|
||||
key={index}
|
||||
key={card.cardType}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onCardTypeChange(card.cardType);
|
||||
|
||||
@@ -18,7 +18,8 @@ export function ThemeStage({
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-wrap">
|
||||
{Object.keys(themes)
|
||||
{/* Needed until themes is proper types */}
|
||||
{Object.keys(themes as Record<string, string>)
|
||||
.filter(
|
||||
(myTheme) =>
|
||||
![
|
||||
@@ -30,10 +31,10 @@ export function ThemeStage({
|
||||
"holi",
|
||||
].includes(myTheme),
|
||||
)
|
||||
.map((myTheme, index) => (
|
||||
.map((myTheme) => (
|
||||
<button
|
||||
className="p-2 lg:p-4"
|
||||
key={index}
|
||||
key={myTheme}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onThemeChange(myTheme);
|
||||
|
||||
@@ -2,14 +2,11 @@ import { useSelector } from "react-redux";
|
||||
|
||||
import type { StoreState } from "../store";
|
||||
|
||||
export const useUserId = <
|
||||
TUserName extends string | undefined,
|
||||
TOutput = TUserName extends string ? string : string | null,
|
||||
>(
|
||||
export const useUserId = <TUserName extends string | undefined>(
|
||||
fallbackUsername?: TUserName,
|
||||
): TOutput => {
|
||||
): TUserName => {
|
||||
const storeValue = useSelector((state: StoreState) => state.user.userId);
|
||||
return (storeValue || fallbackUsername || null) as TOutput;
|
||||
return (storeValue || fallbackUsername || null) as TUserName;
|
||||
};
|
||||
|
||||
export const useIsAuthenticated = (): boolean => {
|
||||
|
||||
@@ -14,9 +14,17 @@ export interface UserState {
|
||||
privateAccess: string | null;
|
||||
}
|
||||
|
||||
function getFromLocalStorage(key: string): string | null {
|
||||
const storageValue = localStorage.getItem(key);
|
||||
if (!storageValue) {
|
||||
return null;
|
||||
}
|
||||
return (JSON.parse(storageValue) as string) || null;
|
||||
}
|
||||
|
||||
const initialState: UserState = {
|
||||
userId: JSON.parse(localStorage.getItem("userId") as string) || null,
|
||||
userKey: JSON.parse(localStorage.getItem("userKey") as string) || null,
|
||||
userId: getFromLocalStorage("userId"),
|
||||
userKey: getFromLocalStorage("userKey"),
|
||||
token: null,
|
||||
privateAccess: null,
|
||||
};
|
||||
|
||||
@@ -2,23 +2,22 @@ import axios from "axios";
|
||||
import { HOST } from "./constants";
|
||||
|
||||
// See https://github.com/stats-organization/github-stats-extended/pull/27#discussion_r2712184285
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const fetchWakatimeStats = async ({
|
||||
username,
|
||||
api_domain: _,
|
||||
}: {
|
||||
username: string;
|
||||
api_domain: string;
|
||||
}) => {
|
||||
}): Promise<unknown> => {
|
||||
if (!username) {
|
||||
throw new Error("missing parameter: username");
|
||||
}
|
||||
|
||||
const { data } = await axios.get(
|
||||
const res = await axios.get<unknown>(
|
||||
`https://${HOST}/api/wakatime-proxy?username=${username}`,
|
||||
);
|
||||
|
||||
return data;
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export { fetchWakatimeStats };
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"extends": ["../../tsconfig.base.json"],
|
||||
"include": ["src", "src/**/*.json"],
|
||||
"include": ["src", "src/**/*.json", "vite.config.ts"],
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM"],
|
||||
"composite": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
@@ -27,10 +27,16 @@ export default defineConfig({
|
||||
{
|
||||
name: "empty-pg-package",
|
||||
resolveId(id) {
|
||||
if (id === "pg") return id;
|
||||
if (id === "pg") {
|
||||
return id;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
load(id) {
|
||||
if (id === "pg") return "export default {}";
|
||||
if (id === "pg") {
|
||||
return "export default {}";
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
+39
-4
@@ -7,6 +7,7 @@ import jsdoc from "eslint-plugin-jsdoc";
|
||||
import react from "eslint-plugin-react";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import { includeIgnoreFile } from "@eslint/compat";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
const gitignorePath = fileURLToPath(new URL(".gitignore", import.meta.url));
|
||||
|
||||
@@ -25,8 +26,6 @@ export default defineConfig(
|
||||
},
|
||||
plugins: {
|
||||
jsdoc,
|
||||
react,
|
||||
"react-hooks": reactHooks,
|
||||
},
|
||||
rules: {
|
||||
"no-unexpected-multiline": "error",
|
||||
@@ -64,7 +63,6 @@ export default defineConfig(
|
||||
"no-this-before-super": "error",
|
||||
"object-shorthand": ["warn"],
|
||||
"no-mixed-spaces-and-tabs": "warn",
|
||||
"no-multiple-empty-lines": "warn",
|
||||
"no-negated-condition": "warn",
|
||||
"no-unneeded-ternary": "warn",
|
||||
"keyword-spacing": [
|
||||
@@ -80,6 +78,43 @@ export default defineConfig(
|
||||
"jsdoc/require-jsdoc": "warn",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.{d.ts,ts,tsx}"],
|
||||
ignores: ["apps/backend/**"],
|
||||
extends: [tseslint.configs.strictTypeChecked, tseslint.configs.stylistic],
|
||||
rules: {
|
||||
"@typescript-eslint/array-type": ["error", { default: "generic" }],
|
||||
"@typescript-eslint/restrict-template-expressions": [
|
||||
"error",
|
||||
{
|
||||
allowAny: false,
|
||||
allowBoolean: true, // for query parameters
|
||||
allowNever: false,
|
||||
allowNullish: false,
|
||||
allowNumber: true,
|
||||
allowRegExp: false,
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
args: "all",
|
||||
argsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
|
||||
// We don't need this we have typescript
|
||||
"jsdoc/require-returns": "off",
|
||||
"jsdoc/require-returns-description": "off",
|
||||
"jsdoc/require-param-description": "off",
|
||||
"jsdoc/require-jsdoc": "off",
|
||||
},
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["apps/backend/**/*.{js}"],
|
||||
languageOptions: {
|
||||
@@ -89,7 +124,7 @@ export default defineConfig(
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["apps/frontend/**/*.{js,jsx}"],
|
||||
files: ["apps/frontend/**/*.{js,jsx,ts,tsx}"],
|
||||
plugins: {
|
||||
react,
|
||||
"react-hooks": reactHooks,
|
||||
|
||||
+2
-1
@@ -15,7 +15,8 @@
|
||||
"knip": "5.81.0",
|
||||
"lint-staged": "16.2.7",
|
||||
"prettier": "3.7.4",
|
||||
"typescript": "5.9.3"
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.53.1"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "husky",
|
||||
|
||||
Generated
+187
@@ -44,6 +44,9 @@ importers:
|
||||
typescript:
|
||||
specifier: 5.9.3
|
||||
version: 5.9.3
|
||||
typescript-eslint:
|
||||
specifier: 8.53.1
|
||||
version: 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
|
||||
apps/backend:
|
||||
dependencies:
|
||||
@@ -1339,10 +1342,69 @@ packages:
|
||||
'@types/yargs@17.0.35':
|
||||
resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.53.1':
|
||||
resolution: {integrity: sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': ^8.53.1
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/parser@8.53.1':
|
||||
resolution: {integrity: sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/project-service@8.53.1':
|
||||
resolution: {integrity: sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/scope-manager@8.53.1':
|
||||
resolution: {integrity: sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/tsconfig-utils@8.53.1':
|
||||
resolution: {integrity: sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/type-utils@8.53.1':
|
||||
resolution: {integrity: sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/types@8.52.0':
|
||||
resolution: {integrity: sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/types@8.53.1':
|
||||
resolution: {integrity: sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.53.1':
|
||||
resolution: {integrity: sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/utils@8.53.1':
|
||||
resolution: {integrity: sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/visitor-keys@8.53.1':
|
||||
resolution: {integrity: sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@ungap/structured-clone@1.3.0':
|
||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||
|
||||
@@ -2530,6 +2592,10 @@ packages:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
ignore@7.0.5:
|
||||
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
immer@11.1.3:
|
||||
resolution: {integrity: sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==}
|
||||
|
||||
@@ -3993,6 +4059,12 @@ packages:
|
||||
try@1.0.3:
|
||||
resolution: {integrity: sha512-AHA8khVCII6zKyRkyPo6pRwoR9v5jb7QFw6e5avtaVSkxVfaEucYIo06xnwB+pJaEarfYNbs7W3Vq+LZLZiWyA==}
|
||||
|
||||
ts-api-utils@2.4.0:
|
||||
resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
|
||||
engines: {node: '>=18.12'}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4'
|
||||
|
||||
ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
@@ -4052,6 +4124,13 @@ packages:
|
||||
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
typescript-eslint@8.53.1:
|
||||
resolution: {integrity: sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
@@ -5411,8 +5490,99 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/yargs-parser': 21.0.3
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/scope-manager': 8.53.1
|
||||
'@typescript-eslint/type-utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.53.1
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
ignore: 7.0.5
|
||||
natural-compare: 1.4.0
|
||||
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.53.1
|
||||
'@typescript-eslint/types': 8.53.1
|
||||
'@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.53.1
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/project-service@8.53.1(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3)
|
||||
'@typescript-eslint/types': 8.53.1
|
||||
debug: 4.4.3
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/scope-manager@8.53.1':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.53.1
|
||||
'@typescript-eslint/visitor-keys': 8.53.1
|
||||
|
||||
'@typescript-eslint/tsconfig-utils@8.53.1(typescript@5.9.3)':
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
'@typescript-eslint/type-utils@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.53.1
|
||||
'@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/types@8.52.0': {}
|
||||
|
||||
'@typescript-eslint/types@8.53.1': {}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.53.1(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/project-service': 8.53.1(typescript@5.9.3)
|
||||
'@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3)
|
||||
'@typescript-eslint/types': 8.53.1
|
||||
'@typescript-eslint/visitor-keys': 8.53.1
|
||||
debug: 4.4.3
|
||||
minimatch: 9.0.5
|
||||
semver: 7.7.3
|
||||
tinyglobby: 0.2.15
|
||||
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
|
||||
'@typescript-eslint/scope-manager': 8.53.1
|
||||
'@typescript-eslint/types': 8.53.1
|
||||
'@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3)
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/visitor-keys@8.53.1':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.53.1
|
||||
eslint-visitor-keys: 4.2.1
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
|
||||
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
|
||||
@@ -6821,6 +6991,8 @@ snapshots:
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
ignore@7.0.5: {}
|
||||
|
||||
immer@11.1.3: {}
|
||||
|
||||
import-fresh@3.3.1:
|
||||
@@ -8625,6 +8797,10 @@ snapshots:
|
||||
|
||||
try@1.0.3: {}
|
||||
|
||||
ts-api-utils@2.4.0(typescript@5.9.3):
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
|
||||
ts-node@10.9.2(@swc/core@1.15.8)(@types/node@25.0.3)(typescript@5.9.3):
|
||||
@@ -8702,6 +8878,17 @@ snapshots:
|
||||
possible-typed-array-names: 1.1.0
|
||||
reflect.getprototypeof: 1.0.10
|
||||
|
||||
typescript-eslint@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
unbox-primitive@1.1.0:
|
||||
|
||||
Reference in New Issue
Block a user