[offers][feat] integrate isSaved API (#475)

pull/476/head
Zhang Ziqing 2 years ago committed by GitHub
parent 0f1e46bd7e
commit 8798958f3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,3 +1,4 @@
import { signIn, useSession } from 'next-auth/react';
import { useState } from 'react'; import { useState } from 'react';
import { DocumentDuplicateIcon } from '@heroicons/react/20/solid'; import { DocumentDuplicateIcon } from '@heroicons/react/20/solid';
import { BookmarkSquareIcon, CheckIcon } from '@heroicons/react/24/outline'; import { BookmarkSquareIcon, CheckIcon } from '@heroicons/react/24/outline';
@ -20,6 +21,7 @@ export default function OffersProfileSave({
const { showToast } = useToast(); const { showToast } = useToast();
const { event: gaEvent } = useGoogleAnalytics(); const { event: gaEvent } = useGoogleAnalytics();
const [isSaved, setSaved] = useState(false); const [isSaved, setSaved] = useState(false);
const { data: session, status } = useSession();
const saveMutation = trpc.useMutation( const saveMutation = trpc.useMutation(
['offers.user.profile.addToUserProfile'], ['offers.user.profile.addToUserProfile'],
@ -32,7 +34,10 @@ export default function OffersProfileSave({
}); });
}, },
onSuccess: () => { onSuccess: () => {
setSaved(true); trpcContext.invalidateQueries([
'offers.profile.isSaved',
{ profileId, userId: session?.user?.id },
]);
showToast({ showToast({
title: `Saved to your dashboard!`, title: `Saved to your dashboard!`,
variant: 'success', variant: 'success',
@ -41,7 +46,20 @@ 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 = () => { const handleSave = () => {
if (status === 'unauthenticated') {
signIn();
} else {
saveMutation.mutate({ saveMutation.mutate({
profileId, profileId,
token: token as string, token: token as string,
@ -51,6 +69,7 @@ export default function OffersProfileSave({
category: 'engagement', category: 'engagement',
label: 'Save to profile in profile submission', label: 'Save to profile in profile submission',
}); });
}
}; };
return ( return (
@ -99,9 +118,9 @@ export default function OffersProfileSave({
</p> </p>
<div className="mb-20"> <div className="mb-20">
<Button <Button
disabled={isSaved} disabled={isSavedQuery.isLoading || isSaved}
icon={isSaved ? CheckIcon : BookmarkSquareIcon} icon={isSaved ? CheckIcon : BookmarkSquareIcon}
isLoading={saveMutation.isLoading} isLoading={saveMutation.isLoading || isSavedQuery.isLoading}
label={isSaved ? 'Saved to user profile' : 'Save to user profile'} label={isSaved ? 'Saved to user profile' : 'Save to user profile'}
variant="primary" variant="primary"
onClick={handleSave} onClick={handleSave}

@ -1,4 +1,5 @@
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { signIn, useSession } from 'next-auth/react';
import { useState } from 'react'; import { useState } from 'react';
import { import {
BookmarkIcon as BookmarkIconOutline, BookmarkIcon as BookmarkIconOutline,
@ -10,23 +11,22 @@ import {
import { BookmarkIcon as BookmarkIconSolid } from '@heroicons/react/24/solid'; import { BookmarkIcon as BookmarkIconSolid } from '@heroicons/react/24/solid';
import { Button, Dialog, Spinner, Tabs, useToast } from '@tih/ui'; 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 ProfilePhotoHolder from '~/components/offers/profile/ProfilePhotoHolder';
import type { BackgroundDisplayData } from '~/components/offers/types'; import type { BackgroundDisplayData } from '~/components/offers/types';
import { JobTypeLabel } from '~/components/offers/types'; import { JobTypeLabel } from '~/components/offers/types';
import Tooltip from '~/components/offers/util/Tooltip';
import { getProfileEditPath } from '~/utils/offers/link'; import { getProfileEditPath } from '~/utils/offers/link';
import { trpc } from '~/utils/trpc'; import { trpc } from '~/utils/trpc';
import type { ProfileDetailTab } from '../constants';
import { profileDetailTabs } from '../constants';
import Tooltip from '../util/Tooltip';
type ProfileHeaderProps = Readonly<{ type ProfileHeaderProps = Readonly<{
background?: BackgroundDisplayData; background?: BackgroundDisplayData;
handleDelete: () => void; handleDelete: () => void;
isEditable: boolean; isEditable: boolean;
isLoading: boolean; isLoading: boolean;
isSaved?: boolean;
selectedTab: ProfileDetailTab; selectedTab: ProfileDetailTab;
setSelectedTab: (tab: ProfileDetailTab) => void; setSelectedTab: (tab: ProfileDetailTab) => void;
}>; }>;
@ -36,20 +36,41 @@ export default function ProfileHeader({
handleDelete, handleDelete,
isEditable, isEditable,
isLoading, isLoading,
isSaved = false,
selectedTab, selectedTab,
setSelectedTab, setSelectedTab,
}: ProfileHeaderProps) { }: ProfileHeaderProps) {
const [isDialogOpen, setIsDialogOpen] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false);
const [saved, setSaved] = useState(isSaved); const [saved, setSaved] = useState(false);
const router = useRouter(); const router = useRouter();
const trpcContext = trpc.useContext(); const trpcContext = trpc.useContext();
const { offerProfileId = '', token = '' } = router.query; const { offerProfileId = '', token = '' } = router.query;
const { showToast } = useToast(); const { showToast } = useToast();
const { data: session, status } = useSession();
const { event: gaEvent } = useGoogleAnalytics();
const handleEditClick = () => { const handleEditClick = () => {
gaEvent({
action: 'offers.edit_profile',
category: 'engagement',
label: 'Edit profile',
});
router.push(getProfileEditPath(offerProfileId as string, token as string)); 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( const saveMutation = trpc.useMutation(
['offers.user.profile.addToUserProfile'], ['offers.user.profile.addToUserProfile'],
{ {
@ -61,7 +82,13 @@ export default function ProfileHeader({
}); });
}, },
onSuccess: () => { onSuccess: () => {
setSaved(true); trpcContext.invalidateQueries([
'offers.profile.isSaved',
{
profileId: offerProfileId as string,
userId: session?.user?.id,
},
]);
showToast({ showToast({
title: `Saved to dashboard!`, title: `Saved to dashboard!`,
variant: 'success', variant: 'success',
@ -80,18 +107,25 @@ export default function ProfileHeader({
}); });
}, },
onSuccess: () => { onSuccess: () => {
setSaved(false); trpcContext.invalidateQueries([
'offers.profile.isSaved',
{
profileId: offerProfileId as string,
userId: session?.user?.id,
},
]);
showToast({ showToast({
title: `Removed from dashboard!`, title: `Removed from dashboard!`,
variant: 'success', variant: 'success',
}); });
trpcContext.invalidateQueries(['offers.profile.listOne']);
}, },
}, },
); );
const toggleSaved = () => { const toggleSaved = () => {
if (saved) { if (status === 'unauthenticated') {
signIn();
} else if (saved) {
unsaveMutation.mutate({ profileId: offerProfileId as string }); unsaveMutation.mutate({ profileId: offerProfileId as string });
} else { } else {
saveMutation.mutate({ saveMutation.mutate({
@ -106,15 +140,22 @@ export default function ProfileHeader({
<div className="flex justify-center space-x-2"> <div className="flex justify-center space-x-2">
<Tooltip <Tooltip
tooltipContent={ tooltipContent={
isSaved ? 'Remove from account' : 'Save to your account' saved ? 'Remove from account' : 'Save to your account'
}> }>
<Button <Button
disabled={ disabled={
isLoading || saveMutation.isLoading || unsaveMutation.isLoading isLoading ||
saveMutation.isLoading ||
unsaveMutation.isLoading ||
isSavedQuery.isLoading
} }
icon={saved ? BookmarkIconSolid : BookmarkIconOutline} icon={saved ? BookmarkIconSolid : BookmarkIconOutline}
isLabelHidden={true} isLabelHidden={true}
isLoading={saveMutation.isLoading || unsaveMutation.isLoading} isLoading={
isSavedQuery.isLoading ||
saveMutation.isLoading ||
unsaveMutation.isLoading
}
label={saved ? 'Remove from account' : 'Save to your account'} label={saved ? 'Remove from account' : 'Save to your account'}
size="md" size="md"
variant="tertiary" variant="tertiary"

@ -4,6 +4,7 @@ import { useSession } from 'next-auth/react';
import { useState } from 'react'; import { useState } from 'react';
import { Spinner, useToast } from '@tih/ui'; import { Spinner, useToast } from '@tih/ui';
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
import { ProfileDetailTab } from '~/components/offers/constants'; import { ProfileDetailTab } from '~/components/offers/constants';
import ProfileComments from '~/components/offers/profile/ProfileComments'; import ProfileComments from '~/components/offers/profile/ProfileComments';
import ProfileDetails from '~/components/offers/profile/ProfileDetails'; import ProfileDetails from '~/components/offers/profile/ProfileDetails';
@ -36,6 +37,7 @@ export default function OfferProfile() {
); );
const [analysis, setAnalysis] = useState<ProfileAnalysis>(); const [analysis, setAnalysis] = useState<ProfileAnalysis>();
const { data: session } = useSession(); const { data: session } = useSession();
const { event: gaEvent } = useGoogleAnalytics();
const getProfileQuery = trpc.useQuery( const getProfileQuery = trpc.useQuery(
[ [
@ -176,6 +178,11 @@ export default function OfferProfile() {
profileId: offerProfileId as string, profileId: offerProfileId as string,
token: token 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} handleDelete={handleDelete}
isEditable={isEditable} isEditable={isEditable}
isLoading={getProfileQuery.isLoading} isLoading={getProfileQuery.isLoading}
isSaved={getProfileQuery.data?.isSaved}
selectedTab={selectedTab} selectedTab={selectedTab}
setSelectedTab={setSelectedTab} setSelectedTab={setSelectedTab}
/> />

@ -1,5 +1,6 @@
import crypto from 'crypto'; import crypto from 'crypto';
import { z } from 'zod'; import { z } from 'zod';
import type { OffersProfile } from '@prisma/client';
import { JobType } from '@prisma/client'; import { JobType } from '@prisma/client';
import * as trpc from '@trpc/server'; import * as trpc from '@trpc/server';
@ -108,14 +109,15 @@ export const offersProfileRouter = createRouter()
token: z.string(), token: z.string(),
}), }),
async resolve({ ctx, input }) { async resolve({ ctx, input }) {
const profile = await ctx.prisma.offersProfile.findFirst({ const profile: OffersProfile | null =
await ctx.prisma.offersProfile.findFirst({
where: { where: {
id: input.profileId id: input.profileId,
} },
}) });
return profile?.editToken === input.token return profile?.editToken === input.token;
} },
}) })
.query('isSaved', { .query('isSaved', {
input: z.object({ input: z.object({
@ -123,36 +125,35 @@ export const offersProfileRouter = createRouter()
userId: z.string().nullish(), userId: z.string().nullish(),
}), }),
async resolve({ ctx, input }) { async resolve({ ctx, input }) {
if (!input.userId) { if (!input.userId) {
return false return false;
} }
const profile = await ctx.prisma.offersProfile.findFirst({ const profile = await ctx.prisma.offersProfile.findFirst({
include: { include: {
users: true users: true,
}, },
where: { where: {
id: input.profileId id: input.profileId,
} },
}) });
const users = profile?.users const users = profile?.users;
if (!users) { if (!users) {
return false return false;
} }
let isSaved = false let isSaved = false;
for (let i = 0; i < users.length; i++) { for (let i = 0; i < users.length; i++) {
if (users[i].id === input.userId) { if (users[i].id === input.userId) {
isSaved = true isSaved = true;
} }
} }
return isSaved return isSaved;
} },
}) })
.query('listOne', { .query('listOne', {
input: z.object({ input: z.object({

Loading…
Cancel
Save