Skip to content

Commit

Permalink
feat team updates (#851)
Browse files Browse the repository at this point in the history
  • Loading branch information
chavda-bhavik authored Oct 21, 2024
2 parents 6b05988 + eb7dc2a commit 0412ac6
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 129 deletions.
Original file line number Diff line number Diff line change
@@ -1,31 +1,68 @@
import { Injectable } from '@nestjs/common';
import { EmailService } from '@impler/services';
import { AuthService } from 'app/auth/services/auth.service';
import { EmailService, PaymentAPIService } from '@impler/services';
import { EMAIL_SUBJECT, IJwtPayload, SCREENS, UserRolesEnum } from '@impler/shared';
import { EnvironmentRepository, ProjectInvitationRepository, ProjectRepository } from '@impler/dal';
import {
ProjectEntity,
ProjectRepository,
EnvironmentRepository,
ProjectInvitationEntity,
ProjectInvitationRepository,
} from '@impler/dal';
import { LEAD_SIGNUP_USING } from '@shared/constants';
import { LeadService } from '@shared/services/lead.service';
import { captureException } from '@shared/helpers/common.helper';

@Injectable()
export class AcceptInvitation {
constructor(
private leadService: LeadService,
private authService: AuthService,
private environmentRepository: EnvironmentRepository,
private projectInvitationRepository: ProjectInvitationRepository,
private emailService: EmailService,
private projectRepository: ProjectRepository,
private emailService: EmailService
private paymentAPIService: PaymentAPIService,
private environmentRepository: EnvironmentRepository,
private projectInvitationRepository: ProjectInvitationRepository
) {}

async exec({ invitationId, user }: { invitationId: string; user: IJwtPayload }) {
const invitation = await this.projectInvitationRepository.findOne({ _id: invitationId });
const environment = await this.environmentRepository.findOne({
_projectId: invitation._projectId,
});
const userProjects = await this.environmentRepository.count({
'apiKeys._userId': user._id,
});
if (userProjects < 1) await this.registerUser(user);

const project = await this.projectRepository.findOne({ _id: environment._projectId });

await this.sendEmails(invitation, project);

await this.environmentRepository.addApiKey(environment._id, user._id, invitation.role);

await this.projectInvitationRepository.delete({ _id: invitationId });

const accessToken = this.authService.getSignedToken(
{
_id: user._id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
role: invitation.role as UserRolesEnum,
isEmailVerified: true,
profilePicture: user.profilePicture,
accessToken: environment.key,
},
invitation._projectId
);

return {
accessToken,
screen: SCREENS.HOME,
};
}
async sendEmails(invitation: ProjectInvitationEntity, project: ProjectEntity) {
const emailContentsSender = this.emailService.getEmailContent({
type: 'ACCEPT_PROJECT_INVITATION_RECIEVER_EMAIL',
data: {
Expand Down Expand Up @@ -58,24 +95,26 @@ export class AcceptInvitation {
from: process.env.EMAIL_FROM,
senderName: process.env.EMAIL_FROM_NAME,
});

const accessToken = this.authService.getSignedToken(
{
_id: user._id,
firstName: user.firstName,
lastName: user.lastName,
}
async registerUser(user: IJwtPayload) {
try {
const userData = {
name: user.firstName + ' ' + user.lastName,
email: user.email,
role: invitation.role as UserRolesEnum,
isEmailVerified: true,
profilePicture: user.profilePicture,
accessToken: environment.key,
},
invitation._projectId
);

return {
accessToken,
screen: SCREENS.HOME,
};
externalId: user.email,
};
await this.paymentAPIService.createUser(userData);
await this.leadService.createLead({
'First Name': user.firstName,
'Last Name': user.lastName,
'Lead Email': user.email,
'Lead Source': 'Invitation',
'Mentioned Role': user.role,
'Signup Method': LEAD_SIGNUP_USING.EMAIL,
'Company Size': user.companySize,
});
} catch (error) {
captureException(error);
}
}
}
14 changes: 8 additions & 6 deletions apps/api/src/app/team/usecase/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { Invite } from './invite/invite.usecase';
import { SentInvitations } from './sent-invitation/sent-invitation.usecase';
import { GetInvitation } from './get-invitation/get-invitation.usecase';
import { AcceptInvitation } from './accept-invitation/accept-invitation.usecase';
import { GenerateUniqueApiKey } from 'app/environment/usecases';
import { GetInvitation } from './get-invitation/get-invitation.usecase';
import { SentInvitations } from './sent-invitation/sent-invitation.usecase';
import { TeamMemberMeta } from './team-member-meta/team-member-meta.usecase';
import { ListTeamMembers } from './list-team-members/list-team-members.usecase';
import { UpdateTeamMember } from './update-team-member-role/update-team-member.usecase';
import { RemoveTeamMember } from './delete-team-member/delete-team-member.usecase';
import { RevokeInvitation } from './revoke-invitation/revoke-invitation.usecase';
import { AcceptInvitation } from './accept-invitation/accept-invitation.usecase';
import { RemoveTeamMember } from './delete-team-member/delete-team-member.usecase';
import { DeclineInvitation } from './decline-invitation/decline-invitation.usecase';
import { TeamMemberMeta } from './team-member-meta/team-member-meta.usecase';
import { UpdateTeamMember } from './update-team-member-role/update-team-member.usecase';
import { PaymentAPIService } from '@impler/services';
import { LeadService } from '@shared/services/lead.service';

export const USE_CASES = [
Invite,
Expand All @@ -23,6 +24,7 @@ export const USE_CASES = [
RevokeInvitation,
DeclineInvitation,
TeamMemberMeta,
LeadService,
PaymentAPIService,
//
];
Expand Down
147 changes: 77 additions & 70 deletions apps/web/components/home/PlanDetails/PlanDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ import { modals } from '@mantine/modals';
import { useCallback, useContext, useEffect } from 'react';
import { Title, Text, Flex, Button, Skeleton, Stack } from '@mantine/core';

import { track } from '@libs/amplitude';
import { numberFormatter } from '@impler/shared';
import { SelectCardModal } from '@components/settings';
import { usePlanDetails } from '@hooks/usePlanDetails';
import { TooltipLink } from '@components/guide-point';
import { PlansModal } from '@components/UpgradePlan/PlansModal';
import {
CONSTANTS,
MODAL_KEYS,
Expand All @@ -20,8 +14,16 @@ import {
SubjectsEnum,
AppAbility,
} from '@config';
import { AbilityContext, Can } from 'store/ability.context';
import { Alert } from '@ui/Alert';
import { track } from '@libs/amplitude';
import { numberFormatter } from '@impler/shared';
import { TooltipLink } from '@components/guide-point';
import { SelectCardModal } from '@components/settings';
import { usePlanDetails } from '@hooks/usePlanDetails';
import { PlansModal } from '@components/UpgradePlan/PlansModal';
import { useAppState } from 'store/app.context';
import { AbilityContext, Can } from 'store/ability.context';
import { InformationIcon } from '@assets/icons/Information.icon';

export function PlanDetails() {
const router = useRouter();
Expand Down Expand Up @@ -118,74 +120,79 @@ export function PlanDetails() {
isLessThanZero || activePlanDetails!.usage.IMPORTED_ROWS > numberOfRecords ? colors.danger : colors.yellow;

return (
<Flex
p="sm"
gap="sm"
direction="row"
align="center"
style={{
border: `1px solid ${
isLessThanZero || activePlanDetails.usage.IMPORTED_ROWS > numberOfRecords ? colors.danger : colors.yellow
}`,
backgroundColor: backgroundColor + '20',
}}
>
<Flex gap={5} justify="space-between" w="100%">
<Flex direction="column" gap={5} align="center">
<Title order={3} fw="bold">
{numberFormatter(activePlanDetails!.usage.IMPORTED_ROWS)}
{'/'}
{numberFormatter(numberOfRecords)}
</Title>
<Text size="sm" fw="bold" color={colors.TXTSecondaryDark}>
Records Imported
</Text>
</Flex>
<Flex direction="column" gap={5} align="center">
<Title order={3} fw="bold">
{activePlanDetails.plan.name}
</Title>
<Text size="sm" fw="bold" color={colors.TXTSecondaryDark}>
Active Plan
</Text>
</Flex>
{Number(activePlanDetails.plan.charge) ? (
<>
<>
<Alert color="yellow" icon={<InformationIcon size="md" />} p="xs">
You&apos;re viewing details of <b>{profileInfo?.projectName}</b> project
</Alert>
<Flex
p="sm"
gap="sm"
direction="row"
align="center"
style={{
border: `1px solid ${
isLessThanZero || activePlanDetails.usage.IMPORTED_ROWS > numberOfRecords ? colors.danger : colors.yellow
}`,
backgroundColor: backgroundColor + '20',
}}
>
<Flex gap={5} justify="space-between" w="100%">
<Flex direction="column" gap={5} align="center">
<Title order={3} fw="bold">
{numberFormatter(activePlanDetails!.usage.IMPORTED_ROWS)}
{'/'}
{numberFormatter(numberOfRecords)}
</Title>
<Text size="sm" fw="bold" color={colors.TXTSecondaryDark}>
Records Imported
</Text>
</Flex>
<Flex direction="column" gap={5} align="center">
<Title order={3} fw="bold">
{activePlanDetails.plan.name}
</Title>
<Text size="sm" fw="bold" color={colors.TXTSecondaryDark}>
Active Plan
</Text>
</Flex>
{Number(activePlanDetails.plan.charge) ? (
<>
<Flex direction="column" gap={5} align="center">
<Title order={3} fw="bold">
{'$' + activePlanDetails.plan.charge}
</Title>
<Text size="sm" fw="bold" color={colors.TXTSecondaryDark}>
Outstanding Amount
</Text>
</Flex>
</>
) : null}
<Flex direction="column" gap={5} align="center">
<Title order={3} fw="bold">
<>{activePlanDetails!.expiryDate}</>
</Title>
<Text size="sm" fw="bold" color={colors.TXTSecondaryDark}>
Expiry Date
</Text>
</Flex>

<Can I={ActionsEnum.BUY} a={SubjectsEnum.PLAN}>
<Flex direction="column" gap={5} align="center">
<Title order={3} fw="bold">
{'$' + activePlanDetails.plan.charge}
</Title>
<Text size="sm" fw="bold" color={colors.TXTSecondaryDark}>
Outstanding Amount
<Button
onClick={showPlans}
color={isLessThanZero || activePlanDetails.usage.IMPORTED_ROWS > numberOfRecords ? 'red' : 'blue'}
>
Choose Plan
</Button>
<Text component={Link} href="/transactions" color={colors.yellow} td="underline">
View all transactions
</Text>
</Flex>
</>
) : null}
<Flex direction="column" gap={5} align="center">
<Title order={3} fw="bold">
<>{activePlanDetails!.expiryDate}</>
</Title>
<Text size="sm" fw="bold" color={colors.TXTSecondaryDark}>
Expiry Date
</Text>
</Can>
</Flex>

<Can I={ActionsEnum.BUY} a={SubjectsEnum.PLAN}>
<Flex direction="column" gap={5} align="center">
<Button
onClick={showPlans}
color={isLessThanZero || activePlanDetails.usage.IMPORTED_ROWS > numberOfRecords ? 'red' : 'blue'}
>
Choose Plan
</Button>
<Text component={Link} href="/transactions" color={colors.yellow} td="underline">
View all transactions
</Text>
</Flex>
</Can>
<TooltipLink link={DOCUMENTATION_REFERENCE_LINKS.subscriptionInformation} iconSize="md" />
</Flex>

<TooltipLink link={DOCUMENTATION_REFERENCE_LINKS.subscriptionInformation} iconSize="md" />
</Flex>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { MODAL_KEYS, ROUTES } from '@config';
import { PaymentMethodOption } from './PaymentMethodOption';

interface PaymentMethodsProps {
isAddCardDisabled?: boolean;
paymentMethods: ICardData[] | undefined;
selectedPaymentMethod: string | undefined;
handlePaymentMethodChange: (methodId: string) => void;
}

export function PaymentMethods({
paymentMethods,
isAddCardDisabled,
selectedPaymentMethod,
handlePaymentMethodChange,
}: PaymentMethodsProps) {
Expand Down Expand Up @@ -44,7 +46,7 @@ export function PaymentMethods({
))}
</Radio.Group>

<Button variant="outline" color="yellow" fullWidth onClick={handleAddMoreCard}>
<Button variant="outline" color="yellow" fullWidth onClick={handleAddMoreCard} disabled={isAddCardDisabled}>
+ Add Card
</Button>
</>
Expand Down
14 changes: 8 additions & 6 deletions apps/web/components/settings/AddCard/SelectCardModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ interface SelectCardModalProps {

export function SelectCardModal({ email, planCode, paymentMethodId }: SelectCardModalProps) {
const {
handleProceed,
paymentMethods,
appliedCouponCode,
isPurchaseLoading,
setAppliedCouponCode,
handlePaymentMethodChange,
handleProceed,
selectedPaymentMethod,
isCouponFeatureEnabled,
isPaymentMethodsFetching,
isPaymentMethodsLoading,
selectedPaymentMethod,
paymentMethods,
handlePaymentMethodChange,
} = useSubscribe({
email,
planCode,
Expand Down Expand Up @@ -52,6 +53,7 @@ export function SelectCardModal({ email, planCode, paymentMethodId }: SelectCard

<PaymentMethods
paymentMethods={paymentMethods}
isAddCardDisabled={isPurchaseLoading}
selectedPaymentMethod={selectedPaymentMethod}
handlePaymentMethodChange={handlePaymentMethodChange}
/>
Expand All @@ -63,8 +65,8 @@ export function SelectCardModal({ email, planCode, paymentMethodId }: SelectCard

<CheckoutDetails checkoutData={checkoutData} isCheckoutDataLoading={isCheckoutDataLoading} />

<Button onClick={handleProceed} fullWidth mt="md">
Confirm
<Button onClick={handleProceed} fullWidth mt="md" loading={isPurchaseLoading}>
{isPurchaseLoading ? 'Processing...' : 'Confirm'}
</Button>
</Stack>
</>
Expand Down
Loading

0 comments on commit 0412ac6

Please sign in to comment.