mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 02:33:01 +00:00
replaced per-endpoint helpers with ts-rest contract and typed client
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { UserRecord } from "@sprint/shared";
|
||||
import type { UserResponse } from "@sprint/shared";
|
||||
import { type FormEvent, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import { Field } from "@/components/ui/field";
|
||||
import { useAddOrganisationMember } from "@/lib/query/hooks";
|
||||
import { parseError, user } from "@/lib/server";
|
||||
import { apiClient, parseError } from "@/lib/server";
|
||||
|
||||
export function AddMember({
|
||||
organisationId,
|
||||
@@ -23,7 +23,7 @@ export function AddMember({
|
||||
organisationId: number;
|
||||
existingMembers: string[];
|
||||
trigger?: React.ReactNode;
|
||||
onSuccess?: (user: UserRecord) => void | Promise<void>;
|
||||
onSuccess?: (user: UserResponse) => void | Promise<void>;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [username, setUsername] = useState("");
|
||||
@@ -62,7 +62,10 @@ export function AddMember({
|
||||
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const userData: UserRecord = await user.byUsername(username);
|
||||
const { data, error } = await apiClient.userByUsername({ query: { username } });
|
||||
if (error) throw new Error(error);
|
||||
if (!data) throw new Error("user not found");
|
||||
const userData = data as UserResponse;
|
||||
const userId = userData.id;
|
||||
await addMember.mutateAsync({ organisationId, userId, role: "member" });
|
||||
setOpen(false);
|
||||
|
||||
@@ -34,10 +34,8 @@ export function IssueComments({ issueId, className }: { issueId: number; classNa
|
||||
|
||||
const sortedComments = useMemo(() => {
|
||||
return [...data].sort((a, b) => {
|
||||
const aDate =
|
||||
a.Comment.createdAt instanceof Date ? a.Comment.createdAt : new Date(a.Comment.createdAt ?? 0);
|
||||
const bDate =
|
||||
b.Comment.createdAt instanceof Date ? b.Comment.createdAt : new Date(b.Comment.createdAt ?? 0);
|
||||
const aDate = a.Comment.createdAt ? new Date(a.Comment.createdAt) : new Date(0);
|
||||
const bDate = b.Comment.createdAt ? new Date(b.Comment.createdAt) : new Date(0);
|
||||
return aDate.getTime() - bDate.getTime();
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IssueResponse, SprintRecord, UserRecord } from "@sprint/shared";
|
||||
import type { IssueResponse, SprintRecord, UserResponse } from "@sprint/shared";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { IssueComments } from "@/components/issue-comments";
|
||||
@@ -51,7 +51,7 @@ export function IssueDetails({
|
||||
issueData: IssueResponse;
|
||||
projectKey: string;
|
||||
sprints: SprintRecord[];
|
||||
members: UserRecord[];
|
||||
members: UserResponse[];
|
||||
statuses: Record<string, string>;
|
||||
onClose: () => void;
|
||||
onDelete?: () => void;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UserRecord } from "@sprint/shared";
|
||||
import type { UserResponse } from "@sprint/shared";
|
||||
import Icon from "@/components/ui/icon";
|
||||
import { IconButton } from "@/components/ui/icon-button";
|
||||
import { UserSelect } from "@/components/user-select";
|
||||
@@ -9,10 +9,10 @@ export function MultiAssigneeSelect({
|
||||
onChange,
|
||||
fallbackUsers = [],
|
||||
}: {
|
||||
users: UserRecord[];
|
||||
users: UserResponse[];
|
||||
assigneeIds: string[];
|
||||
onChange: (assigneeIds: string[]) => void;
|
||||
fallbackUsers?: UserRecord[];
|
||||
fallbackUsers?: UserResponse[];
|
||||
}) {
|
||||
const handleAssigneeChange = (index: number, value: string) => {
|
||||
// if set to "unassigned" and there are other rows, remove this row
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
ORG_DESCRIPTION_MAX_LENGTH,
|
||||
ORG_NAME_MAX_LENGTH,
|
||||
ORG_SLUG_MAX_LENGTH,
|
||||
type OrganisationRecord,
|
||||
type OrganisationRecordType,
|
||||
} from "@sprint/shared";
|
||||
import { type FormEvent, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
@@ -41,10 +41,10 @@ export function OrganisationForm({
|
||||
onOpenChange: controlledOnOpenChange,
|
||||
}: {
|
||||
trigger?: React.ReactNode;
|
||||
completeAction?: (org: OrganisationRecord) => void | Promise<void>;
|
||||
completeAction?: (org: OrganisationRecordType) => void | Promise<void>;
|
||||
errorAction?: (errorMessage: string) => void | Promise<void>;
|
||||
mode?: "create" | "edit";
|
||||
existingOrganisation?: OrganisationRecord;
|
||||
existingOrganisation?: OrganisationRecordType;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}) {
|
||||
|
||||
@@ -51,7 +51,7 @@ import {
|
||||
useUpdateOrganisationMemberRole,
|
||||
} from "@/lib/query/hooks";
|
||||
import { queryKeys } from "@/lib/query/keys";
|
||||
import { issue } from "@/lib/server";
|
||||
import { apiClient } from "@/lib/server";
|
||||
import { capitalise, unCamelCase } from "@/lib/utils";
|
||||
import { Switch } from "./ui/switch";
|
||||
|
||||
@@ -370,8 +370,12 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
const handleRemoveStatusClick = async (status: string) => {
|
||||
if (Object.keys(statuses).length <= 1 || !selectedOrganisation) return;
|
||||
try {
|
||||
const data = await issue.statusCount(selectedOrganisation.Organisation.id, status);
|
||||
const count = data.find((item) => item.status === status)?.count ?? 0;
|
||||
const { data, error } = await apiClient.issuesStatusCount({
|
||||
query: { organisationId: selectedOrganisation.Organisation.id, status },
|
||||
});
|
||||
if (error) throw new Error(error);
|
||||
const statusCounts = (data ?? []) as { status: string; count: number }[];
|
||||
const count = statusCounts.find((item) => item.status === status)?.count ?? 0;
|
||||
if (count > 0) {
|
||||
setStatusToRemove(status);
|
||||
setIssuesUsingStatus(count);
|
||||
@@ -546,8 +550,12 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
const handleRemoveTypeClick = async (typeName: string) => {
|
||||
if (Object.keys(issueTypes).length <= 1 || !selectedOrganisation) return;
|
||||
try {
|
||||
const data = await issue.typeCount(selectedOrganisation.Organisation.id, typeName);
|
||||
const count = data.count ?? 0;
|
||||
const { data, error } = await apiClient.issuesTypeCount({
|
||||
query: { organisationId: selectedOrganisation.Organisation.id, type: typeName },
|
||||
});
|
||||
if (error) throw new Error(error);
|
||||
const typeCount = (data ?? { count: 0 }) as { count: number };
|
||||
const count = typeCount.count ?? 0;
|
||||
if (count > 0) {
|
||||
setTypeToRemove(typeName);
|
||||
setIssuesUsingType(count);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UserRecord } from "@sprint/shared";
|
||||
import type { UserResponse } from "@sprint/shared";
|
||||
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
|
||||
import Loading from "@/components/loading";
|
||||
@@ -6,8 +6,8 @@ import { LoginModal } from "@/components/login-modal";
|
||||
import { clearAuth, getServerURL, setCsrfToken } from "@/lib/utils";
|
||||
|
||||
interface SessionContextValue {
|
||||
user: UserRecord | null;
|
||||
setUser: (user: UserRecord) => void;
|
||||
user: UserResponse | null;
|
||||
setUser: (user: UserResponse) => void;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export function useSessionSafe(): SessionContextValue | null {
|
||||
}
|
||||
|
||||
// for use inside RequireAuth
|
||||
export function useAuthenticatedSession(): { user: UserRecord; setUser: (user: UserRecord) => void } {
|
||||
export function useAuthenticatedSession(): { user: UserResponse; setUser: (user: UserResponse) => void } {
|
||||
const { user, setUser } = useSession();
|
||||
if (!user) {
|
||||
throw new Error("useAuthenticatedSession must be used within RequireAuth");
|
||||
@@ -37,11 +37,11 @@ export function useAuthenticatedSession(): { user: UserRecord; setUser: (user: U
|
||||
}
|
||||
|
||||
export function SessionProvider({ children }: { children: React.ReactNode }) {
|
||||
const [user, setUserState] = useState<UserRecord | null>(null);
|
||||
const [user, setUserState] = useState<UserResponse | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const fetched = useRef(false);
|
||||
|
||||
const setUser = useCallback((user: UserRecord) => {
|
||||
const setUser = useCallback((user: UserResponse) => {
|
||||
setUserState(user);
|
||||
localStorage.setItem("user", JSON.stringify(user));
|
||||
}, []);
|
||||
@@ -57,7 +57,7 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
|
||||
if (!res.ok) {
|
||||
throw new Error(`auth check failed: ${res.status}`);
|
||||
}
|
||||
const data = (await res.json()) as { user: UserRecord; csrfToken: string };
|
||||
const data = (await res.json()) as { user: UserResponse; csrfToken: string };
|
||||
setUser(data.user);
|
||||
setCsrfToken(data.csrfToken);
|
||||
})
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { UserRecord } from "@sprint/shared";
|
||||
import type { UserResponse } from "@sprint/shared";
|
||||
import Avatar from "@/components/avatar";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export default function SmallUserDisplay({ user, className }: { user: UserRecord; className?: string }) {
|
||||
export default function SmallUserDisplay({ user, className }: { user: UserResponse; className?: string }) {
|
||||
return (
|
||||
<div className={cn("flex gap-2 items-center", className)}>
|
||||
<Avatar
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SprintRecord, UserRecord } from "@sprint/shared";
|
||||
import type { SprintRecord } from "@sprint/shared";
|
||||
import { useState } from "react";
|
||||
import SmallSprintDisplay from "@/components/small-sprint-display";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
@@ -12,7 +12,6 @@ export function SprintSelect({
|
||||
sprints: SprintRecord[];
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
fallbackUser?: UserRecord | null;
|
||||
placeholder?: string;
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UserRecord } from "@sprint/shared";
|
||||
import type { UserResponse } from "@sprint/shared";
|
||||
import { useState } from "react";
|
||||
import SmallUserDisplay from "@/components/small-user-display";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
@@ -10,10 +10,10 @@ export function UserSelect({
|
||||
fallbackUser,
|
||||
placeholder = "Select user",
|
||||
}: {
|
||||
users: UserRecord[];
|
||||
users: UserResponse[];
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
fallbackUser?: UserRecord | null;
|
||||
fallbackUser?: UserResponse | null;
|
||||
placeholder?: string;
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
Reference in New Issue
Block a user