Skip to content

Commit f0cc9b5

Browse files
authored
Merge branch 'gitroomhq:main' into main
2 parents 1bb2a5f + 9c648fe commit f0cc9b5

39 files changed

+5142
-1387
lines changed

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

+8
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
RefreshToken,
3737
} from '@gitroom/nestjs-libraries/integrations/social.abstract';
3838
import { timer } from '@gitroom/helpers/utils/timer';
39+
import { TelegramProvider } from '@gitroom/nestjs-libraries/integrations/social/telegram.provider';
3940

4041
@ApiTags('Integrations')
4142
@Controller('/integrations')
@@ -609,4 +610,11 @@ export class IntegrationsController {
609610
) {
610611
return this._integrationService.changePlugActivation(org.id, id, status);
611612
}
613+
614+
@Get('/telegram/updates')
615+
async getUpdates(
616+
@Query() query: { word: string; id?: number },
617+
) {
618+
return new TelegramProvider().getBotId(query);
619+
}
612620
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { ProvidersInterface } from '@gitroom/backend/services/auth/providers.interface';
2+
import { NeynarAPIClient } from '@neynar/nodejs-sdk';
3+
4+
const client = new NeynarAPIClient({
5+
apiKey: process.env.NEYNAR_SECRET_KEY || '00000000-000-0000-000-000000000000',
6+
});
7+
8+
export class FarcasterProvider implements ProvidersInterface {
9+
generateLink() {
10+
return '';
11+
}
12+
13+
async getToken(code: string) {
14+
const data = JSON.parse(Buffer.from(code, 'base64').toString());
15+
const status = await client.lookupSigner({signerUuid: data.signer_uuid});
16+
if (status.status === 'approved') {
17+
return data.signer_uuid;
18+
}
19+
20+
return '';
21+
}
22+
23+
async getUser(providerToken: string) {
24+
const status = await client.lookupSigner({signerUuid: providerToken});
25+
if (status.status !== 'approved') {
26+
return {
27+
id: '',
28+
email: '',
29+
};
30+
}
31+
32+
33+
// const { client, oauth2 } = clientAndYoutube();
34+
// client.setCredentials({ access_token: providerToken });
35+
// const user = oauth2(client);
36+
// const { data } = await user.userinfo.get();
37+
38+
return {
39+
id: String('farcaster_' + status.fid),
40+
email: String('farcaster_' + status.fid),
41+
};
42+
}
43+
}

apps/backend/src/services/auth/providers/providers.factory.ts

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Provider } from '@prisma/client';
22
import { GithubProvider } from '@gitroom/backend/services/auth/providers/github.provider';
33
import { ProvidersInterface } from '@gitroom/backend/services/auth/providers.interface';
44
import { GoogleProvider } from '@gitroom/backend/services/auth/providers/google.provider';
5+
import { FarcasterProvider } from '@gitroom/backend/services/auth/providers/farcaster.provider';
56

67
export class ProvidersFactory {
78
static loadProvider(provider: Provider): ProvidersInterface {
@@ -10,6 +11,8 @@ export class ProvidersFactory {
1011
return new GithubProvider();
1112
case Provider.GOOGLE:
1213
return new GoogleProvider();
14+
case Provider.FARCASTER:
15+
return new FarcasterProvider();
1316
}
1417
}
1518
}
Loading
Loading

