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
11 changes: 8 additions & 3 deletions ceres/src/transport/protocol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,14 @@ impl SmartSession {
));
}
let repo = Repo::new(self.repo_path.clone(), false);
storage.save_git_repo(repo.clone().into()).await.map_err(|e| {
ProtocolError::InvalidInput(format!("failed to create import repo: {e}"))
})?;
storage
.save_git_repo(repo.clone().into())
.await
.map_err(|e| {
ProtocolError::InvalidInput(format!(
"failed to create import repo: {e}"
))
})?;
repo
}
}
Expand Down
35 changes: 20 additions & 15 deletions common/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,22 @@ mod test {
"/third-party/rust/",
"/third-party/rust/crates"
));
assert!(!is_strict_path_prefix("/third-party/rust", "/third-party/rust"));
assert!(!is_strict_path_prefix("/third-party/rust", "/third-party/rust_v1"));
assert!(!is_strict_path_prefix("/third-party/rust_v1", "/third-party/rust"));
assert!(!is_strict_path_prefix("/third-party/foo", "/third-party/bar"));
assert!(!is_strict_path_prefix(
"/third-party/rust",
"/third-party/rust"
));
assert!(!is_strict_path_prefix(
"/third-party/rust",
"/third-party/rust_v1"
));
assert!(!is_strict_path_prefix(
"/third-party/rust_v1",
"/third-party/rust"
));
assert!(!is_strict_path_prefix(
"/third-party/foo",
"/third-party/bar"
));
}

#[test]
Expand All @@ -206,18 +218,11 @@ mod test {
),
Some("/third-party/rust")
);
assert!(nested_import_repo_conflict("/third-party/foo", existing).is_none());
assert!(nested_import_repo_conflict("/third-party/rust_v1", existing).is_none());
assert!(
nested_import_repo_conflict("/third-party/foo", existing).is_none()
);
assert!(
nested_import_repo_conflict("/third-party/rust_v1", existing).is_none()
);
assert!(
nested_import_repo_conflict(
"/third-party/rust/crates/sw/ay/swayws/1.3.0",
existing
)
.is_none()
nested_import_repo_conflict("/third-party/rust/crates/sw/ay/swayws/1.3.0", existing)
.is_none()
);
}

