first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
import {useMutation} from '@tanstack/react-query';
import {BackendResponse} from '../../http/backend-response/backend-response';
import {toast} from '../../ui/toast/toast';
import {apiClient, queryClient} from '../../http/query-client';
import {WorkspaceQueryKeys} from './workspace-query-keys';
import {WorkspaceMember} from '../types/workspace-member';
import {WorkspaceInvite} from '../types/workspace-invite';
import {message} from '../../i18n/message';
import {showHttpErrorToast} from '../../utils/http/show-http-error-toast';
interface Response extends BackendResponse {}
interface Props {
workspaceId: number;
member: WorkspaceMember | WorkspaceInvite;
roleId: number;
}
function ChangeRole({workspaceId, member, ...other}: Props): Promise<Response> {
const modelType = member.model_type;
const memberId =
member.model_type === 'invite' ? member.id : member.member_id;
return apiClient
.post(
`workspace/${workspaceId}/${modelType}/${memberId}/change-role`,
other,
)
.then(r => r.data);
}
export function useChangeRole() {
return useMutation({
mutationFn: (props: Props) => ChangeRole(props),
onSuccess: (response, props) => {
toast(message('Role changed'));
queryClient.invalidateQueries({
queryKey: WorkspaceQueryKeys.workspaceWithMembers(props.workspaceId),
});
},
onError: err => showHttpErrorToast(err),
});
}

View File

@@ -0,0 +1,34 @@
import {useMutation} from '@tanstack/react-query';
import {UseFormReturn} from 'react-hook-form';
import {BackendResponse} from '../../http/backend-response/backend-response';
import {toast} from '../../ui/toast/toast';
import {apiClient, queryClient} from '../../http/query-client';
import {WorkspaceQueryKeys} from './workspace-query-keys';
import {Workspace} from '../types/workspace';
import {onFormQueryError} from '../../errors/on-form-query-error';
import {message} from '../../i18n/message';
interface Response extends BackendResponse {
workspace: Workspace;
}
interface Props {
name: string;
}
export function useCreateWorkspace(form: UseFormReturn<Props>) {
return useMutation({
mutationFn: (props: Props) => createWorkspace(props),
onSuccess: () => {
toast(message('Created workspace'));
queryClient.invalidateQueries({
queryKey: WorkspaceQueryKeys.fetchUserWorkspaces,
});
},
onError: r => onFormQueryError(r, form),
});
}
function createWorkspace(props: Props): Promise<Response> {
return apiClient.post('workspace', props).then(r => r.data);
}

View File

@@ -0,0 +1,36 @@
import axios from 'axios';
import {useMutation} from '@tanstack/react-query';
import {BackendResponse} from '../../http/backend-response/backend-response';
import {toast} from '../../ui/toast/toast';
import {apiClient, queryClient} from '../../http/query-client';
import {useUserNotifications} from '../../notifications/dialog/requests/user-notifications';
import {message} from '../../i18n/message';
import {showHttpErrorToast} from '../../utils/http/show-http-error-toast';
interface Response extends BackendResponse {}
interface Props {
inviteId: string;
}
function deleteInvite({inviteId}: Props): Promise<Response> {
return apiClient.delete(`workspace/invite/${inviteId}`).then(r => r.data);
}
export function useDeleteInvite() {
return useMutation({
mutationFn: (props: Props) => deleteInvite(props),
onSuccess: () => {
queryClient.invalidateQueries({queryKey: useUserNotifications.key});
toast(message('Declined workspace invitation'));
},
onError: e => {
if (axios.isAxiosError(e) && e.response && e.response.status === 404) {
queryClient.invalidateQueries({queryKey: useUserNotifications.key});
toast.danger(message('This invite is no longer valid'));
} else {
showHttpErrorToast(e);
}
},
});
}

View File

