mirror of
https://github.com/hex248/sprint.git
synced 2026-02-07 18:23:03 +00:00
added tanstack query packages and provider components
This commit is contained in:
10
bun.lock
10
bun.lock
@@ -50,6 +50,8 @@
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@sprint/shared": "workspace:*",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tanstack/react-query": "^5.90.19",
|
||||
"@tanstack/react-query-devtools": "^5.91.2",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -424,6 +426,14 @@
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="],
|
||||
|
||||
"@tanstack/query-core": ["@tanstack/query-core@5.90.19", "", {}, "sha512-GLW5sjPVIvH491VV1ufddnfldyVB+teCnpPIvweEfkpRx7CfUmUGhoh9cdcUKBh/KwVxk22aNEDxeTsvmyB/WA=="],
|
||||
|
||||
"@tanstack/query-devtools": ["@tanstack/query-devtools@5.92.0", "", {}, "sha512-N8D27KH1vEpVacvZgJL27xC6yPFUy0Zkezn5gnB3L3gRCxlDeSuiya7fKge8Y91uMTnC8aSxBQhcK6ocY7alpQ=="],
|
||||
|
||||
"@tanstack/react-query": ["@tanstack/react-query@5.90.19", "", { "dependencies": { "@tanstack/query-core": "5.90.19" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-qTZRZ4QyTzQc+M0IzrbKHxSeISUmRB3RPGmao5bT+sI6ayxSRhn0FXEnT5Hg3as8SBFcRosrXXRFB+yAcxVxJQ=="],
|
||||
|
||||
"@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.91.2", "", { "dependencies": { "@tanstack/query-devtools": "5.92.0" }, "peerDependencies": { "@tanstack/react-query": "^5.90.14", "react": "^18 || ^19" } }, "sha512-ZJ1503ay5fFeEYFUdo7LMNFzZryi6B0Cacrgr2h1JRkvikK1khgIq6Nq2EcblqEdIlgB/r7XDW8f8DQ89RuUgg=="],
|
||||
|
||||
"@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="],
|
||||
|
||||
"@tauri-apps/cli": ["@tauri-apps/cli@2.9.6", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.9.6", "@tauri-apps/cli-darwin-x64": "2.9.6", "@tauri-apps/cli-linux-arm-gnueabihf": "2.9.6", "@tauri-apps/cli-linux-arm64-gnu": "2.9.6", "@tauri-apps/cli-linux-arm64-musl": "2.9.6", "@tauri-apps/cli-linux-riscv64-gnu": "2.9.6", "@tauri-apps/cli-linux-x64-gnu": "2.9.6", "@tauri-apps/cli-linux-x64-musl": "2.9.6", "@tauri-apps/cli-win32-arm64-msvc": "2.9.6", "@tauri-apps/cli-win32-ia32-msvc": "2.9.6", "@tauri-apps/cli-win32-x64-msvc": "2.9.6" }, "bin": { "tauri": "tauri.js" } }, "sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw=="],
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@sprint/shared": "workspace:*",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tanstack/react-query": "^5.90.19",
|
||||
"@tanstack/react-query-devtools": "^5.91.2",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
||||
13
packages/frontend/src/components/query-provider.tsx
Normal file
13
packages/frontend/src/components/query-provider.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
import type { ReactNode } from "react";
|
||||
import { queryClient } from "@/lib/query/client";
|
||||
|
||||
export function QueryProvider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
141
packages/frontend/src/components/selection-provider.tsx
Normal file
141
packages/frontend/src/components/selection-provider.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import type { IssueResponse, OrganisationResponse, ProjectResponse } from "@sprint/shared";
|
||||
import type { ReactNode } from "react";
|
||||
import { createContext, useCallback, useContext, useMemo, useState } from "react";
|
||||
|
||||
type SelectionContextValue = {
|
||||
selectedOrganisationId: number | null;
|
||||
selectedProjectId: number | null;
|
||||
selectedIssueId: number | null;
|
||||
initialParams: {
|
||||
orgSlug: string;
|
||||
projectKey: string;
|
||||
issueNumber: number | null;
|
||||
};
|
||||
selectOrganisation: (organisation: OrganisationResponse | null) => void;
|
||||
selectProject: (project: ProjectResponse | null) => void;
|
||||
selectIssue: (issue: IssueResponse | null) => void;
|
||||
};
|
||||
|
||||
const SelectionContext = createContext<SelectionContextValue | null>(null);
|
||||
|
||||
const readStoredId = (key: string) => {
|
||||
const value = localStorage.getItem(key);
|
||||
if (!value) return null;
|
||||
const parsed = Number(value);
|
||||
return Number.isNaN(parsed) ? null : parsed;
|
||||
};
|
||||
|
||||
const updateUrlParams = (updates: {
|
||||
orgSlug?: string | null;
|
||||
projectKey?: string | null;
|
||||
issueNumber?: number | null;
|
||||
}) => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
if (updates.orgSlug !== undefined) {
|
||||
if (updates.orgSlug) params.set("o", updates.orgSlug);
|
||||
else params.delete("o");
|
||||
}
|
||||
|
||||
if (updates.projectKey !== undefined) {
|
||||
if (updates.projectKey) params.set("p", updates.projectKey);
|
||||
else params.delete("p");
|
||||
}
|
||||
|
||||
if (updates.issueNumber !== undefined) {
|
||||
if (updates.issueNumber != null) params.set("i", `${updates.issueNumber}`);
|
||||
else params.delete("i");
|
||||
}
|
||||
|
||||
const search = params.toString();
|
||||
const nextUrl = `${window.location.pathname}${search ? `?${search}` : ""}`;
|
||||
window.history.replaceState(null, "", nextUrl);
|
||||
};
|
||||
|
||||
export function SelectionProvider({ children }: { children: ReactNode }) {
|
||||
const initialParams = useMemo(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const orgSlug = params.get("o")?.trim().toLowerCase() ?? "";
|
||||
const projectKey = params.get("p")?.trim().toLowerCase() ?? "";
|
||||
const issueParam = params.get("i")?.trim() ?? "";
|
||||
const issueNumber = issueParam === "" ? null : Number.parseInt(issueParam, 10);
|
||||
|
||||
return {
|
||||
orgSlug,
|
||||
projectKey,
|
||||
issueNumber: issueNumber != null && Number.isNaN(issueNumber) ? null : issueNumber,
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [selectedOrganisationId, setSelectedOrganisationId] = useState<number | null>(() =>
|
||||
readStoredId("selectedOrganisationId"),
|
||||
);
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<number | null>(() =>
|
||||
readStoredId("selectedProjectId"),
|
||||
);
|
||||
const [selectedIssueId, setSelectedIssueId] = useState<number | null>(null);
|
||||
|
||||
const selectOrganisation = useCallback((organisation: OrganisationResponse | null) => {
|
||||
const id = organisation?.Organisation.id ?? null;
|
||||
setSelectedOrganisationId(id);
|
||||
setSelectedProjectId(null);
|
||||
setSelectedIssueId(null);
|
||||
if (id != null) localStorage.setItem("selectedOrganisationId", `${id}`);
|
||||
else localStorage.removeItem("selectedOrganisationId");
|
||||
localStorage.removeItem("selectedProjectId");
|
||||
updateUrlParams({
|
||||
orgSlug: organisation?.Organisation.slug.toLowerCase() ?? null,
|
||||
projectKey: null,
|
||||
issueNumber: null,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const selectProject = useCallback((project: ProjectResponse | null) => {
|
||||
const id = project?.Project.id ?? null;
|
||||
setSelectedProjectId(id);
|
||||
setSelectedIssueId(null);
|
||||
if (id != null) localStorage.setItem("selectedProjectId", `${id}`);
|
||||
else localStorage.removeItem("selectedProjectId");
|
||||
updateUrlParams({
|
||||
projectKey: project?.Project.key.toLowerCase() ?? null,
|
||||
issueNumber: null,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const selectIssue = useCallback((issue: IssueResponse | null) => {
|
||||
const id = issue?.Issue.id ?? null;
|
||||
setSelectedIssueId(id);
|
||||
updateUrlParams({ issueNumber: issue?.Issue.number ?? null });
|
||||
}, []);
|
||||
|
||||
const value = useMemo<SelectionContextValue>(
|
||||
() => ({
|
||||
selectedOrganisationId,
|
||||
selectedProjectId,
|
||||
selectedIssueId,
|
||||
initialParams,
|
||||
selectOrganisation,
|
||||
selectProject,
|
||||
selectIssue,
|
||||
}),
|
||||
[
|
||||
selectedOrganisationId,
|
||||
selectedProjectId,
|
||||
selectedIssueId,
|
||||
initialParams,
|
||||
selectOrganisation,
|
||||
selectProject,
|
||||
selectIssue,
|
||||
],
|
||||
);
|
||||
|
||||
return <SelectionContext.Provider value={value}>{children}</SelectionContext.Provider>;
|
||||
}
|
||||
|
||||
export function useSelection() {
|
||||
const context = useContext(SelectionContext);
|
||||
if (!context) {
|
||||
throw new Error("useSelection must be used within SelectionProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -2,6 +2,8 @@ import "./App.css";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||
import { QueryProvider } from "@/components/query-provider";
|
||||
import { SelectionProvider } from "@/components/selection-provider";
|
||||
import { RequireAuth, SessionProvider } from "@/components/session-provider";
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
@@ -15,7 +17,9 @@ import Test from "@/pages/Test";
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
||||
<QueryProvider>
|
||||
<SessionProvider>
|
||||
<SelectionProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
{/* public routes */}
|
||||
@@ -45,7 +49,9 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
<Toaster visibleToasts={1} duration={2000} />
|
||||
</SelectionProvider>
|
||||
</SessionProvider>
|
||||
</QueryProvider>
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user