Expand Down
4 changes: 1 addition & 3 deletions jupiter/src/storage/git_db_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ use callisto::{
};
use common::{
errors::MegaError,
utils::{
generate_id, nested_import_repo_conflict_message,
},
utils::{generate_id, nested_import_repo_conflict_message},
};
use futures::Stream;
use sea_orm::{
Expand Down
90 changes: 49 additions & 41 deletions moon/apps/web/components/AdminGroups/AddMembersDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { useAddAdminGroupMembers } from '@/hooks/admin/useAddAdminGroupMembers'
import { useAdminGroupMembersList } from '@/hooks/admin/useAdminGroupMembersList'
import { useGetSyncMembers } from '@/hooks/useGetSyncMembers'
import { megaUserHandle } from '@/utils/megaUser'

interface AddMembersDialogProps {
groupId: number | null
Expand Down Expand Up @@ -35,8 +36,8 @@

const addMembersMutation = useAddAdminGroupMembers()

// Get list of existing member usernames in current group
const existingMemberUsernames = new Set(groupMembersData?.data?.items?.map((member) => member.username) || [])
// Group API stores campsite public ids in the `username` field.
const existingMemberIds = new Set(groupMembersData?.data?.items?.map((member) => member.username) || [])

// Fetch members when dialog opens
useEffect(() => {
Expand All @@ -47,8 +48,10 @@
}
}, [groupId, refetchMembers])

const handleMemberToggle = (username: string) => {
setSelectedMembers((prev) => (prev.includes(username) ? prev.filter((u) => u !== username) : [...prev, username]))
const handleMemberToggle = (campsiteUserId: string) => {
setSelectedMembers((prev) =>
prev.includes(campsiteUserId) ? prev.filter((u) => u !== campsiteUserId) : [...prev, campsiteUserId]
)
}

const handleAddMembersSubmit = async () => {
Expand Down Expand Up @@ -145,9 +148,7 @@
<div className='space-y-4'>
{/* Available members to add */}
{(() => {
const availableMembers = members.filter(
(member) => !existingMemberUsernames.has(member.user.username)
)
const availableMembers = members.filter((member) => !existingMemberIds.has(member.user.id))

return availableMembers.length > 0 ? (
<div>
Expand All @@ -157,27 +158,28 @@
<div className='max-h-60 overflow-y-auto rounded-md border border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-900/50'>
<div className='divide-y divide-gray-200 dark:divide-gray-700'>
{availableMembers.map((member) => {
const isSelected = selectedMembers.includes(member.user.username)
const isSelected = selectedMembers.includes(member.user.id)
const handle = megaUserHandle(member.user)

return (
<div
key={member.user.id}
className={`flex cursor-pointer items-center px-4 py-3 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800 ${
isSelected ? 'border-l-4 border-blue-500 bg-blue-50 dark:bg-blue-900/20' : ''
}`}
onClick={() => handleMemberToggle(member.user.username)}
onClick={() => handleMemberToggle(member.user.id)}
>
<input
type='checkbox'
checked={isSelected}
onChange={(e) => {
e.stopPropagation()
handleMemberToggle(member.user.username)
handleMemberToggle(member.user.id)
}}
onClick={(e) => e.stopPropagation()}
className='mr-3 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500'
/>
<img

Check warning on line 182 in moon/apps/web/components/AdminGroups/AddMembersDialog.tsx

View workflow job for this annotation

GitHub Actions / test-web-ui

Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element
src={member.user.avatar_urls?.sm || ''}
alt={member.user.display_name}
className='mr-3 h-8 w-8 shrink-0 rounded-full border border-gray-200 dark:border-gray-600'
Expand All @@ -186,7 +188,7 @@
<p className='text-primary truncate text-sm font-medium'>
{member.user.display_name}
</p>
<p className='text-tertiary truncate text-xs'>@{member.user.username}</p>
<p className='text-tertiary truncate text-xs'>@{handle}</p>
</div>
<div className='ml-3 flex shrink-0 items-center gap-2'>
<span
Expand Down Expand Up @@ -214,7 +216,7 @@

{/* Already in group members */}
{(() => {
const existingMembers = members.filter((member) => existingMemberUsernames.has(member.user.username))
const existingMembers = members.filter((member) => existingMemberIds.has(member.user.id))

return existingMembers.length > 0 ? (
<div>
Expand All @@ -223,36 +225,42 @@
</h3>
<div className='max-h-40 overflow-y-auto rounded-md border border-gray-200 bg-gray-100 dark:border-gray-700 dark:bg-gray-800/50'>
<div className='divide-y divide-gray-200 dark:divide-gray-700'>
{existingMembers.map((member) => (
<div key={member.user.id} className='flex items-center px-4 py-3 opacity-60'>
<div className='mr-3 flex h-4 w-4 items-center justify-center'>
<div className='h-2 w-2 rounded-full bg-green-500'></div>
</div>
<img
src={member.user.avatar_urls?.sm || ''}
alt={member.user.display_name}
className='mr-3 h-8 w-8 shrink-0 rounded-full border border-gray-200 dark:border-gray-600'
/>
<div className='min-w-0 flex-1'>
<p className='truncate text-sm font-medium text-gray-500'>{member.user.display_name}</p>
<p className='truncate text-xs text-gray-400'>@{member.user.username}</p>
</div>
<div className='ml-3 flex shrink-0 items-center gap-2'>
<span
className={`rounded-full px-2 py-1 text-xs font-medium ${
member.role === 'admin'
? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300'
: 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
}`}
>
{member.role}
</span>
<span className='rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-700 dark:bg-green-900/30 dark:text-green-300'>
In Group
</span>
{existingMembers.map((member) => {
const handle = megaUserHandle(member.user)

return (
<div key={member.user.id} className='flex items-center px-4 py-3 opacity-60'>
<div className='mr-3 flex h-4 w-4 items-center justify-center'>
<div className='h-2 w-2 rounded-full bg-green-500'></div>
</div>
<img

Check warning on line 236 in moon/apps/web/components/AdminGroups/AddMembersDialog.tsx

View workflow job for this annotation

GitHub Actions / test-web-ui

Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element
src={member.user.avatar_urls?.sm || ''}
alt={member.user.display_name}
className='mr-3 h-8 w-8 shrink-0 rounded-full border border-gray-200 dark:border-gray-600'
/>
<div className='min-w-0 flex-1'>
<p className='truncate text-sm font-medium text-gray-500'>
{member.user.display_name}
</p>
<p className='truncate text-xs text-gray-400'>@{handle}</p>
</div>
<div className='ml-3 flex shrink-0 items-center gap-2'>
<span
className={`rounded-full px-2 py-1 text-xs font-medium ${
member.role === 'admin'
? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300'
: 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
}`}
>
{member.role}
</span>
<span className='rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-700 dark:bg-green-900/30 dark:text-green-300'>
In Group
</span>
</div>
</div>
</div>
))}
)
})}
</div>
</div>
</div>
Expand Down
75 changes: 47 additions & 28 deletions moon/apps/web/components/AdminGroups/GroupMembersDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import { Button, LoadingSpinner, PlusIcon, TrashIcon } from '@gitmono/ui'

import { useMemberMap } from '@/components/Issues/utils/sideEffect'
import { useAdminGroupMembersList } from '@/hooks/admin/useAdminGroupMembersList'
import { useDeleteAdminGroupMember } from '@/hooks/admin/useDeleteAdminGroupMember'
import { megaUserHandle } from '@/utils/megaUser'

import { AddMembersDialog } from './AddMembersDialog'

Expand All @@ -16,6 +18,7 @@
export const GroupMembersDialog = ({ groupId, groupName, onClose }: GroupMembersDialogProps) => {
const [deletingUsername, setDeletingUsername] = useState<string | null>(null)
const [showAddMembersDialog, setShowAddMembersDialog] = useState(false)
const memberMap = useMemberMap()

// Get current group's member list
const { data: groupMembersData, isLoading } = useAdminGroupMembersList(groupId || 0, {
Expand Down Expand Up @@ -82,35 +85,51 @@
</div>
) : (
<div className='space-y-2'>
{members.map((member) => (
<div
key={member.id}
className='flex items-center rounded-lg border border-gray-200 bg-gray-50 px-4 py-3 transition-colors hover:bg-gray-100 dark:border-gray-700 dark:bg-gray-900/50 dark:hover:bg-gray-800'
>
<div className='mr-4 flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-gray-200 bg-gray-300 dark:border-gray-600 dark:bg-gray-600'>
<span className='text-sm font-medium text-gray-600 dark:text-gray-300'>
{member.username.charAt(0).toUpperCase()}
</span>
</div>
<div className='min-w-0 flex-1'>
<p className='text-primary truncate text-sm font-medium'>@{member.username}</p>
<p className='text-tertiary truncate text-xs'>
Joined: {new Date(member.joined_at * 1000).toLocaleDateString()}
</p>
</div>
<div className='flex shrink-0 items-center gap-3'>
<Button
variant='plain'
size='sm'
onClick={() => handleDeleteMember(member.username)}
disabled={deletingUsername === member.username || deleteMemberMutation.isPending}
className='text-red-600 hover:bg-red-50 hover:text-red-800 dark:hover:bg-red-900/20'
>
{deletingUsername === member.username ? <LoadingSpinner /> : <TrashIcon className='h-4 w-4' />}
</Button>
{members.map((member) => {
const synced = memberMap.get(member.username)
const displayName = megaUserHandle(synced?.user, member.username)
const handle = synced?.user.username || member.username
const avatarUrl = synced?.user.avatar_urls?.sm || synced?.user.avatar_urls?.base
const initial = (displayName || handle).charAt(0).toUpperCase()

return (
<div
key={member.id}
className='flex items-center rounded-lg border border-gray-200 bg-gray-50 px-4 py-3 transition-colors hover:bg-gray-100 dark:border-gray-700 dark:bg-gray-900/50 dark:hover:bg-gray-800'
>
{avatarUrl ? (
<img

Check warning on line 101 in moon/apps/web/components/AdminGroups/GroupMembersDialog.tsx

View workflow job for this annotation

GitHub Actions / test-web-ui

Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element
src={avatarUrl}
alt={displayName}
className='mr-4 h-10 w-10 shrink-0 rounded-full border border-gray-200 dark:border-gray-600'
/>
) : (
<div className='mr-4 flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-gray-200 bg-gray-300 dark:border-gray-600 dark:bg-gray-600'>
<span className='text-sm font-medium text-gray-600 dark:text-gray-300'>{initial}</span>
</div>
)}
<div className='min-w-0 flex-1'>
<p className='text-primary truncate text-sm font-medium'>{displayName}</p>
<p className='text-tertiary truncate text-xs'>
@{handle}
{' · '}
Joined: {new Date(member.joined_at * 1000).toLocaleDateString()}
</p>
</div>
<div className='flex shrink-0 items-center gap-3'>
<Button
variant='plain'
size='sm'
onClick={() => handleDeleteMember(member.username)}
disabled={deletingUsername === member.username || deleteMemberMutation.isPending}
className='text-red-600 hover:bg-red-50 hover:text-red-800 dark:hover:bg-red-900/20'
>
{deletingUsername === member.username ? <LoadingSpinner /> : <TrashIcon className='h-4 w-4' />}
</Button>
</div>
</div>
</div>
))}
)
})}
</div>
)}
</div>
Expand Down
Loading
Loading