mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-14 09:57:56 +08:00
[offers][feat] integrate isSaved API (#475)
This commit is contained in:
@ -1,3 +1,4 @@
|
||||
import { signIn, useSession } from 'next-auth/react';
|
||||
import { useState } from 'react';
|
||||
import { DocumentDuplicateIcon } from '@heroicons/react/20/solid';
|
||||
import { BookmarkSquareIcon, CheckIcon } from '@heroicons/react/24/outline';
|
||||
@ -20,6 +21,7 @@ export default function OffersProfileSave({
|
||||
const { showToast } = useToast();
|
||||
const { event: gaEvent } = useGoogleAnalytics();
|
||||
const [isSaved, setSaved] = useState(false);
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
const saveMutation = trpc.useMutation(
|
||||
['offers.user.profile.addToUserProfile'],
|
||||
@ -32,7 +34,10 @@ export default function OffersProfileSave({
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
setSaved(true);
|
||||
trpcContext.invalidateQueries([
|
||||
'offers.profile.isSaved',
|
||||
{ profileId, userId: session?.user?.id },
|
||||
]);
|
||||
showToast({
|
||||
title: `Saved to your dashboard!`,
|
||||
variant: 'success',
|
||||
@ -41,16 +46,30 @@ export default function OffersProfileSave({
|
||||
},
|
||||
);
|
||||
|
||||
const isSavedQuery = trpc.useQuery(
|
||||
[`offers.profile.isSaved`, { profileId, userId: session?.user?.id }],
|
||||
{
|
||||
onSuccess: (res) => {
|
||||
setSaved(res);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const trpcContext = trpc.useContext();
|
||||
const handleSave = () => {
|
||||
saveMutation.mutate({
|
||||
profileId,
|
||||
token: token as string,
|
||||
});
|
||||
gaEvent({
|
||||
action: 'offers.profile_submission_save_to_profile',
|
||||
category: 'engagement',
|
||||
label: 'Save to profile in profile submission',
|
||||
});
|
||||
if (status === 'unauthenticated') {
|
||||
signIn();
|
||||
} else {
|
||||
saveMutation.mutate({
|
||||
profileId,
|
||||
token: token as string,
|
||||
});
|
||||
gaEvent({
|
||||
action: 'offers.profile_submission_save_to_profile',
|
||||
category: 'engagement',
|
||||
label: 'Save to profile in profile submission',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -99,9 +118,9 @@ export default function OffersProfileSave({
|
||||
</p>
|
||||
<div className="mb-20">
|
||||
<Button
|
||||
disabled={isSaved}
|
||||
disabled={isSavedQuery.isLoading || isSaved}
|
||||
icon={isSaved ? CheckIcon : BookmarkSquareIcon}
|
||||
isLoading={saveMutation.isLoading}
|
||||
isLoading={saveMutation.isLoading || isSavedQuery.isLoading}
|
||||
label={isSaved ? 'Saved to user profile' : 'Save to user profile'}
|
||||
variant="primary"
|
||||
onClick={handleSave}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import { signIn, useSession } from 'next-auth/react';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
BookmarkIcon as BookmarkIconOutline,
|
||||
@ -10,23 +11,22 @@ import {
|
||||
import { BookmarkIcon as BookmarkIconSolid } from '@heroicons/react/24/solid';
|
||||
import { Button, Dialog, Spinner, Tabs, useToast } from '@tih/ui';
|
||||
|
||||
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
|
||||
import type { ProfileDetailTab } from '~/components/offers/constants';
|
||||
import { profileDetailTabs } from '~/components/offers/constants';
|
||||
import ProfilePhotoHolder from '~/components/offers/profile/ProfilePhotoHolder';
|
||||
import type { BackgroundDisplayData } from '~/components/offers/types';
|
||||
import { JobTypeLabel } from '~/components/offers/types';
|
||||
import Tooltip from '~/components/offers/util/Tooltip';
|
||||
|
||||
import { getProfileEditPath } from '~/utils/offers/link';
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
import type { ProfileDetailTab } from '../constants';
|
||||
import { profileDetailTabs } from '../constants';
|
||||
import Tooltip from '../util/Tooltip';
|
||||
|
||||
type ProfileHeaderProps = Readonly<{
|
||||
background?: BackgroundDisplayData;
|
||||
handleDelete: () => void;
|
||||
isEditable: boolean;
|
||||
isLoading: boolean;
|
||||
isSaved?: boolean;
|
||||
selectedTab: ProfileDetailTab;
|
||||
setSelectedTab: (tab: ProfileDetailTab) => void;
|
||||
}>;
|
||||
@ -36,20 +36,41 @@ export default function ProfileHeader({
|
||||
handleDelete,
|
||||
isEditable,
|
||||
isLoading,
|
||||
isSaved = false,
|
||||
selectedTab,
|
||||
setSelectedTab,
|
||||
}: ProfileHeaderProps) {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [saved, setSaved] = useState(isSaved);
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const trpcContext = trpc.useContext();
|
||||
|
||||
const { offerProfileId = '', token = '' } = router.query;
|
||||
const { showToast } = useToast();
|
||||
const { data: session, status } = useSession();
|
||||
const { event: gaEvent } = useGoogleAnalytics();
|
||||
|
||||
const handleEditClick = () => {
|
||||
gaEvent({
|
||||
action: 'offers.edit_profile',
|
||||
category: 'engagement',
|
||||
label: 'Edit profile',
|
||||
});
|
||||
router.push(getProfileEditPath(offerProfileId as string, token as string));
|
||||
};
|
||||
|
||||
const isSavedQuery = trpc.useQuery(
|
||||
[
|
||||
`offers.profile.isSaved`,
|
||||
{ profileId: offerProfileId as string, userId: session?.user?.id },
|
||||
],
|
||||
{
|
||||
onSuccess: (res) => {
|
||||
setSaved(res);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const saveMutation = trpc.useMutation(
|
||||
['offers.user.profile.addToUserProfile'],
|
||||
{
|
||||
@ -61,7 +82,13 @@ export default function ProfileHeader({
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
setSaved(true);
|
||||
trpcContext.invalidateQueries([
|
||||
'offers.profile.isSaved',
|
||||
{
|
||||
profileId: offerProfileId as string,
|
||||
userId: session?.user?.id,
|
||||
},
|
||||
]);
|
||||
showToast({
|
||||
title: `Saved to dashboard!`,
|
||||
variant: 'success',
|
||||
@ -80,18 +107,25 @@ export default function ProfileHeader({
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
setSaved(false);
|
||||
trpcContext.invalidateQueries([
|
||||
'offers.profile.isSaved',
|
||||
{
|
||||
profileId: offerProfileId as string,
|
||||
userId: session?.user?.id,
|
||||
},
|
||||
]);
|
||||
showToast({
|
||||
title: `Removed from dashboard!`,
|
||||
variant: 'success',
|
||||
});
|
||||
trpcContext.invalidateQueries(['offers.profile.listOne']);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const toggleSaved = () => {
|
||||
if (saved) {
|
||||
if (status === 'unauthenticated') {
|
||||
signIn();
|
||||
} else if (saved) {
|
||||
unsaveMutation.mutate({ profileId: offerProfileId as string });
|
||||
} else {
|
||||
saveMutation.mutate({
|
||||
@ -106,15 +140,22 @@ export default function ProfileHeader({
|
||||
<div className="flex justify-center space-x-2">
|
||||
<Tooltip
|
||||
tooltipContent={
|
||||
isSaved ? 'Remove from account' : 'Save to your account'
|
||||
saved ? 'Remove from account' : 'Save to your account'
|
||||
}>
|
||||
<Button
|
||||
disabled={
|
||||
isLoading || saveMutation.isLoading || unsaveMutation.isLoading
|
||||
isLoading ||
|
||||
saveMutation.isLoading ||
|
||||
unsaveMutation.isLoading ||
|
||||
isSavedQuery.isLoading
|
||||
}
|
||||
icon={saved ? BookmarkIconSolid : BookmarkIconOutline}
|
||||
isLabelHidden={true}
|
||||
isLoading={saveMutation.isLoading || unsaveMutation.isLoading}
|
||||
isLoading={
|
||||
isSavedQuery.isLoading ||
|
||||
saveMutation.isLoading ||
|
||||
unsaveMutation.isLoading
|
||||
}
|
||||
label={saved ? 'Remove from account' : 'Save to your account'}
|
||||
size="md"
|
||||
variant="tertiary"
|
||||
|
@ -4,6 +4,7 @@ import { useSession } from 'next-auth/react';
|
||||
import { useState } from 'react';
|
||||
import { Spinner, useToast } from '@tih/ui';
|
||||
|
||||
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
|
||||
import { ProfileDetailTab } from '~/components/offers/constants';
|
||||
import ProfileComments from '~/components/offers/profile/ProfileComments';
|
||||
import ProfileDetails from '~/components/offers/profile/ProfileDetails';
|
||||
@ -36,6 +37,7 @@ export default function OfferProfile() {
|
||||
);
|
||||
const [analysis, setAnalysis] = useState<ProfileAnalysis>();
|
||||
const { data: session } = useSession();
|
||||
const { event: gaEvent } = useGoogleAnalytics();
|
||||
|
||||
const getProfileQuery = trpc.useQuery(
|
||||
[
|
||||
@ -176,6 +178,11 @@ export default function OfferProfile() {
|
||||
profileId: offerProfileId as string,
|
||||
token: token as string,
|
||||
});
|
||||
gaEvent({
|
||||
action: 'offers.delete_profile',
|
||||
category: 'engagement',
|
||||
label: 'Delete profile',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -202,7 +209,6 @@ export default function OfferProfile() {
|
||||
handleDelete={handleDelete}
|
||||
isEditable={isEditable}
|
||||
isLoading={getProfileQuery.isLoading}
|
||||
isSaved={getProfileQuery.data?.isSaved}
|
||||
selectedTab={selectedTab}
|
||||
setSelectedTab={setSelectedTab}
|
||||
/>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import crypto from 'crypto';
|
||||
import { z } from 'zod';
|
||||
import type { OffersProfile } from '@prisma/client';
|
||||
import { JobType } from '@prisma/client';
|
||||
import * as trpc from '@trpc/server';
|
||||
|
||||
@ -108,14 +109,15 @@ export const offersProfileRouter = createRouter()
|
||||
token: z.string(),
|
||||
}),
|
||||
async resolve({ ctx, input }) {
|
||||
const profile = await ctx.prisma.offersProfile.findFirst({
|
||||
where: {
|
||||
id: input.profileId
|
||||
}
|
||||
})
|
||||
const profile: OffersProfile | null =
|
||||
await ctx.prisma.offersProfile.findFirst({
|
||||
where: {
|
||||
id: input.profileId,
|
||||
},
|
||||
});
|
||||
|
||||
return profile?.editToken === input.token
|
||||
}
|
||||
return profile?.editToken === input.token;
|
||||
},
|
||||
})
|
||||
.query('isSaved', {
|
||||
input: z.object({
|
||||
@ -123,36 +125,35 @@ export const offersProfileRouter = createRouter()
|
||||
userId: z.string().nullish(),
|
||||
}),
|
||||
async resolve({ ctx, input }) {
|
||||
|
||||
if (!input.userId) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
const profile = await ctx.prisma.offersProfile.findFirst({
|
||||
include: {
|
||||
users: true
|
||||
users: true,
|
||||
},
|
||||
where: {
|
||||
id: input.profileId
|
||||
}
|
||||
})
|
||||
id: input.profileId,
|
||||
},
|
||||
});
|
||||
|
||||
const users = profile?.users
|
||||
const users = profile?.users;
|
||||
|
||||
if (!users) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
let isSaved = false
|
||||
let isSaved = false;
|
||||
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
if (users[i].id === input.userId) {
|
||||
isSaved = true
|
||||
isSaved = true;
|
||||
}
|
||||
}
|
||||
|
||||
return isSaved
|
||||
}
|
||||
return isSaved;
|
||||
},
|
||||
})
|
||||
.query('listOne', {
|
||||
input: z.object({
|
||||
|
Reference in New Issue
Block a user