Skip to content

Commit 5bad88d

Browse files
committed
feat: ask for shortlinking
1 parent d8b8b3d commit 5bad88d

File tree

8 files changed

+227
-30
lines changed

8 files changed

+227
-30
lines changed

apps/backend/src/api/routes/settings.controller.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Organization } from '@prisma/client';
44
import { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';
55
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';
66
import { AddTeamMemberDto } from '@gitroom/nestjs-libraries/dtos/settings/add.team.member.dto';
7+
import { ShortlinkPreferenceDto } from '@gitroom/nestjs-libraries/dtos/settings/shortlink-preference.dto';
78
import { ApiTags } from '@nestjs/swagger';
89
import { AuthorizationActions, Sections } from '@gitroom/backend/services/auth/permissions/permission.exception.class';
910

@@ -46,4 +47,21 @@ export class SettingsController {
4647
) {
4748
return this._organizationService.deleteTeamMember(org, id);
4849
}
50+
51+
@Get('/shortlink')
52+
async getShortlinkPreference(@GetOrgFromRequest() org: Organization) {
53+
return this._organizationService.getShortlinkPreference(org.id);
54+
}
55+
56+
@Post('/shortlink')
57+
@CheckPolicies([AuthorizationActions.Create, Sections.ADMIN])
58+
async updateShortlinkPreference(
59+
@GetOrgFromRequest() org: Organization,
60+
@Body() body: ShortlinkPreferenceDto
61+
) {
62+
return this._organizationService.updateShortlinkPreference(
63+
org.id,
64+
body.shortlink
65+
);
66+
}
4967
}

apps/frontend/src/components/new-launch/manage.modal.tsx

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
DropdownArrowSmallIcon,
4242
} from '@gitroom/frontend/components/ui/icons';
4343
import { useHasScroll } from '@gitroom/frontend/components/ui/is.scroll.hook';
44+
import { useShortlinkPreference } from '@gitroom/frontend/components/settings/shortlink-preference.component';
4445

4546
function countCharacters(text: string, type: string): number {
4647
if (type !== 'x') {
@@ -58,6 +59,7 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
5859
const toaster = useToaster();
5960
const modal = useModals();
6061
const [showSettings, setShowSettings] = useState(false);
62+
const { data: shortlinkPreferenceData } = useShortlinkPreference();
6163

6264
const { addEditSets, mutate, customClose, dummy } = props;
6365

@@ -276,28 +278,38 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
276278
}
277279
}
278280

279-
const shortLinkUrl = dummy
280-
? { ask: false }
281-
: await (
282-
await fetch('/posts/should-shortlink', {
283-
method: 'POST',
284-
body: JSON.stringify({
285-
messages: checkAllValid.flatMap((p: any) =>
286-
p.values.flatMap((a: any) => a.content)
287-
),
288-
}),
289-
})
290-
).json();
291-
292-
const shortLink = !shortLinkUrl.ask
293-
? false
294-
: await deleteDialog(
295-
t(
296-
'shortlink_urls_question',
297-
'Do you want to shortlink the URLs? it will let you get statistics over clicks'
298-
),
299-
t('yes_shortlink_it', 'Yes, shortlink it!')
300-
);
281+
const shortlinkPreference = shortlinkPreferenceData?.shortlink || 'ASK';
282+
283+
let shortLink = false;
284+
285+
if (!dummy && shortlinkPreference !== 'NO') {
286+
const shortLinkUrl = await (
287+
await fetch('/posts/should-shortlink', {
288+
method: 'POST',
289+
body: JSON.stringify({
290+
messages: checkAllValid.flatMap((p: any) =>
291+
p.values.flatMap((a: any) => a.content)
292+
),
293+
}),
294+
})
295+
).json();
296+
297+
if (shortLinkUrl.ask) {
298+
if (shortlinkPreference === 'YES') {
299+
// Automatically shortlink without asking
300+
shortLink = true;
301+
} else {
302+
// ASK: Show the dialog
303+
shortLink = await deleteDialog(
304+
t(
305+
'shortlink_urls_question',
306+
'Do you want to shortlink the URLs? it will let you get statistics over clicks'
307+
),
308+
t('yes_shortlink_it', 'Yes, shortlink it!')
309+
);
310+
}
311+
}
312+
}
301313

