-
-
Notifications
You must be signed in to change notification settings - Fork 392
Add search to server select #911
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| <?php | ||
|
|
||
| namespace App\Actions\Server; | ||
|
|
||
| use App\Models\Project; | ||
| use Illuminate\Support\Collection; | ||
| use Illuminate\Support\Facades\Validator; | ||
|
|
||
| class GetServers | ||
| { | ||
| public function get(Project $project, array $input, int $perPage = 10): Collection | ||
| { | ||
| $validated = $this->validate($input); | ||
|
|
||
| $serversQuery = $project->servers(); | ||
|
|
||
| if (! empty($validated['query'])) { | ||
| $serversQuery->where('name', 'like', "%{$validated['query']}%"); | ||
| } | ||
|
|
||
| $page = $validated['page'] ?? 1; | ||
|
|
||
| return $serversQuery | ||
| ->skip(($page - 1) * $perPage) | ||
| ->take($perPage) | ||
| ->get(); | ||
| } | ||
|
|
||
| private function validate(array $input): array | ||
| { | ||
| return Validator::make($input, [ | ||
| 'query' => [ | ||
| 'nullable', | ||
| 'string', | ||
| ], | ||
| 'page' => [ | ||
| 'nullable', | ||
| 'integer', | ||
| 'min:1', | ||
| ], | ||
| ])->validate(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,83 +1,84 @@ | ||
| import { type SharedData } from '@/types'; | ||
| import { type Server } from '@/types/server'; | ||
| import { useForm, usePage } from '@inertiajs/react'; | ||
| import { useState } from 'react'; | ||
| import { | ||
| DropdownMenu, | ||
| DropdownMenuCheckboxItem, | ||
| DropdownMenuContent, | ||
| DropdownMenuItem, | ||
| DropdownMenuSeparator, | ||
| DropdownMenuTrigger, | ||
| } from '@/components/ui/dropdown-menu'; | ||
| import { useState, useEffect } from 'react'; | ||
| import { Button } from '@/components/ui/button'; | ||
| import { ChevronsUpDownIcon, PlusIcon } from 'lucide-react'; | ||
| import { useInitials } from '@/hooks/use-initials'; | ||
| import { Avatar, AvatarFallback } from '@/components/ui/avatar'; | ||
| import { type Server } from '@/types/server'; | ||
| import type { SharedData } from '@/types'; | ||
| import CreateServer from '@/pages/servers/components/create-server'; | ||
| import ServerSelect from '@/pages/servers/components/server-select'; | ||
| import { CommandGroup, CommandItem } from '@/components/ui/command'; | ||
|
|
||
| export function ServerSwitch() { | ||
| const page = usePage<SharedData>(); | ||
| const [selectedServer, setSelectedServer] = useState(page.props.server || null); | ||
| const [open, setOpen] = useState(false); | ||
| const [serverFormOpen, setServerFormOpen] = useState(false); | ||
| const [selected, setSelected] = useState<string>(page.props.server?.id?.toString() ?? ''); | ||
| const initials = useInitials(); | ||
| const form = useForm(); | ||
|
|
||
| const handleServerChange = (server: Server) => { | ||
| setSelectedServer(server); | ||
| useEffect(() => { | ||
| setSelected(page.props.server?.id?.toString() ?? ''); | ||
| }, [page.props.server?.id]); | ||
|
|
||
| const handleServerChange = (value: string, server: Server) => { | ||
| setSelected(value); | ||
| setOpen(false); | ||
| form.post(route('servers.switch', { server: server.id })); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex items-center"> | ||
| <DropdownMenu modal={false}> | ||
| <DropdownMenuTrigger asChild> | ||
| <Button variant="ghost" className="px-1!"> | ||
| {selectedServer && ( | ||
| <> | ||
| <Avatar className="size-6 rounded-sm"> | ||
| <AvatarFallback className="rounded-sm">{initials(selectedServer?.name ?? '')}</AvatarFallback> | ||
| </Avatar> | ||
| <span className="hidden lg:flex">{selectedServer?.name}</span> | ||
| </> | ||
| )} | ||
| const footer = ( | ||
| <CommandGroup> | ||
| <CreateServer defaultOpen={serverFormOpen} onOpenChange={setServerFormOpen}> | ||
| <CommandItem | ||
| value="create-server" | ||
| onSelect={() => { | ||
| setServerFormOpen(true); | ||
| }} | ||
| className="gap-0" | ||
| > | ||
| <div className="flex items-center"> | ||
| <PlusIcon size={5} /> | ||
| <span className="ml-2">Create new server</span> | ||
| </div> | ||
| </CommandItem> | ||
| </CreateServer> | ||
| </CommandGroup> | ||
| ); | ||
|
|
||
| {!selectedServer && ( | ||
| <> | ||
| <Avatar className="size-6 rounded-sm"> | ||
| <AvatarFallback className="rounded-sm">S</AvatarFallback> | ||
| </Avatar> | ||
| <span className="hidden lg:flex">Select a server</span> | ||
| </> | ||
| )} | ||
| const trigger = ( | ||
| <Button variant="ghost" className="px-1!"> | ||
| {page.props.server ? ( | ||
| <> | ||
| <Avatar className="size-6 rounded-sm"> | ||
| <AvatarFallback className="rounded-sm">{initials(page.props.server?.name ?? '')}</AvatarFallback> | ||
| </Avatar> | ||
| <span className="hidden lg:flex">{page.props.server?.name}</span> | ||
| </> | ||
| ) : ( | ||
| <> | ||
| <Avatar className="size-6 rounded-sm"> | ||
| <AvatarFallback className="rounded-sm">S</AvatarFallback> | ||
| </Avatar> | ||
| <span className="hidden lg:flex">Select a server</span> | ||
| </> | ||
| )} | ||
| <ChevronsUpDownIcon size={5} /> | ||
|
||
| </Button> | ||
| ); | ||
|
|
||
| <ChevronsUpDownIcon size={5} /> | ||
| </Button> | ||
| </DropdownMenuTrigger> | ||
| <DropdownMenuContent className="w-56" align="start"> | ||
| {page.props.project_servers.length > 0 ? ( | ||
| page.props.project_servers.map((server) => ( | ||
| <DropdownMenuCheckboxItem | ||
| key={`server-${server.id.toString()}`} | ||
| checked={selectedServer?.id === server.id} | ||
| onCheckedChange={() => handleServerChange(server)} | ||
| > | ||
| {server.name} | ||
| </DropdownMenuCheckboxItem> | ||
| )) | ||
| ) : ( | ||
| <DropdownMenuItem disabled>No servers</DropdownMenuItem> | ||
| )} | ||
| <DropdownMenuSeparator /> | ||
| <CreateServer> | ||
| <DropdownMenuItem className="gap-0" onSelect={(e) => e.preventDefault()}> | ||
| <div className="flex items-center"> | ||
| <PlusIcon size={5} /> | ||
| <span className="ml-2">Create new server</span> | ||
| </div> | ||
| </DropdownMenuItem> | ||
| </CreateServer> | ||
| </DropdownMenuContent> | ||
| </DropdownMenu> | ||
| return ( | ||
| <div className="flex items-center"> | ||
| <ServerSelect | ||
| value={selected} | ||
| onValueChangeAdvanced={handleServerChange} | ||
| trigger={trigger} | ||
| open={open} | ||
| onOpenChange={setOpen} | ||
| footer={footer} | ||
| showIp={false} | ||
| /> | ||
| </div> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
sizeprop value5appears to be incorrect. ThePlusIconfrom lucide-react expects pixel values or uses CSS classes for sizing. In the original code (removed DropdownMenuItem), this was alsosize={5}. This should likely besize={16}or use theclassNameprop with Tailwind classes likeclassName=\"size-4\"for consistency with other icons in the codebase (see line 151 in server-select.tsx which usesclassName=\"ml-2 size-4 shrink-0 opacity-50\").