@@ -0,0 +1,41 @@
import {useMutation} from '@tanstack/react-query';
import {BackendResponse} from '../../http/backend-response/backend-response';
import {toast} from '../../ui/toast/toast';
import {apiClient, queryClient} from '../../http/query-client';
import {WorkspaceQueryKeys} from './workspace-query-keys';
import {useActiveWorkspaceId} from '../active-workspace-id-context';
import {PersonalWorkspace} from '../user-workspaces';
import {message} from '../../i18n/message';
import {showHttpErrorToast} from '../../utils/http/show-http-error-toast';
interface Response extends BackendResponse {}
export interface DeleteWorkspacePayload {
id: number;
}
function deleteWorkspace({id}: DeleteWorkspacePayload): Promise<Response> {
return apiClient.delete(`workspace/${id}`).then(r => r.data);
}
export function useDeleteWorkspace() {
const {workspaceId, setWorkspaceId} = useActiveWorkspaceId();
return useMutation({
mutationFn: (props: DeleteWorkspacePayload) => deleteWorkspace(props),
onSuccess: (r, payload) => {
toast(message('Deleted workspace'));
queryClient.invalidateQueries({
queryKey: WorkspaceQueryKeys.fetchUserWorkspaces,
});
queryClient.invalidateQueries({
queryKey: WorkspaceQueryKeys.workspaceWithMembers(payload.id),
});
// if user deleted workspace that is currently active, switch to personal workspace
if (workspaceId === payload.id) {
setWorkspaceId(PersonalWorkspace.id);
}
},
onError: err => showHttpErrorToast(err),
});
}

View File

@@ -0,0 +1,34 @@
import {useMutation} from '@tanstack/react-query';
import {apiClient, queryClient} from '../../http/query-client';
import {BackendResponse} from '../../http/backend-response/backend-response';
import {WorkspaceInvite} from '../types/workspace-invite';
import {WorkspaceQueryKeys} from './workspace-query-keys';
import {showHttpErrorToast} from '../../utils/http/show-http-error-toast';
interface Response extends BackendResponse {
invites: WorkspaceInvite[];
}
interface Props {
workspaceId: number;
emails: string[];
roleId: number;
}
function InviteMembers({workspaceId, ...other}: Props): Promise<Response> {
return apiClient
.post(`workspace/${workspaceId}/invite`, other)
.then(r => r.data);
}
export function useInviteMembers() {
return useMutation({
mutationFn: (props: Props) => InviteMembers(props),
onSuccess: (response, props) => {
queryClient.invalidateQueries({
queryKey: WorkspaceQueryKeys.workspaceWithMembers(props.workspaceId),
});
},
onError: err => showHttpErrorToast(err),
});
}

View File

@@ -0,0 +1,46 @@
import axios from 'axios';
import {useMutation} from '@tanstack/react-query';
import {BackendResponse} from '../../http/backend-response/backend-response';
import {toast} from '../../ui/toast/toast';
import {apiClient, queryClient} from '../../http/query-client';
import {Workspace} from '../types/workspace';
import {WorkspaceQueryKeys} from './workspace-query-keys';
import {useActiveWorkspaceId} from '../active-workspace-id-context';
import {useUserNotifications} from '../../notifications/dialog/requests/user-notifications';
import {message} from '../../i18n/message';
import {showHttpErrorToast} from '../../utils/http/show-http-error-toast';
interface Response extends BackendResponse {
workspace: Workspace;
}
interface Props {
inviteId: string;
}
export function useJoinWorkspace() {
const {setWorkspaceId} = useActiveWorkspaceId() || {};
return useMutation({
mutationFn: (props: Props) => joinWorkspace(props),
onSuccess: response => {
toast(message('Joined workspace'));
setWorkspaceId(response.workspace.id);
queryClient.invalidateQueries({
queryKey: WorkspaceQueryKeys.fetchUserWorkspaces,
});
queryClient.invalidateQueries({queryKey: useUserNotifications.key});
},
onError: e => {
if (axios.isAxiosError(e) && e.response && e.response.status === 404) {
queryClient.invalidateQueries({queryKey: useUserNotifications.key});
toast.danger(message('This invite is no longer valid'));
} else {
showHttpErrorToast(e);
}
},
});
}
function joinWorkspace({inviteId}: Props): Promise<Response> {
return apiClient.get(`workspace/join/${inviteId}`).then(r => r.data);
}

View File