apps/frontend/src/app/auth/layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export default async function AuthLayout({
6868
<div className="absolute top-0 bg-gradient-to-t from-customColor9 w-[1px] translate-x-[22px] h-full" />
6969
</div>
7070
<div>
71-
<div className="absolute right-0 bg-gradient-to-l from-customColor9 h-[1px] translate-y-[22px] w-full" />
71+
<div className="absolute right-0 bg-gradient-to-l from-customColor9 h-[1px] translate-y-[60px] w-full" />
7272
</div>
7373
</div>
7474
<div className="absolute top-0 bg-gradient-to-t from-customColor9 w-[1px] -translate-x-[22px] h-full" />

apps/frontend/src/app/layout.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ export default async function AppLayout({ children }: { children: ReactNode }) {
4242
uploadDirectory={process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY!}
4343
tolt={process.env.NEXT_PUBLIC_TOLT!}
4444
facebookPixel={process.env.NEXT_PUBLIC_FACEBOOK_PIXEL!}
45+
telegramBotName={process.env.TELEGRAM_BOT_NAME!}
46+
neynarClientId={process.env.NEYNAR_CLIENT_ID!}
4547
>
4648
<ToltScript />
4749
<FacebookComponent />

apps/frontend/src/components/auth/login.tsx

+15-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { GithubProvider } from '@gitroom/frontend/components/auth/providers/gith
1212
import interClass from '@gitroom/react/helpers/inter.font';
1313
import { GoogleProvider } from '@gitroom/frontend/components/auth/providers/google.provider';
1414
import { useVariables } from '@gitroom/react/helpers/variable.context';
15+
import { FarcasterProvider } from '@gitroom/frontend/components/auth/providers/farcaster.provider';
1516

1617
type Inputs = {
1718
email: string;
@@ -22,7 +23,7 @@ type Inputs = {
2223

2324
export function Login() {
2425
const [loading, setLoading] = useState(false);
25-
const {isGeneral} = useVariables();
26+
const { isGeneral, neynarClientId } = useVariables();
2627
const resolver = useMemo(() => {
2728
return classValidatorResolver(LoginUserDto);
2829
}, []);
@@ -62,7 +63,14 @@ export function Login() {
6263
</h1>
6364
</div>
6465

65-
{!isGeneral ? <GithubProvider /> : <GoogleProvider />}
66+
{!isGeneral ? (
67+
<GithubProvider />
68+
) : (
69+
<div className="gap-[5px] flex flex-col">
70+
<GoogleProvider />
71+
{!!neynarClientId && <FarcasterProvider />}
72+
</div>
73+
)}
6674
<div className="h-[20px] mb-[24px] mt-[24px] relative">
6775
<div className="absolute w-full h-[1px] bg-fifth top-[50%] -translate-y-[50%]" />
6876
<div
@@ -89,7 +97,11 @@ export function Login() {
8997
</div>
9098
<div className="text-center mt-6">
9199
<div className="w-full flex">
92-
<Button type="submit" className="flex-1 rounded-[4px]" loading={loading}>
100+
<Button
101+
type="submit"
102+
className="flex-1 rounded-[4px]"
103+
loading={loading}
104+
>
93105
Sign in
94106
</Button>
95107
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
'use client';
2+
3+
import React, {
4+
useCallback,
5+
useEffect,
6+
useState,
7+
useRef,
8+
FC,
9+
ReactNode,
10+
} from 'react';
11+
import { useNeynarContext } from '@neynar/react';
12+
13+
export const NeynarAuthButton: FC<{
14+
children: ReactNode;
15+
onLogin: (code: string) => void;
16+
}> = (props) => {
17+
const { children, onLogin } = props;
18+
const { client_id } = useNeynarContext();
19+
20+
const [showModal, setShowModal] = useState(false);
21+
22+
const authWindowRef = useRef<Window | null>(null);
23+
const neynarLoginUrl = `${
24+
process.env.NEYNAR_LOGIN_URL ?? 'https://app.neynar.com/login'
25+
}?client_id=${client_id}`;
26+
const authOrigin = new URL(neynarLoginUrl).origin;
27+
28+
const modalRef = useRef<HTMLDivElement>(null);
29+
30+
const handleMessage = useCallback(
31+
async (event: MessageEvent) => {
32+
if (
33+
event.origin === authOrigin &&
34+
event.data &&
35+
event.data.is_authenticated
36+
) {
37+
authWindowRef.current?.close();
38+
window.removeEventListener('message', handleMessage); // Remove listener here
39+
const _user = {
40+
signer_uuid: event.data.signer_uuid,
41+
...event.data.user,
42+
};
43+
44+
onLogin(Buffer.from(JSON.stringify(_user)).toString('base64'));
45+
}
46+
},
47+
[client_id, onLogin]
48+
);
49+
50+
const handleSignIn = useCallback(() => {
51+
const width = 600,
52+
height = 700;
53+
const left = window.screen.width / 2 - width / 2;
54+
const top = window.screen.height / 2 - height / 2;
55+
const windowFeatures = `width=${width},height=${height},top=${top},left=${left}`;
56+
57+
authWindowRef.current = window.open(
58+
neynarLoginUrl,
59+
'_blank',
60+
windowFeatures
61+
);
62+
63+
if (!authWindowRef.current) {
64+
console.error(
65+
'Failed to open the authentication window. Please check your pop-up blocker settings.'
66+
);
67+
return;
68+
}
69+
70+
window.addEventListener('message', handleMessage, false);
71+
}, [client_id, handleMessage]);
72+
73+
const closeModal = () => setShowModal(false);
74+
75+
useEffect(() => {
76+
return () => {
77+
window.removeEventListener('message', handleMessage); // Cleanup function to remove listener
78+
};
79+
}, [handleMessage]);
80+
81+
const handleOutsideClick = useCallback((event: any) => {
82+
if (modalRef.current && !modalRef.current.contains(event.target)) {
83+
closeModal();
84+
}
85+
}, []);
86+
87+
useEffect(() => {
88+
if (showModal) {
89+
document.addEventListener('mousedown', handleOutsideClick);
90+
} else {
91+
document.removeEventListener('mousedown', handleOutsideClick);
92+
}
93+
94+
return () => {
95+
document.removeEventListener('mousedown', handleOutsideClick);
96+
};
97+
}, [showModal, handleOutsideClick]);
98+
99+
return <div onClick={handleSignIn}>{children}</div>;
100+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { FC, useCallback } from 'react';
2+
import interClass from '@gitroom/react/helpers/inter.font';
3+
import { useVariables } from '@gitroom/react/helpers/variable.context';
4+
import { NeynarContextProvider, Theme, useNeynarContext } from '@neynar/react';
5+
import { NeynarAuthButton } from '@gitroom/frontend/components/auth/nayner.auth.button';
6+
7+
export const FarcasterProvider = () => {
8+
const gotoLogin = useCallback(async (code: string) => {
9+
window.location.href = `/auth?provider=FARCASTER&code=${code}`;
10+
}, []);
11+
12+
return (
13+
<ButtonCaster login={gotoLogin} />
14+
);
15+
};
16+
17+
export const ButtonCaster: FC<{ login: (code: string) => void }> = (props) => {
18+
const { login } = props;
19+
const { neynarClientId } = useVariables();
20+
return (
21+
<NeynarContextProvider
22+
settings={{
23+
clientId: neynarClientId,
24+
defaultTheme: Theme.Dark,
25+
}}
26+
>
27+
<NeynarAuthButton onLogin={login}>
28+
<div
29+
className={`cursor-pointer bg-[#855ECD] h-[44px] rounded-[4px] flex justify-center items-center text-white ${interClass} gap-[4px]`}
30+
>
31+
<svg
32+
width="21px"
33+
height="21px"
34+
viewBox="0 0 1000 1000"
35+
fill="none"
36+
xmlns="http://www.w3.org/2000/svg"
37+
>
38+
<path
39+
d="M257.778 155.556H742.222V844.445H671.111V528.889H670.414C662.554 441.677 589.258 373.333 500 373.333C410.742 373.333 337.446 441.677 329.586 528.889H328.889V844.445H257.778V155.556Z"
40+
fill="white"
41+
/>
42+
<path
43+
d="M128.889 253.333L157.778 351.111H182.222V746.667C169.949 746.667 160 756.616 160 768.889V795.556H155.556C143.283 795.556 133.333 805.505 133.333 817.778V844.445H382.222V817.778C382.222 805.505 372.273 795.556 360 795.556H355.556V768.889C355.556 756.616 345.606 746.667 333.333 746.667H306.667V253.333H128.889Z"
44+
fill="white"
45+
/>
46+
<path
47+
d="M675.556 746.667C663.282 746.667 653.333 756.616 653.333 768.889V795.556H648.889C636.616 795.556 626.667 805.505 626.667 817.778V844.445H875.556V817.778C875.556 805.505 865.606 795.556 853.333 795.556H848.889V768.889C848.889 756.616 838.94 746.667 826.667 746.667V351.111H851.111L880 253.333H702.222V746.667H675.556Z"
48+
fill="white"
49+
/>
50+
</svg>
51+
<div>Continue with Farcaster</div>
52+
</div>
53+
</NeynarAuthButton>
54+
</NeynarContextProvider>
55+
);
56+
};

apps/frontend/src/components/auth/providers/google.provider.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const GoogleProvider = () => {
3939
/>
4040
</svg>
4141
</div>
42-
<div>Sign in with Google</div>
42+
<div>Continue with Google</div>
4343
</div>
4444
);
4545
};

apps/frontend/src/components/auth/register.tsx

+10-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { useFireEvents } from '@gitroom/helpers/utils/use.fire.events';
1818
import { useVariables } from '@gitroom/react/helpers/variable.context';
1919
import { useTrack } from '@gitroom/react/helpers/use.track';
2020
import { TrackEnum } from '@gitroom/nestjs-libraries/user/track.enum';
21+
import { FarcasterProvider } from '@gitroom/frontend/components/auth/providers/farcaster.provider';
2122

2223
type Inputs = {
2324
email: string;
@@ -85,7 +86,7 @@ export function RegisterAfter({
8586
token: string;
8687
provider: string;
8788
}) {
88-
const { isGeneral } = useVariables();
89+
const { isGeneral, neynarClientId } = useVariables();
8990
const [loading, setLoading] = useState(false);
9091
const router = useRouter();
9192
const fireEvents = useFireEvents();
@@ -153,7 +154,14 @@ export function RegisterAfter({
153154
</h1>
154155
</div>
155156
{!isAfterProvider &&
156-
(!isGeneral ? <GithubProvider /> : <GoogleProvider />)}
157+
(!isGeneral ? (
158+
<GithubProvider />
159+
) : (
160+
<div className="gap-[5px] flex flex-col">
161+
<GoogleProvider />
162+
{!!neynarClientId && <FarcasterProvider />}
163+
</div>
164+
))}
157165
{!isAfterProvider && (
158166
<div className="h-[20px] mb-[24px] mt-[24px] relative">
159167
<div className="absolute w-full h-[1px] bg-fifth top-[50%] -translate-y-[50%]" />

apps/frontend/src/components/launches/add.edit.model.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ export const AddEditModal: FC<{
595595
)}
596596
>
597597
<Image
598-
src={selectedIntegrations?.[0]?.picture}
598+
src={selectedIntegrations?.[0]?.picture || '/no-picture.jpg'}
599599
className="rounded-full"
600600
alt={selectedIntegrations?.[0]?.identifier}
601601
width={32}

0 commit comments

Comments
 (0)