feat: ask for shortlinking
This commit is contained in:
parent
d8b8b3d629
commit
5bad88daa9
|
|
@ -4,6 +4,7 @@ import { Organization } from '@prisma/client';
|
|||
import { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';
|
||||
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';
|
||||
import { AddTeamMemberDto } from '@gitroom/nestjs-libraries/dtos/settings/add.team.member.dto';
|
||||
import { ShortlinkPreferenceDto } from '@gitroom/nestjs-libraries/dtos/settings/shortlink-preference.dto';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthorizationActions, Sections } from '@gitroom/backend/services/auth/permissions/permission.exception.class';
|
||||
|
||||
|
|
@ -46,4 +47,21 @@ export class SettingsController {
|
|||
) {
|
||||
return this._organizationService.deleteTeamMember(org, id);
|
||||
}
|
||||
|
||||
@Get('/shortlink')
|
||||
async getShortlinkPreference(@GetOrgFromRequest() org: Organization) {
|
||||
return this._organizationService.getShortlinkPreference(org.id);
|
||||
}
|
||||
|
||||
@Post('/shortlink')
|
||||
@CheckPolicies([AuthorizationActions.Create, Sections.ADMIN])
|
||||
async updateShortlinkPreference(
|
||||
@GetOrgFromRequest() org: Organization,
|
||||
@Body() body: ShortlinkPreferenceDto
|
||||
) {
|
||||
return this._organizationService.updateShortlinkPreference(
|
||||
org.id,
|
||||
body.shortlink
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import {
|
|||
DropdownArrowSmallIcon,
|
||||
} from '@gitroom/frontend/components/ui/icons';
|
||||
import { useHasScroll } from '@gitroom/frontend/components/ui/is.scroll.hook';
|
||||
import { useShortlinkPreference } from '@gitroom/frontend/components/settings/shortlink-preference.component';
|
||||
|
||||
function countCharacters(text: string, type: string): number {
|
||||
if (type !== 'x') {
|
||||
|
|
@ -58,6 +59,7 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
|
|||
const toaster = useToaster();
|
||||
const modal = useModals();
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const { data: shortlinkPreferenceData } = useShortlinkPreference();
|
||||
|
||||
const { addEditSets, mutate, customClose, dummy } = props;
|
||||
|
||||
|
|
@ -276,28 +278,38 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
|
|||
}
|
||||
}
|
||||
|
||||
const shortLinkUrl = dummy
|
||||
? { ask: false }
|
||||
: await (
|
||||
await fetch('/posts/should-shortlink', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
messages: checkAllValid.flatMap((p: any) =>
|
||||
p.values.flatMap((a: any) => a.content)
|
||||
),
|
||||
}),
|
||||
})
|
||||
).json();
|
||||
const shortlinkPreference = shortlinkPreferenceData?.shortlink || 'ASK';
|
||||
|
||||
const shortLink = !shortLinkUrl.ask
|
||||
? false
|
||||
: await deleteDialog(
|
||||
t(
|
||||
'shortlink_urls_question',
|
||||
'Do you want to shortlink the URLs? it will let you get statistics over clicks'
|
||||
),
|
||||
t('yes_shortlink_it', 'Yes, shortlink it!')
|
||||
);
|
||||
let shortLink = false;
|
||||
|
||||
if (!dummy && shortlinkPreference !== 'NO') {
|
||||
const shortLinkUrl = await (
|
||||
await fetch('/posts/should-shortlink', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
messages: checkAllValid.flatMap((p: any) =>
|
||||
p.values.flatMap((a: any) => a.content)
|
||||
),
|
||||
}),
|
||||
})
|
||||
).json();
|
||||
|
||||
if (shortLinkUrl.ask) {
|
||||
if (shortlinkPreference === 'YES') {
|
||||
// Automatically shortlink without asking
|
||||
shortLink = true;
|
||||
} else {
|
||||
// ASK: Show the dialog
|
||||
shortLink = await deleteDialog(
|
||||
t(
|
||||
'shortlink_urls_question',
|
||||
'Do you want to shortlink the URLs? it will let you get statistics over clicks'
|
||||
),
|
||||
t('yes_shortlink_it', 'Yes, shortlink it!')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const group = existingData.group || makeId(10);
|
||||
const data = {
|
||||
|
|
@ -373,7 +385,7 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
|
|||
}
|
||||
}
|
||||
},
|
||||
[ref, repeater, tags, date, addEditSets, dummy]
|
||||
[ref, repeater, tags, date, addEditSets, dummy, shortlinkPreferenceData]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import React from 'react';
|
|||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
import dynamic from 'next/dynamic';
|
||||
import EmailNotificationsComponent from '@gitroom/frontend/components/settings/email-notifications.component';
|
||||
import ShortlinkPreferenceComponent from '@gitroom/frontend/components/settings/shortlink-preference.component';
|
||||
|
||||
const MetricComponent = dynamic(
|
||||
() => import('@gitroom/frontend/components/settings/metric.component'),
|
||||
|
|
@ -19,6 +20,7 @@ export const GlobalSettings = () => {
|
|||
<h3 className="text-[20px]">{t('global_settings', 'Global Settings')}</h3>
|
||||
<MetricComponent />
|
||||
<EmailNotificationsComponent />
|
||||
<ShortlinkPreferenceComponent />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
'use client';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import useSWR from 'swr';
|
||||
import { Select } from '@gitroom/react/form/select';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
|
||||
type ShortLinkPreference = 'ASK' | 'YES' | 'NO';
|
||||
|
||||
interface ShortlinkPreferenceResponse {
|
||||
shortlink: ShortLinkPreference;
|
||||
}
|
||||
|
||||
export const useShortlinkPreference = () => {
|
||||
const fetch = useFetch();
|
||||
|
||||
const load = useCallback(async () => {
|
||||
return (await fetch('/settings/shortlink')).json();
|
||||
}, []);
|
||||
|
||||
return useSWR<ShortlinkPreferenceResponse>('shortlink-preference', load, {
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
revalidateIfStale: false,
|
||||
revalidateOnMount: true,
|
||||
refreshWhenHidden: false,
|
||||
refreshWhenOffline: false,
|
||||
});
|
||||
};
|
||||
|
||||
const ShortlinkPreferenceComponent = () => {
|
||||
const t = useT();
|
||||
const fetch = useFetch();
|
||||
const toaster = useToaster();
|
||||
const { data, isLoading, mutate } = useShortlinkPreference();
|
||||
|
||||
const [localValue, setLocalValue] = useState<ShortLinkPreference>('ASK');
|
||||
|
||||
// Sync local state with fetched data
|
||||
useEffect(() => {
|
||||
if (data?.shortlink) {
|
||||
setLocalValue(data.shortlink);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const newValue = event.target.value as ShortLinkPreference;
|
||||
|
||||
// Update local state immediately
|
||||
setLocalValue(newValue);
|
||||
|
||||
await fetch('/settings/shortlink', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ shortlink: newValue }),
|
||||
});
|
||||
|
||||
mutate({ shortlink: newValue });
|
||||
toaster.show(t('settings_updated', 'Settings updated'), 'success');
|
||||
},
|
||||
[fetch, mutate, toaster, t]
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="my-[16px] mt-[16px] bg-sixth border-fifth border rounded-[4px] p-[24px]">
|
||||
<div className="animate-pulse">{t('loading', 'Loading...')}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="my-[16px] mt-[16px] bg-sixth border-fifth border rounded-[4px] p-[24px] flex flex-col gap-[24px]">
|
||||
<div className="mt-[4px]">
|
||||
{t('shortlink_settings', 'Shortlink Settings')}
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-[24px]">
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="text-[14px]">
|
||||
{t('shortlink_preference', 'Shortlink Preference')}
|
||||
</div>
|
||||
<div className="text-[12px] text-customColor18">
|
||||
{t(
|
||||
'shortlink_preference_description',
|
||||
'Control how URLs in your posts are handled. Shortlinks provide click statistics.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[200px]">
|
||||
<Select
|
||||
name="shortlink"
|
||||
label=""
|
||||
disableForm={true}
|
||||
hideErrors={true}
|
||||
value={localValue}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="ASK">
|
||||
{t('shortlink_ask', 'Ask every time')}
|
||||
</option>
|
||||
<option value="YES">
|
||||
{t('shortlink_yes', 'Always shortlink')}
|
||||
</option>
|
||||
<option value="NO">
|
||||
{t('shortlink_no', 'Never shortlink')}
|
||||
</option>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShortlinkPreferenceComponent;
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
|
||||
import { Role, SubscriptionTier } from '@prisma/client';
|
||||
import { Role, ShortLinkPreference, SubscriptionTier } from '@prisma/client';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthService } from '@gitroom/helpers/auth/auth.service';
|
||||
import { CreateOrgUserDto } from '@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto';
|
||||
|
|
@ -329,4 +329,26 @@ export class OrganizationRepository {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
getShortlinkPreference(orgId: string) {
|
||||
return this._organization.model.organization.findUnique({
|
||||
where: {
|
||||
id: orgId,
|
||||
},
|
||||
select: {
|
||||
shortlink: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
updateShortlinkPreference(orgId: string, shortlink: ShortLinkPreference) {
|
||||
return this._organization.model.organization.update({
|
||||
where: {
|
||||
id: orgId,
|
||||
},
|
||||
data: {
|
||||
shortlink,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { AddTeamMemberDto } from '@gitroom/nestjs-libraries/dtos/settings/add.te
|
|||
import { AuthService } from '@gitroom/helpers/auth/auth.service';
|
||||
import dayjs from 'dayjs';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import { Organization } from '@prisma/client';
|
||||
import { Organization, ShortLinkPreference } from '@prisma/client';
|
||||
import { AutopostService } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.service';
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -111,4 +111,15 @@ export class OrganizationService {
|
|||
disable
|
||||
);
|
||||
}
|
||||
|
||||
getShortlinkPreference(orgId: string) {
|
||||
return this._organizationRepository.getShortlinkPreference(orgId);
|
||||
}
|
||||
|
||||
updateShortlinkPreference(orgId: string, shortlink: ShortLinkPreference) {
|
||||
return this._organizationRepository.updateShortlinkPreference(
|
||||
orgId,
|
||||
shortlink
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,15 +9,16 @@ datasource db {
|
|||
}
|
||||
|
||||
model Organization {
|
||||
id String @id @default(uuid())
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
description String?
|
||||
apiKey String?
|
||||
paymentId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
allowTrial Boolean @default(false)
|
||||
isTrailing Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
allowTrial Boolean @default(false)
|
||||
isTrailing Boolean @default(false)
|
||||
shortlink ShortLinkPreference @default(ASK)
|
||||
autoPost AutoPost[]
|
||||
Comments Comments[]
|
||||
credits Credits[]
|
||||
|
|
@ -879,3 +880,9 @@ enum APPROVED_SUBMIT_FOR_ORDER {
|
|||
WAITING_CONFIRMATION
|
||||
YES
|
||||
}
|
||||
|
||||
enum ShortLinkPreference {
|
||||
ASK
|
||||
YES
|
||||
NO
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import { IsEnum } from 'class-validator';
|
||||
import { ShortLinkPreference } from '@prisma/client';
|
||||
|
||||
export class ShortlinkPreferenceDto {
|
||||
@IsEnum(ShortLinkPreference)
|
||||
shortlink: ShortLinkPreference;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue