Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 23 additions & 11 deletions frontend/src/components/mining/PassiveMiningDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const miningSource = ref<MiningSource>();

const $leadminerStore = useLeadminerStore();
const $supabase = useSupabaseClient();
const $toast = useToast();

const { t } = useI18n({
useScope: 'local',
Expand All @@ -54,15 +55,25 @@ function closePassiveMiningDialog() {

async function enablePassiveMining() {
if (miningSource.value) {
const { error } = await $supabase
// @ts-expect-error: Issue with nuxt/supabase
.schema('private')
.from('mining_sources')
.update({ passive_mining: true })
.match({ email: miningSource.value.email });
try {
const { error } = await $supabase
// @ts-expect-error: Issue with nuxt/supabase
.schema('private')
.from('mining_sources')
.update({ passive_mining: true })
.match({ email: miningSource.value.email });

if (error) {
throw error;
if (error) {
throw error;
}
} catch (error) {
$toast.add({
severity: 'error',
summary: t('passive_mining_update_failed'),
detail: (error as Error).message,
life: 4500,
});
return;
}
}
closePassiveMiningDialog();
Expand All @@ -75,14 +86,15 @@ async function enablePassiveMining() {
"header": "Continuous Contact Extraction",
"paragraph_1": "New contacts found in incoming emails will be automatically saved.",
"paragraph_2": "Enable continuous contact extraction from future emails?",
"yes_enable": "Yes, enable"
"yes_enable": "Yes, enable",
"passive_mining_update_failed": "Unable to update continuous mining"
},
"fr": {
"header": "Extraction continue des contacts",
"paragraph_1": "Les nouveaux contacts trouvés dans les e-mails entrants seront automatiquement enregistrés.",
"paragraph_2": "Activer l'extraction continue des contacts à partir des futurs e-mails ?",

"yes_enable": "Oui, activer"
"yes_enable": "Oui, activer",
"passive_mining_update_failed": "Impossible de mettre à jour l'extraction continue"
}
}
</i18n>
11 changes: 7 additions & 4 deletions frontend/src/components/mining/stepper-panels/mine/MinePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@
v-if="!$leadminerStore.activeMiningTask"
id="mine-stepper-start-button"
:disabled="
(sourceType === 'email' && $leadminerStore.isLoadingBoxes) ||
(sourceType === 'email' &&
($leadminerStore.isLoadingBoxes || totalEmails === 0)) ||
totalEmails === 0 &&
!$leadminerStore.googleContactsSyncEnabled) ||
$leadminerStore.isLoadingStartMining
"
:loading="$leadminerStore.isLoadingStartMining"
Expand Down Expand Up @@ -397,11 +399,12 @@ function openMiningSettings() {
}

async function startMiningBoxes() {
if (
const hasCheckedBoxes =
Object.keys(selectedBoxes.value).filter(
(key) => selectedBoxes.value[key].checked && key !== '',
).length === 0
) {
).length > 0;

if (!hasCheckedBoxes && !$leadminerStore.googleContactsSyncEnabled) {
openMiningSettings();
$toast.add({
severity: 'error',
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/components/senders/EmailSenderManagement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { useSmtpSendersStore } from '~/stores/smtp-senders';
import AddEmailSenderDialog from './AddEmailSenderDialog.vue';
import type { SmtpSender } from '@/types/smtp-senders';

defineProps<{
hideAddButton?: boolean;
}>();

const { t } = useI18n({ useScope: 'local' });
const { t: globalT } = useI18n({ useScope: 'global' });
const $confirm = useConfirm();
Expand All @@ -22,6 +26,8 @@ function openAddDialog() {
showAddDialog.value = true;
}

defineExpose({ openAddDialog });

function openEditDialog(sender: SmtpSender) {
editingSender.value = sender;
showAddDialog.value = true;
Expand Down Expand Up @@ -86,7 +92,7 @@ onMounted(() => {

<template>
<div class="flex flex-col gap-4">
<div class="flex items-center justify-end">
<div v-if="!hideAddButton" class="flex items-center justify-end">
<Button
:label="t('add_email_sender')"
icon="pi pi-plus"
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/components/sms-fleet/SmsFleetManagement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type SupportedProvider = 'smsgate' | 'simple-sms-gateway';

const props = defineProps<{
autoAdd?: boolean;
hideAddButton?: boolean;
}>();

const emit = defineEmits<{
Expand Down Expand Up @@ -212,11 +213,13 @@ function getProviderLabel(provider: SmsGatewayProvider): string {
onMounted(() => {
$smsFleetStore.fetchGateways();
});

defineExpose({ openAddDialog });
</script>

<template>
<div class="flex flex-col gap-4">
<div class="flex items-center justify-end">
<div v-if="!hideAddButton" class="flex items-center justify-end">
<Button
:label="t('add_gateway')"
icon="pi pi-plus"
Expand Down
38 changes: 33 additions & 5 deletions frontend/src/pages/campaigns.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,34 @@
<div class="flex flex-col gap-4">
<Panel toggleable class="border border-surface-200 rounded-md p-4">
<template #header>
<div class="flex items-center gap-4 w-full">
<h1 class="text-xl font-semibold">{{ t('senders') }}</h1>
<SenderFilterTabs v-model="senderFilter" />
<div class="flex items-center justify-between gap-4 w-full">
<div class="flex items-center gap-4">
<h1 class="text-xl font-semibold">{{ t('senders') }}</h1>
<SenderFilterTabs v-model="senderFilter" />
</div>
<div class="flex items-center gap-2">
<Button
v-if="senderFilter !== 'sms'"
:label="t('add_email_sender')"
icon="pi pi-plus"
outlined
@click.stop="emailSenderManagementRef?.openAddDialog()"
/>
<Button
v-if="senderFilter !== 'email'"
:label="t('add_sms_gateway')"
icon="pi pi-plus"
outlined
@click.stop="smsFleetManagementRef?.openAddDialog()"
/>
</div>
</div>
</template>
<div v-show="senderFilter !== 'sms'">
<EmailSenderManagement />
<EmailSenderManagement ref="emailSenderManagementRef" hide-add-button />
</div>
<div v-show="senderFilter !== 'email'">
<SmsFleetManagement />
<SmsFleetManagement ref="smsFleetManagementRef" hide-add-button />
</div>
</Panel>

Expand Down Expand Up @@ -395,6 +413,12 @@ import type { SenderFilter } from '~/components/senders/SenderFilterTabs.vue';
const $campaignsStore = useCampaignsStore();
const senderFilter = ref<SenderFilter>('all');
const campaignFilter = ref<SenderFilter>('all');
const emailSenderManagementRef = ref<InstanceType<
typeof EmailSenderManagement
> | null>(null);
const smsFleetManagementRef = ref<InstanceType<
typeof SmsFleetManagement
> | null>(null);

const filteredCampaigns = computed(() => {
if (campaignFilter.value === 'all') return $campaignsStore.campaigns;
Expand Down Expand Up @@ -767,6 +791,8 @@ onBeforeUnmount(() => {
"en": {
"sms_gateways": "SMS Gateways",
"senders": "Senders",
"add_email_sender": "Add Email Sender",
"add_sms_gateway": "Add SMS Gateway",
"campaigns": "Campaigns",
"refresh": "Refresh",
"no_campaigns": "No campaigns yet",
Expand Down Expand Up @@ -828,6 +854,8 @@ onBeforeUnmount(() => {
"fr": {
"sms_gateways": "Passerelles SMS",
"senders": "Expéditeurs",
"add_email_sender": "Ajouter un expéditeur email",
"add_sms_gateway": "Ajouter une passerelle SMS",
"campaigns": "Campagnes",
"refresh": "Rafraîchir",
"no_campaigns": "Aucune campagne",
Expand Down
38 changes: 38 additions & 0 deletions supabase/migrations/20260530120000_add_smtp_senders.sql
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,41 @@ CREATE POLICY "Users can manage own smtp_senders"
ON private.smtp_senders
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);

-- Backfill smtp_senders for existing OAuth mining sources (Google / Azure).
-- The oauth_refresh_token is left NULL; the backend resolves the token from
-- the linked mining_sources row (via mining_source_email) at send time.
INSERT INTO private.smtp_senders (
user_id,
name,
email,
smtp_host,
smtp_port,
smtp_encryption,
smtp_user,
auth_type,
oauth_provider,
active,
mining_source_email
)
SELECT
ms.user_id,
CASE
WHEN ms.type = 'google' THEN 'Gmail (' || ms.email || ')'
WHEN ms.type = 'azure' THEN 'Outlook (' || ms.email || ')'
END,
ms.email,
CASE
WHEN ms.type = 'google' THEN 'smtp.gmail.com'
WHEN ms.type = 'azure' THEN 'smtp-mail.outlook.com'
END,
587,
'starttls',
ms.email,
'oauth',
ms.type,
TRUE,
ms.email
FROM private.mining_sources ms
WHERE ms.type IN ('google', 'azure')
ON CONFLICT (user_id, email) DO NOTHING;
Loading