mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-24 21:51:34 +10:00
refactor(v4.0.0-alpha): beginning of a new era
This commit is contained in:
@ -0,0 +1,25 @@
|
||||
import { MessageDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
|
||||
export const resendVerificationEmail = async () => {
|
||||
const response = await axios.post<MessageDto, AxiosResponse<MessageDto>>(
|
||||
"/auth/verify-email/resend",
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useResendVerificationEmail = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: resendVerificationEmailFn,
|
||||
} = useMutation({
|
||||
mutationFn: resendVerificationEmail,
|
||||
});
|
||||
|
||||
return { resendVerificationEmail: resendVerificationEmailFn, loading, error };
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
import { MessageDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
|
||||
export const verifyEmail = async (data: { token: string }) => {
|
||||
const response = await axios.post<MessageDto, AxiosResponse<MessageDto>>(
|
||||
`/auth/verify-email?token=${data.token}`,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useVerifyEmail = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: verifyEmailFn,
|
||||
} = useMutation({
|
||||
mutationFn: verifyEmail,
|
||||
});
|
||||
|
||||
return { verifyEmail: verifyEmailFn, loading, error };
|
||||
};
|
||||
20
apps/client/src/services/auth/index.ts
Normal file
20
apps/client/src/services/auth/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export * from "./login";
|
||||
export * from "./logout";
|
||||
export * from "./refresh";
|
||||
export * from "./register";
|
||||
export * from "./update-password";
|
||||
|
||||
// Email Verification
|
||||
export * from "./email-verification/resend-verify-email";
|
||||
export * from "./email-verification/verify-email";
|
||||
|
||||
// Password Recovery
|
||||
export * from "./password-recovery/forgot-password";
|
||||
export * from "./password-recovery/reset-password";
|
||||
|
||||
// Two Factor Authentication
|
||||
export * from "./two-factor-authentication/backup-otp";
|
||||
export * from "./two-factor-authentication/disable";
|
||||
export * from "./two-factor-authentication/enable";
|
||||
export * from "./two-factor-authentication/setup";
|
||||
export * from "./two-factor-authentication/verify-otp";
|
||||
40
apps/client/src/services/auth/login.ts
Normal file
40
apps/client/src/services/auth/login.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { AuthResponseDto, LoginDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
import { useAuthStore } from "@/client/stores/auth";
|
||||
|
||||
export const login = async (data: LoginDto) => {
|
||||
const response = await axios.post<AuthResponseDto, AxiosResponse<AuthResponseDto>, LoginDto>(
|
||||
"/auth/login",
|
||||
data,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useLogin = () => {
|
||||
const navigate = useNavigate();
|
||||
const setUser = useAuthStore((state) => state.setUser);
|
||||
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: loginFn,
|
||||
} = useMutation({
|
||||
mutationFn: login,
|
||||
onSuccess: (data) => {
|
||||
if (data.status === "2fa_required") {
|
||||
return navigate("/auth/verify-otp");
|
||||
}
|
||||
|
||||
setUser(data.user);
|
||||
queryClient.setQueryData(["user"], data.user);
|
||||
},
|
||||
});
|
||||
|
||||
return { login: loginFn, loading, error };
|
||||
};
|
||||
31
apps/client/src/services/auth/logout.ts
Normal file
31
apps/client/src/services/auth/logout.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
import { useAuthStore } from "@/client/stores/auth";
|
||||
|
||||
export const logout = async () => {
|
||||
await axios.post("/auth/logout");
|
||||
};
|
||||
|
||||
export const useLogout = () => {
|
||||
const setUser = useAuthStore((state) => state.setUser);
|
||||
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: logoutFn,
|
||||
} = useMutation({
|
||||
mutationFn: logout,
|
||||
onSuccess: () => {
|
||||
setUser(null);
|
||||
queryClient.setQueryData(["user"], null);
|
||||
},
|
||||
onError: () => {
|
||||
setUser(null);
|
||||
queryClient.setQueryData(["user"], null);
|
||||
},
|
||||
});
|
||||
|
||||
return { logout: logoutFn, loading, error };
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
import { ForgotPasswordDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
|
||||
export const forgotPassword = async (data: ForgotPasswordDto) => {
|
||||
const response = await axios.post<void, AxiosResponse<void>, ForgotPasswordDto>(
|
||||
"/auth/forgot-password",
|
||||
data,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useForgotPassword = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: forgotPasswordFn,
|
||||
} = useMutation({
|
||||
mutationFn: forgotPassword,
|
||||
});
|
||||
|
||||
return { forgotPassword: forgotPasswordFn, loading, error };
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
import { ResetPasswordDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
|
||||
export const resetPassword = async (data: ResetPasswordDto) => {
|
||||
const response = await axios.post<void, AxiosResponse<void>, ResetPasswordDto>(
|
||||
"/auth/reset-password",
|
||||
data,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useResetPassword = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: resetPasswordFn,
|
||||
} = useMutation({
|
||||
mutationFn: resetPassword,
|
||||
});
|
||||
|
||||
return { resetPassword: resetPasswordFn, loading, error };
|
||||
};
|
||||
8
apps/client/src/services/auth/refresh.ts
Normal file
8
apps/client/src/services/auth/refresh.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { MessageDto } from "@reactive-resume/dto";
|
||||
import { AxiosInstance, AxiosResponse } from "axios";
|
||||
|
||||
export const refresh = async (axios: AxiosInstance) => {
|
||||
const response = await axios.post<MessageDto, AxiosResponse<MessageDto>>("/auth/refresh");
|
||||
|
||||
return response.data;
|
||||
};
|
||||
34
apps/client/src/services/auth/register.ts
Normal file
34
apps/client/src/services/auth/register.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { AuthResponseDto, RegisterDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
import { useAuthStore } from "@/client/stores/auth";
|
||||
|
||||
export const register = async (data: RegisterDto) => {
|
||||
const response = await axios.post<AuthResponseDto, AxiosResponse<AuthResponseDto>, RegisterDto>(
|
||||
"/auth/register",
|
||||
data,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useRegister = () => {
|
||||
const setUser = useAuthStore((state) => state.setUser);
|
||||
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: registerFn,
|
||||
} = useMutation({
|
||||
mutationFn: register,
|
||||
onSuccess: (data) => {
|
||||
setUser(data.user);
|
||||
queryClient.setQueryData(["user"], data.user);
|
||||
},
|
||||
});
|
||||
|
||||
return { register: registerFn, loading, error };
|
||||
};
|
||||
@ -0,0 +1,43 @@
|
||||
import { AuthResponseDto, TwoFactorBackupDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosError, AxiosResponse } from "axios";
|
||||
|
||||
import { toast } from "@/client/hooks/use-toast";
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
import { useAuthStore } from "@/client/stores/auth";
|
||||
|
||||
export const backupOtp = async (data: TwoFactorBackupDto) => {
|
||||
const response = await axios.post<
|
||||
AuthResponseDto,
|
||||
AxiosResponse<AuthResponseDto>,
|
||||
TwoFactorBackupDto
|
||||
>("/auth/2fa/backup", data);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useBackupOtp = () => {
|
||||
const setUser = useAuthStore((state) => state.setUser);
|
||||
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: backupOtpFn,
|
||||
} = useMutation({
|
||||
mutationFn: backupOtp,
|
||||
onSuccess: (data) => {
|
||||
setUser(data.user);
|
||||
queryClient.setQueryData(["user"], data.user);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (error instanceof AxiosError) {
|
||||
const message = error.response?.data?.message || error.message;
|
||||
|
||||
toast({ variant: "error", title: message });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { backupOtp: backupOtpFn, loading, error };
|
||||
};
|
||||
@ -0,0 +1,23 @@
|
||||
import { MessageDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
|
||||
export const disable2FA = async () => {
|
||||
const response = await axios.post<MessageDto, AxiosResponse<MessageDto>>("/auth/2fa/disable");
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useDisable2FA = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: disable2FAFn,
|
||||
} = useMutation({
|
||||
mutationFn: disable2FA,
|
||||
});
|
||||
|
||||
return { disable2FA: disable2FAFn, loading, error };
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
import { BackupCodesDto, TwoFactorDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
|
||||
export const enable2FA = async (data: TwoFactorDto) => {
|
||||
const response = await axios.post<BackupCodesDto, AxiosResponse<BackupCodesDto>, TwoFactorDto>(
|
||||
"/auth/2fa/enable",
|
||||
data,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useEnable2FA = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: enable2FAFn,
|
||||
} = useMutation({
|
||||
mutationFn: enable2FA,
|
||||
});
|
||||
|
||||
return { enable2FA: enable2FAFn, loading, error };
|
||||
};
|
||||
@ -0,0 +1,23 @@
|
||||
import { MessageDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
|
||||
export const setup2FA = async () => {
|
||||
const response = await axios.post<MessageDto, AxiosResponse<MessageDto>>("/auth/2fa/setup");
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useSetup2FA = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: setup2FAFn,
|
||||
} = useMutation({
|
||||
mutationFn: setup2FA,
|
||||
});
|
||||
|
||||
return { setup2FA: setup2FAFn, loading, error };
|
||||
};
|
||||
@ -0,0 +1,42 @@
|
||||
import { AuthResponseDto, TwoFactorDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosError, AxiosResponse } from "axios";
|
||||
|
||||
import { toast } from "@/client/hooks/use-toast";
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
import { useAuthStore } from "@/client/stores/auth";
|
||||
|
||||
export const verifyOtp = async (data: TwoFactorDto) => {
|
||||
const response = await axios.post<AuthResponseDto, AxiosResponse<AuthResponseDto>, TwoFactorDto>(
|
||||
"/auth/2fa/verify",
|
||||
data,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useVerifyOtp = () => {
|
||||
const setUser = useAuthStore((state) => state.setUser);
|
||||
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: verifyOtpFn,
|
||||
} = useMutation({
|
||||
mutationFn: verifyOtp,
|
||||
onSuccess: (data) => {
|
||||
setUser(data.user);
|
||||
queryClient.setQueryData(["user"], data.user);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (error instanceof AxiosError) {
|
||||
const message = error.response?.data?.message || error.message;
|
||||
|
||||
toast({ variant: "error", title: message });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { verifyOtp: verifyOtpFn, loading, error };
|
||||
};
|
||||
26
apps/client/src/services/auth/update-password.ts
Normal file
26
apps/client/src/services/auth/update-password.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { MessageDto, UpdatePasswordDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
|
||||
export const updatePassword = async (data: UpdatePasswordDto) => {
|
||||
const response = await axios.patch<MessageDto, AxiosResponse<MessageDto>, UpdatePasswordDto>(
|
||||
"/auth/password",
|
||||
data,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useUpdatePassword = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: updatePasswordFn,
|
||||
} = useMutation({
|
||||
mutationFn: updatePassword,
|
||||
});
|
||||
|
||||
return { updatePassword: updatePasswordFn, loading, error };
|
||||
};
|
||||
30
apps/client/src/services/openai/change-tone.ts
Normal file
30
apps/client/src/services/openai/change-tone.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { openai } from "./client";
|
||||
|
||||
const PROMPT = `You are an AI writing assistant specialized in writing copy for resumes.
|
||||
Do not return anything else except the text you improved. It should not begin with a newline. It should not have any prefix or suffix text.
|
||||
Change the tone of the following paragraph to be {mood}:
|
||||
|
||||
Text: """{input}"""
|
||||
|
||||
Revised Text: """`;
|
||||
|
||||
type Mood = "casual" | "professional" | "confident" | "friendly";
|
||||
|
||||
export const changeTone = async (text: string, mood: Mood) => {
|
||||
const prompt = PROMPT.replace("{mood}", mood).replace("{input}", text);
|
||||
|
||||
const result = await openai().completions.create({
|
||||
prompt,
|
||||
model: "gpt-3.5-turbo",
|
||||
max_tokens: 1024,
|
||||
temperature: 0.5,
|
||||
stop: ['"""'],
|
||||
n: 1,
|
||||
});
|
||||
|
||||
if (result.choices.length === 0) {
|
||||
throw new Error("OpenAI did not return any choices for your text.");
|
||||
}
|
||||
|
||||
return result.choices[0].text;
|
||||
};
|
||||
18
apps/client/src/services/openai/client.ts
Normal file
18
apps/client/src/services/openai/client.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { OpenAI } from "openai";
|
||||
|
||||
import { useOpenAiStore } from "@/client/stores/openai";
|
||||
|
||||
export const openai = () => {
|
||||
const { apiKey } = useOpenAiStore.getState();
|
||||
|
||||
if (!apiKey) {
|
||||
throw new Error(
|
||||
"Your OpenAI API Key has not been set yet. Please go to your account settings to enable OpenAI Integration.",
|
||||
);
|
||||
}
|
||||
|
||||
return new OpenAI({
|
||||
apiKey,
|
||||
dangerouslyAllowBrowser: true,
|
||||
});
|
||||
};
|
||||
28
apps/client/src/services/openai/fix-grammar.ts
Normal file
28
apps/client/src/services/openai/fix-grammar.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { openai } from "./client";
|
||||
|
||||
const PROMPT = `You are an AI writing assistant specialized in writing copy for resumes.
|
||||
Do not return anything else except the text you improved. It should not begin with a newline. It should not have any prefix or suffix text.
|
||||
Just fix the spelling and grammar of the following paragraph, do not change the meaning:
|
||||
|
||||
Text: """{input}"""
|
||||
|
||||
Revised Text: """`;
|
||||
|
||||
export const fixGrammar = async (text: string) => {
|
||||
const prompt = PROMPT.replace("{input}", text);
|
||||
|
||||
const result = await openai().completions.create({
|
||||
prompt,
|
||||
model: "gpt-3.5-turbo",
|
||||
max_tokens: 1024,
|
||||
temperature: 0,
|
||||
stop: ['"""'],
|
||||
n: 1,
|
||||
});
|
||||
|
||||
if (result.choices.length === 0) {
|
||||
throw new Error("OpenAI did not return any choices for your text.");
|
||||
}
|
||||
|
||||
return result.choices[0].text;
|
||||
};
|
||||
28
apps/client/src/services/openai/improve-writing.ts
Normal file
28
apps/client/src/services/openai/improve-writing.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { openai } from "./client";
|
||||
|
||||
const PROMPT = `You are an AI writing assistant specialized in writing copy for resumes.
|
||||
Do not return anything else except the text you improved. It should not begin with a newline. It should not have any prefix or suffix text.
|
||||
Improve the writing of the following paragraph:
|
||||
|
||||
Text: """{input}"""
|
||||
|
||||
Revised Text: """`;
|
||||
|
||||
export const improveWriting = async (text: string) => {
|
||||
const prompt = PROMPT.replace("{input}", text);
|
||||
|
||||
const result = await openai().completions.create({
|
||||
prompt,
|
||||
model: "gpt-3.5-turbo",
|
||||
max_tokens: 1024,
|
||||
temperature: 0,
|
||||
stop: ['"""'],
|
||||
n: 1,
|
||||
});
|
||||
|
||||
if (result.choices.length === 0) {
|
||||
throw new Error("OpenAI did not return any choices for your text.");
|
||||
}
|
||||
|
||||
return result.choices[0].text;
|
||||
};
|
||||
35
apps/client/src/services/resume/create.ts
Normal file
35
apps/client/src/services/resume/create.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { CreateResumeDto, ResumeDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
|
||||
export const createResume = async (data: CreateResumeDto) => {
|
||||
const response = await axios.post<ResumeDto, AxiosResponse<ResumeDto>, CreateResumeDto>(
|
||||
"/resume",
|
||||
data,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useCreateResume = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: createResumeFn,
|
||||
} = useMutation({
|
||||
mutationFn: createResume,
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueryData<ResumeDto>(["resume", { id: data.id }], data);
|
||||
|
||||
queryClient.setQueryData<ResumeDto[]>(["resumes"], (cache) => {
|
||||
if (!cache) return [data];
|
||||
return [...cache, data];
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { createResume: createResumeFn, loading, error };
|
||||
};
|
||||
34
apps/client/src/services/resume/delete.ts
Normal file
34
apps/client/src/services/resume/delete.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { DeleteResumeDto, ResumeDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
|
||||
export const deleteResume = async (data: DeleteResumeDto) => {
|
||||
const response = await axios.delete<ResumeDto, AxiosResponse<ResumeDto>, DeleteResumeDto>(
|
||||
`/resume/${data.id}`,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useDeleteResume = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: deleteResumeFn,
|
||||
} = useMutation({
|
||||
mutationFn: deleteResume,
|
||||
onSuccess: (data) => {
|
||||
queryClient.removeQueries({ queryKey: ["resume", data.id] });
|
||||
|
||||
queryClient.setQueryData<ResumeDto[]>(["resumes"], (cache) => {
|
||||
if (!cache) return [];
|
||||
return cache.filter((resume) => resume.id !== data.id);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { deleteResume: deleteResumeFn, loading, error };
|
||||
};
|
||||
35
apps/client/src/services/resume/import.ts
Normal file
35
apps/client/src/services/resume/import.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { ImportResumeDto, ResumeDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
|
||||
export const importResume = async (data: ImportResumeDto) => {
|
||||
const response = await axios.post<ResumeDto, AxiosResponse<ResumeDto>, ImportResumeDto>(
|
||||
"/resume/import",
|
||||
data,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useImportResume = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: importResumeFn,
|
||||
} = useMutation({
|
||||
mutationFn: importResume,
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueryData<ResumeDto>(["resume", { id: data.id }], data);
|
||||
|
||||
queryClient.setQueryData<ResumeDto[]>(["resumes"], (cache) => {
|
||||
if (!cache) return [data];
|
||||
return [...cache, data];
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { importResume: importResumeFn, loading, error };
|
||||
};
|
||||
7
apps/client/src/services/resume/index.ts
Normal file
7
apps/client/src/services/resume/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export * from "./create";
|
||||
export * from "./delete";
|
||||
export * from "./print";
|
||||
export * from "./resume";
|
||||
export * from "./resumes";
|
||||
export * from "./statistics";
|
||||
export * from "./update";
|
||||
24
apps/client/src/services/resume/preview.ts
Normal file
24
apps/client/src/services/resume/preview.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { UrlDto } from "@reactive-resume/dto";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { RESUME_PREVIEW_KEY } from "@/client/constants/query-keys";
|
||||
import { axios } from "@/client/libs/axios";
|
||||
|
||||
export const previewResume = async (data: { id: string }) => {
|
||||
const response = await axios.get<UrlDto>(`/resume/print/${data.id}/preview`);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useResumePreview = (id: string) => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
data,
|
||||
} = useQuery({
|
||||
queryKey: [RESUME_PREVIEW_KEY, { id }],
|
||||
queryFn: () => previewResume({ id }),
|
||||
});
|
||||
|
||||
return { url: data?.url, loading, error };
|
||||
};
|
||||
48
apps/client/src/services/resume/print.ts
Normal file
48
apps/client/src/services/resume/print.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { StatisticsDto, UrlDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { RESUME_KEY } from "@/client/constants/query-keys";
|
||||
import { toast } from "@/client/hooks/use-toast";
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
|
||||
export const printResume = async (data: { id: string }) => {
|
||||
const response = await axios.get<UrlDto>(`/resume/print/${data.id}`);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const usePrintResume = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: printResumeFn,
|
||||
} = useMutation({
|
||||
mutationFn: printResume,
|
||||
onSuccess: (_, { id }) => {
|
||||
queryClient.setQueryData([...RESUME_KEY, "statistics", id], (cache: StatisticsDto) => {
|
||||
if (cache === undefined) return cache;
|
||||
return { ...cache, downloads: cache.downloads + 1 } satisfies StatisticsDto;
|
||||
});
|
||||
|
||||
toast({
|
||||
variant: "success",
|
||||
title: "A PDF of your resume has been successfully generated.",
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
if (error instanceof AxiosError) {
|
||||
const message = error.response?.data.message || error.message;
|
||||
|
||||
toast({
|
||||
variant: "error",
|
||||
title: "An error occurred while trying to generate your resume.",
|
||||
description: message,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { printResume: printResumeFn, loading, error };
|
||||
};
|
||||
34
apps/client/src/services/resume/resume.ts
Normal file
34
apps/client/src/services/resume/resume.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { ResumeDto } from "@reactive-resume/dto";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { RESUME_KEY } from "@/client/constants/query-keys";
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { useResumeStore } from "@/client/stores/resume";
|
||||
|
||||
export const findResumeById = async (data: { id: string }) => {
|
||||
const response = await axios.get<ResumeDto>(`/resume/${data.id}`);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const findResumeByUsernameSlug = async (data: { username: string; slug: string }) => {
|
||||
const response = await axios.get<ResumeDto>(`/resume/public/${data.username}/${data.slug}`);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useResume = (id: string) => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
data: resume,
|
||||
} = useQuery({
|
||||
queryKey: [RESUME_KEY, { id }],
|
||||
queryFn: () => findResumeById({ id }),
|
||||
});
|
||||
|
||||
useResumeStore.setState({ resume });
|
||||
useResumeStore.temporal.getState().clear();
|
||||
|
||||
return { resume, loading, error };
|
||||
};
|
||||
25
apps/client/src/services/resume/resumes.ts
Normal file
25
apps/client/src/services/resume/resumes.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { ResumeDto } from "@reactive-resume/dto";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { RESUMES_KEY } from "@/client/constants/query-keys";
|
||||
import { axios } from "@/client/libs/axios";
|
||||
|
||||
export const fetchResumes = async () => {
|
||||
const response = await axios.get<ResumeDto[], AxiosResponse<ResumeDto[]>>("/resume");
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useResumes = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
data: resumes,
|
||||
} = useQuery({
|
||||
queryKey: RESUMES_KEY,
|
||||
queryFn: fetchResumes,
|
||||
});
|
||||
|
||||
return { resumes, loading, error };
|
||||
};
|
||||
25
apps/client/src/services/resume/statistics.ts
Normal file
25
apps/client/src/services/resume/statistics.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { StatisticsDto } from "@reactive-resume/dto";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { RESUME_KEY } from "@/client/constants/query-keys";
|
||||
import { axios } from "@/client/libs/axios";
|
||||
|
||||
export const findResumeStatisticsById = async (data: { id: string }) => {
|
||||
const response = await axios.get<StatisticsDto>(`/resume/${data.id}/statistics`);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useResumeStatistics = (id: string, enabled: boolean = false) => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
data: statistics,
|
||||
} = useQuery({
|
||||
queryKey: [...RESUME_KEY, "statistics", id],
|
||||
queryFn: () => findResumeStatisticsById({ id }),
|
||||
enabled,
|
||||
});
|
||||
|
||||
return { statistics, loading, error };
|
||||
};
|
||||
51
apps/client/src/services/resume/update.ts
Normal file
51
apps/client/src/services/resume/update.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { ResumeDto, UpdateResumeDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
import debounce from "lodash.debounce";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
|
||||
export const updateResume = async (data: UpdateResumeDto) => {
|
||||
const response = await axios.patch<ResumeDto, AxiosResponse<ResumeDto>, UpdateResumeDto>(
|
||||
`/resume/${data.id}`,
|
||||
data,
|
||||
);
|
||||
|
||||
queryClient.setQueryData<ResumeDto>(["resume", { id: response.data.id }], response.data);
|
||||
|
||||
queryClient.setQueryData<ResumeDto[]>(["resumes"], (cache) => {
|
||||
if (!cache) return [response.data];
|
||||
return cache.map((resume) => {
|
||||
if (resume.id === response.data.id) return response.data;
|
||||
return resume;
|
||||
});
|
||||
});
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const debouncedUpdateResume = debounce(updateResume, 500);
|
||||
|
||||
export const useUpdateResume = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: updateResumeFn,
|
||||
} = useMutation({
|
||||
mutationFn: updateResume,
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueryData<ResumeDto>(["resume", { id: data.id }], data);
|
||||
|
||||
queryClient.setQueryData<ResumeDto[]>(["resumes"], (cache) => {
|
||||
if (!cache) return [data];
|
||||
return cache.map((resume) => {
|
||||
if (resume.id === data.id) return data;
|
||||
return resume;
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { updateResume: updateResumeFn, loading, error };
|
||||
};
|
||||
1
apps/client/src/services/storage/index.ts
Normal file
1
apps/client/src/services/storage/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./upload-image";
|
||||
25
apps/client/src/services/storage/upload-image.ts
Normal file
25
apps/client/src/services/storage/upload-image.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
|
||||
export const uploadImage = (file: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
return axios.put<string, AxiosResponse<string>, FormData>("/storage/image", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
});
|
||||
};
|
||||
|
||||
export const useUploadImage = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: uploadImageFn,
|
||||
} = useMutation({
|
||||
mutationFn: uploadImage,
|
||||
});
|
||||
|
||||
return { uploadImage: uploadImageFn, loading, error };
|
||||
};
|
||||
27
apps/client/src/services/user/delete-user.ts
Normal file
27
apps/client/src/services/user/delete-user.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { MessageDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
|
||||
export const deleteUser = async () => {
|
||||
const response = await axios.delete<MessageDto, AxiosResponse<MessageDto>>("/user/me");
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useDeleteUser = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: deleteUserFn,
|
||||
} = useMutation({
|
||||
mutationFn: deleteUser,
|
||||
onSuccess: () => {
|
||||
queryClient.clear();
|
||||
},
|
||||
});
|
||||
|
||||
return { deleteUser: deleteUserFn, loading, error };
|
||||
};
|
||||
3
apps/client/src/services/user/index.ts
Normal file
3
apps/client/src/services/user/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./delete-user";
|
||||
export * from "./update-user";
|
||||
export * from "./user";
|
||||
30
apps/client/src/services/user/update-user.ts
Normal file
30
apps/client/src/services/user/update-user.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { UpdateUserDto, UserDto } from "@reactive-resume/dto";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { queryClient } from "@/client/libs/query-client";
|
||||
|
||||
export const updateUser = async (data: UpdateUserDto) => {
|
||||
const response = await axios.patch<UserDto, AxiosResponse<UserDto>, UpdateUserDto>(
|
||||
"/user/me",
|
||||
data,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useUpdateUser = () => {
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
mutateAsync: updateUserFn,
|
||||
} = useMutation({
|
||||
mutationFn: updateUser,
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueryData(["user"], data);
|
||||
},
|
||||
});
|
||||
|
||||
return { updateUser: updateUserFn, loading, error };
|
||||
};
|
||||
32
apps/client/src/services/user/user.ts
Normal file
32
apps/client/src/services/user/user.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { UserDto } from "@reactive-resume/dto";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { axios } from "@/client/libs/axios";
|
||||
import { useAuthStore } from "@/client/stores/auth";
|
||||
|
||||
export const fetchUser = async () => {
|
||||
const response = await axios.get<UserDto, AxiosResponse<UserDto>>("/user/me");
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const useUser = () => {
|
||||
const setUser = useAuthStore((state) => state.setUser);
|
||||
|
||||
const {
|
||||
error,
|
||||
isPending: loading,
|
||||
data: user,
|
||||
} = useQuery({
|
||||
queryKey: ["user"],
|
||||
queryFn: fetchUser,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setUser(user ? user : null);
|
||||
}, [user, setUser]);
|
||||
|
||||
return { user: user, loading, error };
|
||||
};
|
||||
Reference in New Issue
Block a user