302314
const group = existingData.group || makeId(10);
303315
const data = {
@@ -373,7 +385,7 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
373385
}
374386
}
375387
},
376-
[ref, repeater, tags, date, addEditSets, dummy]
388+
[ref, repeater, tags, date, addEditSets, dummy, shortlinkPreferenceData]
377389
);
378390

379391
return (

apps/frontend/src/components/settings/global.settings.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React from 'react';
44
import { useT } from '@gitroom/react/translation/get.transation.service.client';
55
import dynamic from 'next/dynamic';
66
import EmailNotificationsComponent from '@gitroom/frontend/components/settings/email-notifications.component';
7+
import ShortlinkPreferenceComponent from '@gitroom/frontend/components/settings/shortlink-preference.component';
78

89
const MetricComponent = dynamic(
910
() => import('@gitroom/frontend/components/settings/metric.component'),
@@ -19,6 +20,7 @@ export const GlobalSettings = () => {
1920
<h3 className="text-[20px]">{t('global_settings', 'Global Settings')}</h3>
2021
<MetricComponent />
2122
<EmailNotificationsComponent />
23+
<ShortlinkPreferenceComponent />
2224
</div>
2325
);
2426
};
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
'use client';
2+
3+
import React, { useCallback, useEffect, useState } from 'react';
4+
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
5+
import useSWR from 'swr';
6+
import { Select } from '@gitroom/react/form/select';
7+
import { useToaster } from '@gitroom/react/toaster/toaster';
8+
import { useT } from '@gitroom/react/translation/get.transation.service.client';
9+
10+
type ShortLinkPreference = 'ASK' | 'YES' | 'NO';
11+
12+
interface ShortlinkPreferenceResponse {
13+
shortlink: ShortLinkPreference;
14+
}
15+
16+
export const useShortlinkPreference = () => {
17+
const fetch = useFetch();
18+
19+
const load = useCallback(async () => {
20+
return (await fetch('/settings/shortlink')).json();
21+
}, []);
22+
23+
return useSWR<ShortlinkPreferenceResponse>('shortlink-preference', load, {
24+
revalidateOnFocus: false,
25+
revalidateOnReconnect: false,
26+
revalidateIfStale: false,
27+
revalidateOnMount: true,
28+
refreshWhenHidden: false,
29+
refreshWhenOffline: false,
30+
});
31+
};
32+
33+
const ShortlinkPreferenceComponent = () => {
34+
const t = useT();
35+
const fetch = useFetch();
36+
const toaster = useToaster();
37+
const { data, isLoading, mutate } = useShortlinkPreference();
38+
39+
const [localValue, setLocalValue] = useState<ShortLinkPreference>('ASK');
40+
41+
// Sync local state with fetched data
42+
useEffect(() => {
43+
if (data?.shortlink) {
44+
setLocalValue(data.shortlink);
45+
}
46+
}, [data]);
47+
48+
const handleChange = useCallback(
49+
async (event: React.ChangeEvent<HTMLSelectElement>) => {
50+
const newValue = event.target.value as ShortLinkPreference;
51+
52+
// Update local state immediately
53+
setLocalValue(newValue);
54+
55+
await fetch('/settings/shortlink', {
56+
method: 'POST',
57+
body: JSON.stringify({ shortlink: newValue }),
58+
});
59+
60+
mutate({ shortlink: newValue });
61+
toaster.show(t('settings_updated', 'Settings updated'), 'success');
62+
},
63+
[fetch, mutate, toaster, t]
64+
);
65+
66+
if (isLoading) {
67+
return (
68+
<div className="my-[16px] mt-[16px] bg-sixth border-fifth border rounded-[4px] p-[24px]">
69+
<div className="animate-pulse">{t('loading', 'Loading...')}</div>
70+
</div>
71+
);
72+
}
73+
74+
return (
75+
<div className="my-[16px] mt-[16px] bg-sixth border-fifth border rounded-[4px] p-[24px] flex flex-col gap-[24px]">
76+
<div className="mt-[4px]">
77+
{t('shortlink_settings', 'Shortlink Settings')}
78+
</div>
79+
<div className="flex items-center justify-between gap-[24px]">
80+
<div className="flex flex-col flex-1">
81+
<div className="text-[14px]">
82+
{t('shortlink_preference', 'Shortlink Preference')}
83+
</div>
84+
<div className="text-[12px] text-customColor18">
85+
{t(
86+
'shortlink_preference_description',
87+
'Control how URLs in your posts are handled. Shortlinks provide click statistics.'
88+
)}
89+
</div>
90+
</div>
91+
<div className="w-[200px]">
92+
<Select
93+
name="shortlink"
94+
label=""
95+
disableForm={true}
96+
hideErrors={true}
97+
value={localValue}
98+
onChange={handleChange}
99+
>
100+
<option value="ASK">
101+
{t('shortlink_ask', 'Ask every time')}
102+
</option>
103+
<option value="YES">
104+
{t('shortlink_yes', 'Always shortlink')}
105+
</option>
106+
<option value="NO">
107+
{t('shortlink_no', 'Never shortlink')}
108+
</option>
109+
</Select>
110+
</div>
111+
</div>
112+
</div>
113+
);
114+
};
115+
116+
export default ShortlinkPreferenceComponent;
117+

libraries/nestjs-libraries/src/database/prisma/organizations/organization.repository.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
2-
import { Role, SubscriptionTier } from '@prisma/client';
2+
import { Role, ShortLinkPreference, SubscriptionTier } from '@prisma/client';
33
import { Injectable } from '@nestjs/common';
44
import { AuthService } from '@gitroom/helpers/auth/auth.service';
55
import { CreateOrgUserDto } from '@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto';
@@ -329,4 +329,26 @@ export class OrganizationRepository {
329329
},
330330
});
331331
}
332+
333+
getShortlinkPreference(orgId: string) {
334+
return this._organization.model.organization.findUnique({
335+
where: {
336+
id: orgId,
337+
},
338+
select: {
339+
shortlink: true,
340+
},
341+
});
342+
}
343+
344+
updateShortlinkPreference(orgId: string, shortlink: ShortLinkPreference) {
345+
return this._organization.model.organization.update({
346+
where: {
347+
id: orgId,
348+
},
349+
data: {
350+
shortlink,
351+
},
352+
});
353+
}
332354
}