@@ -0,0 +1,50 @@
import {useMutation} from '@tanstack/react-query';
import {BackendResponse} from '../../http/backend-response/backend-response';
import {apiClient, queryClient} from '../../http/query-client';
import {WorkspaceQueryKeys} from './workspace-query-keys';
import {useAuth} from '../../auth/use-auth';
import {useActiveWorkspaceId} from '../active-workspace-id-context';
import {PersonalWorkspace} from '../user-workspaces';
import {showHttpErrorToast} from '../../utils/http/show-http-error-toast';
interface Response extends BackendResponse {}
interface Props {
workspaceId: number;
memberId: number | string;
memberType: 'invite' | 'member';
}
function removeMember({
workspaceId,
memberId,
memberType,
}: Props): Promise<Response> {
const endpoint =
memberType === 'invite'
? `workspace/invite/${memberId}`
: `workspace/${workspaceId}/member/${memberId}`;
return apiClient.delete(endpoint).then(r => r.data);
}
export function useRemoveMember() {
const {workspaceId, setWorkspaceId} = useActiveWorkspaceId();
const {user} = useAuth();
return useMutation({
mutationFn: (props: Props) => removeMember(props),
onSuccess: (response, props) => {
queryClient.invalidateQueries({
queryKey: WorkspaceQueryKeys.fetchUserWorkspaces,
});
queryClient.invalidateQueries({
queryKey: WorkspaceQueryKeys.workspaceWithMembers(props.workspaceId),
});
// if user left workspace that is currently active, switch to personal workspace
if (props.memberId === user?.id && workspaceId === props.workspaceId) {
setWorkspaceId(PersonalWorkspace.id);
}
},
onError: err => showHttpErrorToast(err),
});
}

View File

@@ -0,0 +1,35 @@
import {useMutation} from '@tanstack/react-query';
import {BackendResponse} from '../../http/backend-response/backend-response';
import {WorkspaceInvite} from '../types/workspace-invite';
import {toast} from '../../ui/toast/toast';
import {apiClient} from '../../http/query-client';
import {showHttpErrorToast} from '../../utils/http/show-http-error-toast';
interface Response extends BackendResponse {
invite: WorkspaceInvite;
}
interface Props {
workspaceId: number;
inviteId: string;
}
function ResendInvite({
workspaceId,
inviteId,
...other
}: Props): Promise<Response> {
return apiClient
.post(`workspace/${workspaceId}/${inviteId}/resend`, other)
.then(r => r.data);
}
export function useResendInvite() {
return useMutation({
mutationFn: (props: Props) => ResendInvite(props),
onSuccess: () => {
toast('Invite sent');
},
onError: err => showHttpErrorToast(err),
});
}

View File

@@ -0,0 +1,48 @@
import {useMutation} from '@tanstack/react-query';
import {UseFormReturn} from 'react-hook-form';
import {BackendResponse} from '../../http/backend-response/backend-response';
import {toast} from '../../ui/toast/toast';
import {apiClient, queryClient} from '../../http/query-client';
import {WorkspaceQueryKeys} from './workspace-query-keys';
import {Workspace} from '../types/workspace';
import {onFormQueryError} from '../../errors/on-form-query-error';
import {useDialogContext} from '../../ui/overlays/dialog/dialog-context';
import {message} from '../../i18n/message';
interface Response extends BackendResponse {
workspace: Workspace;
}
export interface UpdateWorkspacePayload {
id: number;
name: string;
}
function updateWorkspace({
id,
...props
}: UpdateWorkspacePayload): Promise<Response> {
return apiClient.put(`workspace/${id}`, props).then(r => r.data);
}
export function useUpdateWorkspace(
form: UseFormReturn<UpdateWorkspacePayload>,
) {
const {close} = useDialogContext();
return useMutation({
mutationFn: (props: UpdateWorkspacePayload) => updateWorkspace(props),
onSuccess: response => {
close();
toast(message('Updated workspace'));
queryClient.invalidateQueries({
queryKey: WorkspaceQueryKeys.fetchUserWorkspaces,
});
queryClient.invalidateQueries({
queryKey: WorkspaceQueryKeys.workspaceWithMembers(
response.workspace.id,
),
});
},
onError: r => onFormQueryError(r, form),
});
}

View File

@@ -0,0 +1,4 @@
export const WorkspaceQueryKeys = {
fetchUserWorkspaces: ['user-workspaces'],
workspaceWithMembers: (id: number) => ['workspace-with-members', id],
};

View File

@@ -0,0 +1,24 @@
import {useQuery} from '@tanstack/react-query';
import {WorkspaceQueryKeys} from './workspace-query-keys';
import {Workspace} from '../types/workspace';
import {BackendResponse} from '../../http/backend-response/backend-response';
import {apiClient} from '../../http/query-client';
export interface FetchWorkspaceWithMembersResponse extends BackendResponse {
workspace: Workspace;
}
function fetchWorkspaceWithMembers(
workspaceId: number,
): Promise<FetchWorkspaceWithMembersResponse> {
return apiClient
.get(`workspace/${workspaceId}`)
.then(response => response.data);
}
export function useWorkspaceWithMembers(workspaceId: number) {
return useQuery({
queryKey: WorkspaceQueryKeys.workspaceWithMembers(workspaceId),
queryFn: () => fetchWorkspaceWithMembers(workspaceId),
});
}