Skip to content
Merged
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
37 changes: 17 additions & 20 deletions packages/app/control/src/components/ui/github-avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use client';

import React, { memo, useEffect, useState } from 'react';
import { Avatar, AvatarFallback, AvatarImage } from './avatar';
import { cn } from '@/lib/utils';
import type { RouterOutputs } from '@/trpc/client';
import { api } from '@/trpc/client';
import { SiGithub } from '@icons-pack/react-simple-icons';
import { searchUser } from '@/services/github/users';
import React, { memo, useEffect, useState } from 'react';
import { Avatar, AvatarFallback, AvatarImage } from './avatar';

export const MinimalGithubAvatar = memo(function MinimalGithubAvatar({
login,
Expand Down Expand Up @@ -32,6 +33,8 @@ export const MinimalGithubAvatar = memo(function MinimalGithubAvatar({
);
});

type SearchUsersResult = NonNullable<RouterOutputs['github']['searchUsers']>;

export const GithubAvatar = memo(function GithubAvatar({
pageUrl,
className,
Expand All @@ -46,7 +49,7 @@ export const GithubAvatar = memo(function GithubAvatar({
linkToProfile?: boolean;
}) {
const [user, setUser] = useState<
NonNullable<Awaited<ReturnType<typeof searchUser>>>['items'][number] | null
NonNullable<SearchUsersResult>['items'][number] | null
>(null);

const owner = React.useMemo(() => {
Expand All @@ -62,32 +65,26 @@ export const GithubAvatar = memo(function GithubAvatar({
}
}, [pageUrl]);

const search = api.github.searchUsers.useQuery(
{ q: owner ?? '' },
{ enabled: Boolean(owner) }
);

useEffect(() => {
if (!owner) {
setUser(null);
return;
}
let isCurrent = true;
searchUser(owner)
.then(fetched => {
if (!isCurrent) return;
setUser(fetched?.items[0] ?? null);
})
.catch(() => {
if (!isCurrent) return;
setUser(null);
});
return () => {
isCurrent = false;
};
}, [owner]);
const next = search.data?.items?.[0] ?? null;
setUser(next);
}, [owner, search.data]);

const avatar = (
<Avatar className="size-10 rounded-full overflow-hidden border border-border/50 shadow-sm">
{owner ? (
<AvatarImage
src={user?.avatar_url ?? `https://github.com/${owner}.png`}
alt={user?.name ?? owner}
alt={user?.login ?? owner}
className="object-cover transition-opacity duration-200"
/>
) : null}
Expand Down Expand Up @@ -164,7 +161,7 @@ export const GithubAvatar = memo(function GithubAvatar({
);
}

const displayName = user?.name ?? owner;
const displayName = user?.login ?? owner;
const profileUrl = pageUrl;

return (
Expand Down
2 changes: 1 addition & 1 deletion packages/app/control/src/services/github/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Octokit } from 'octokit';
import { env } from '@/env';
import { Octokit } from 'octokit';

export const githubClient = new Octokit({
auth: env.GITHUB_TOKEN,
Expand Down
11 changes: 3 additions & 8 deletions packages/app/control/src/services/github/link.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import type { z } from 'zod';

import { getUser } from '@/services/github/users';
import { getRepo } from './repo';
import { getUser } from './users';

import type { githubLinkSchema } from './schema';
import type { GithubLink } from './schema';

export const resolveGithubId = async (
data: z.infer<typeof githubLinkSchema>
) => {
export const resolveGithubId = async (data: GithubLink): Promise<number> => {
if (data.type === 'user') {
const username = data.url.split('/').pop();
if (!username) {
Expand Down
2 changes: 2 additions & 0 deletions packages/app/control/src/services/github/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ export const githubLinkSchema = z.discriminatedUnion('type', [
),
}),
]);

export type GithubLink = z.infer<typeof githubLinkSchema>;
50 changes: 30 additions & 20 deletions packages/app/control/src/services/github/users.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
import { githubClient } from './client';

export const getUser = async (username: string) => {
return githubClient.rest.users
.getByUsername({
username,
})
.then(res => res.data)
.catch(error => {
console.error('Error getting GitHub user:', error);
return null;
});
type SearchUsersResponse = Awaited<
ReturnType<typeof githubClient.rest.search.users>
>['data'];
type SearchedUser = SearchUsersResponse['items'][number];

export const searchUsers = async (
q: string
): Promise<SearchUsersResponse | null> => {
try {
const res = await githubClient.rest.search.users({ q });
return res.data;
} catch (error) {
console.error('Error searching GitHub users:', error);
return null;
}
};

export const searchUser = async (query: string) => {
return githubClient.rest.search
.users({
q: query,
})
.then(res => res.data)
.catch(error => {
console.error('Error searching GitHub user:', error);
return null;
});
export const getUser = async (
username: string
): Promise<SearchedUser | null> => {
try {
const data = await searchUsers(username);
if (!data) return null;
return (
data.items?.find(
user => user.login?.toLowerCase() === username.toLowerCase()
) ?? null
);
} catch (error) {
console.error('Error getting GitHub user:', error);
return null;
}
};
12 changes: 12 additions & 0 deletions packages/app/control/src/trpc/routers/github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { searchUsers } from '@/services/github/users';
import z from 'zod';
import { createTRPCRouter, publicProcedure } from '../trpc';

export const githubRouter = createTRPCRouter({
searchUsers: publicProcedure
.input(z.object({ q: z.string().min(1) }))
.query(async ({ input }) => {
const res = await searchUsers(input.q);
return res;
}),
});
8 changes: 5 additions & 3 deletions packages/app/control/src/trpc/routers/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { createCallerFactory, createTRPCRouter } from '../trpc';

import { appsRouter } from './apps';
import { userRouter } from './user';
import { adminRouter } from './admin/admin';
import { uploadRouter } from './upload';
import { appsRouter } from './apps';
import { creditsRouter } from './credits';
import { githubRouter } from './github';
import { uploadRouter } from './upload';
import { userRouter } from './user';

export const appRouter = createTRPCRouter({
apps: appsRouter,
user: userRouter,
credits: creditsRouter,
admin: adminRouter,
upload: uploadRouter,
github: githubRouter,
});

export type AppRouter = typeof appRouter;
Expand Down
Loading