libraries/nestjs-libraries/src/database/prisma/organizations/organization.service.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { AddTeamMemberDto } from '@gitroom/nestjs-libraries/dtos/settings/add.te
66
import { AuthService } from '@gitroom/helpers/auth/auth.service';
77
import dayjs from 'dayjs';
88
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
9-
import { Organization } from '@prisma/client';
9+
import { Organization, ShortLinkPreference } from '@prisma/client';
1010
import { AutopostService } from '@gitroom/nestjs-libraries/database/prisma/autopost/autopost.service';
1111

1212
@Injectable()
@@ -111,4 +111,15 @@ export class OrganizationService {
111111
disable
112112
);
113113
}
114+
115+
getShortlinkPreference(orgId: string) {
116+
return this._organizationRepository.getShortlinkPreference(orgId);
117+
}
118+
119+
updateShortlinkPreference(orgId: string, shortlink: ShortLinkPreference) {
120+
return this._organizationRepository.updateShortlinkPreference(
121+
orgId,
122+
shortlink
123+
);
124+
}
114125
}

libraries/nestjs-libraries/src/database/prisma/schema.prisma

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@ datasource db {
99
}
1010

1111
model Organization {
12-
id String @id @default(uuid())
12+
id String @id @default(uuid())
1313
name String
1414
description String?
1515
apiKey String?
1616
paymentId String?
17-
createdAt DateTime @default(now())
18-
updatedAt DateTime @updatedAt
19-
allowTrial Boolean @default(false)
20-
isTrailing Boolean @default(false)
17+
createdAt DateTime @default(now())
18+
updatedAt DateTime @updatedAt
19+
allowTrial Boolean @default(false)
20+
isTrailing Boolean @default(false)
21+
shortlink ShortLinkPreference @default(ASK)
2122
autoPost AutoPost[]
2223
Comments Comments[]
2324
credits Credits[]
@@ -879,3 +880,9 @@ enum APPROVED_SUBMIT_FOR_ORDER {
879880
WAITING_CONFIRMATION
880881
YES
881882
}
883+
884+
enum ShortLinkPreference {
885+
ASK
886+
YES
887+
NO
888+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { IsEnum } from 'class-validator';
2+
import { ShortLinkPreference } from '@prisma/client';
3+
4+
export class ShortlinkPreferenceDto {
5+
@IsEnum(ShortLinkPreference)
6+
shortlink: ShortLinkPreference;
7+
}
8+

0 commit comments

Comments
 (0)