Skip to content

Commit

Permalink
Merge pull request #1070 from givepraise/feat/1048/first-activation
Browse files Browse the repository at this point in the history
feat: first praise -> activate
  • Loading branch information
kristoferlund authored Jun 30, 2023
2 parents 29bbcae + 1220b5b commit a5c7bb6
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 204 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

### Changed
- **Discord bot**: Improved user onboarding - Activation flow when praising first-time #1070

### Deprecated

Expand Down
2 changes: 1 addition & 1 deletion packages/api/openapi.json

Large diffs are not rendered by default.

46 changes: 2 additions & 44 deletions packages/discord-bot/src/handlers/activate.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { CommandHandler } from '../interfaces/CommandHandler';
import { getUserAccount } from '../utils/getUserAccount';
import { GuildMember } from 'discord.js';
import { getActivateToken } from '../utils/getActivateToken';
import { renderMessage } from '../utils/renderMessage';
import { sendActivationMessage } from '../utils/sendActivationMessage';

/**
* Executes command /activate
Expand All @@ -21,45 +19,5 @@ export const activationHandler: CommandHandler = async (
return;
}

try {
const userAccount = await getUserAccount(
(member as GuildMember).user,
host
);
if (userAccount.user && userAccount.user !== null) {
await interaction.editReply({
content: await renderMessage(
'PRAISE_ACCOUNT_ALREADY_ACTIVATED_ERROR',
host
),
});
return;
}

const activateToken = await getActivateToken(userAccount, host);

if (!activateToken) {
await interaction.editReply({
content: 'Unable to activate user account.',
});
return;
}

const hostUrl =
process.env.NODE_ENV === 'development'
? process.env.FRONTEND_URL
: `https://${host}`;

const activationURL = `${hostUrl || 'undefined:/'}/activate?accountId=${
member.user.id
}&platform=DISCORD&token=${activateToken}`;

await interaction.editReply({
content: `To activate your account, follow this link and sign a message using your Ethereum wallet. [Activate my account!](${activationURL})`,
});
} catch (error) {
await interaction.editReply({
content: 'Unable to activate user account.',
});
}
await sendActivationMessage(interaction, host, member);
};
216 changes: 60 additions & 156 deletions packages/discord-bot/src/handlers/praise.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import { GuildMember, User } from 'discord.js';
import { ComponentType, GuildMember } from 'discord.js';
import { parseReceivers } from '../utils/parseReceivers';
import { sendReceiverDM } from '../utils/embeds/sendReceiverDM';
import { renderMessage, ephemeralWarning } from '../utils/renderMessage';

import { ephemeralWarning } from '../utils/renderMessage';
import { assertPraiseGiver } from '../utils/assertPraiseGiver';
import { assertPraiseAllowedInChannel } from '../utils/assertPraiseAllowedInChannel';
import { CommandHandler } from '../interfaces/CommandHandler';
import { getUserAccount } from '../utils/getUserAccount';
import { createPraise } from '../utils/createPraise';
import { praiseSuccessEmbed } from '../utils/embeds/praiseSuccessEmbed';
import { apiClient } from '../utils/api';
import {
PraisePaginatedResponseDto,
UserAccount,
Praise,
} from '../utils/api-schema';
import { getSetting } from '../utils/settingsUtil';

import { logger } from '../utils/logger';
import { sendActivationMessage } from '../utils/sendActivationMessage';
import { givePraise } from '../utils/givePraise';

/**
* Execute command /praise
Expand Down Expand Up @@ -77,160 +71,70 @@ export const praiseHandler: CommandHandler = async (
return;
}

const giverAccount = await getUserAccount(
(member as GuildMember).user,
host
);
let giverAccount = await getUserAccount((member as GuildMember).user, host);

if (!giverAccount || !giverAccount.user || giverAccount.user === null) {
await ephemeralWarning(
const response = await sendActivationMessage(
interaction,
'PRAISE_ACCOUNT_NOT_ACTIVATED_ERROR',
host
);
return;
}

const validReceiverIds: string[] = [
...new Set(
parsedReceivers.validReceiverIds.map((id: string) =>
id.replace(/\D/g, '')
)
),
];

const selfPraiseAllowed = (await getSetting(
'SELF_PRAISE_ALLOWED',
host
)) as boolean;

let warnSelfPraise = false;
if (
!selfPraiseAllowed &&
validReceiverIds.includes(giverAccount.accountId)
) {
warnSelfPraise = true;
validReceiverIds.splice(
validReceiverIds.indexOf(giverAccount.accountId),
1
);
}

const receivers: { guildMember: GuildMember; userAccount: UserAccount }[] =
await Promise.all(
(
await guild.members.fetch({ user: validReceiverIds })
).map(async (guildMember) => {
const userAccount = await getUserAccount(guildMember.user, host);
return {
guildMember,
userAccount,
};
})
host,
member,
true
);

let praiseItems: Praise[] = [];
if (receivers.length !== 0) {
await interaction.editReply({
embeds: [
await praiseSuccessEmbed(
interaction.user,
validReceiverIds.map((id) => `<@!${id}>`),
reason,
if (!response) return;

try {
const collector = response.createMessageComponentCollector({
filter: (i) =>
i.user.id === interaction.user.id && i.customId === 'retry',
componentType: ComponentType.Button,
time: 600000,
});

collector.on('collect', async (i) => {
giverAccount = await getUserAccount(
(member as GuildMember).user,
host
),
],
});
praiseItems = await createPraise(
);

if (giverAccount && giverAccount.user) {
collector.stop();
await givePraise(
interaction,
guild,
member as GuildMember,
giverAccount,
parsedReceivers,
receiverOptions,
reason,
host,
responseUrl
);
return;
}

await i.update({
content:
i.message.content +
'\nRetry failed... Retry praise after activating on the Praise dashboard',
});
});
} catch {
await interaction.editReply(
'Timed out... Please use /praise command again.'
);
}
} else {
await givePraise(
interaction,
guild,
member as GuildMember,
giverAccount,
receivers.map((receiver) => receiver.userAccount),
parsedReceivers,
receiverOptions,
reason,
host
);
} else if (warnSelfPraise) {
await ephemeralWarning(interaction, 'SELF_PRAISE_WARNING', host);
} else if (!receivers.length) {
await ephemeralWarning(
interaction,
'PRAISE_INVALID_RECEIVERS_ERROR',
host
);
} else {
await ephemeralWarning(interaction, 'PRAISE_FAILED', host);
}

const hostUrl =
process.env.NODE_ENV === 'development'
? process.env?.FRONTEND_URL || 'undefined:/'
: `https://${host}`;

await Promise.all(
praiseItems.map(async (praise) => {
console.log(praiseItems, receivers);
await sendReceiverDM(
praise._id,
receivers.filter(
(receiver) =>
receiver.userAccount.accountId === praise.receiver.accountId
)[0],
member as GuildMember,
reason,
responseUrl,
host,
hostUrl,
interaction.channelId
);
})
);

const warningMsgParts: string[] = [];

if (parsedReceivers.undefinedReceivers) {
const warning = await renderMessage(
'PRAISE_UNDEFINED_RECEIVERS_WARNING',
host,
{
receivers: parsedReceivers.undefinedReceivers.map((id) =>
id.replace(/[<>]/, '')
),
user: member.user as User,
}
responseUrl
);
warningMsgParts.push(warning);
}

if (parsedReceivers.roleMentions) {
const warning = await renderMessage('PRAISE_TO_ROLE_WARNING', host, {
user: member.user as User,
receivers: parsedReceivers.roleMentions,
});
warningMsgParts.push(warning);
}

if (receivers.length !== 0 && warnSelfPraise) {
const warning = await renderMessage('SELF_PRAISE_WARNING', host);
warningMsgParts.push(warning);
}

const warningMsg = warningMsgParts.join('\n');

if (warningMsg && warningMsg.length !== 0) {
await interaction.followUp({ content: warningMsg, ephemeral: true });
}

const praiseItemsCount = await apiClient
.get(`/praise?limit=1&giver=${giverAccount._id}`, {
headers: { host },
})
.then((res) => (res.data as PraisePaginatedResponseDto).totalPages)
.catch(() => 0);

if (receivers.length && receiverOptions.length && praiseItemsCount === 0) {
await interaction.followUp({
content: await renderMessage('FIRST_TIME_PRAISER', host),
ephemeral: true,
});
}
} catch (err) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
4 changes: 2 additions & 2 deletions packages/discord-bot/src/utils/embeds/praiseEmbeds.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { User, EmbedBuilder, Role, Embed } from 'discord.js';
import { User, EmbedBuilder, Role } from 'discord.js';
import { renderMessage } from '../renderMessage';

export const praiseRoleError = async (
Expand Down Expand Up @@ -43,6 +43,6 @@ export const praiseWelcomeEmbed = (
return new EmbedBuilder()
.setTitle('Welcome to Praise!')
.setDescription(
`✅ Praise community created\n✅ Praise bot added to Discord\n✅ Praise bot linked to community\n`
'✅ Praise community created\n✅ Praise bot added to Discord\n✅ Praise bot linked to community\n'
);
};
Loading

0 comments on commit a5c7bb6

Please sign in to comment.