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
18 changes: 18 additions & 0 deletions app/Actions/GetCommunityLinks.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use App\Data\Requests\GetCommunityLinksRequest;
use App\Models\Link;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\LengthAwarePaginator;

Expand All @@ -22,6 +23,7 @@ public function execute(GetCommunityLinksRequest $data): LengthAwarePaginator
->tap($this->search($data->search))
->tap($this->filterAuthor($data->author))
->tap($this->filterTags($data->tags))
->tap($this->filterUser($data->user))
->latest('published_at')
->latest('id')
->with(['author', 'user', 'tags'])
Expand Down Expand Up @@ -78,4 +80,20 @@ private function filterTags(?array $tags): callable
);
};
}

private function filterUser(?string $userUuid): callable
{
if ($userUuid === null) {
return function (Builder $query): void {};
}

$user = User::query()->where('uuid', $userUuid)->first();
if ($user === null) {
return function (Builder $query): void {};
}

return function (Builder $query) use ($user): void {
$query->where('user_id', $user->id);
};
}
}
45 changes: 45 additions & 0 deletions app/Actions/GetCommunityUsers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace App\Actions;

use App\Models\Link;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;

class GetCommunityUsers
{
/**
* @return Collection<int, User>
*/
public function execute(?string $search): Collection
{
return User::query()
->whereExists(
Link::query()
->wherePublic()
->wherePublished()
->whereColumn('users.id', 'links.user_id')
)
->tap($this->search($search))
->orderBy('username')
->limit(30)
->get();
}

/**
* @return callable(Builder<User>): void
*/
private function search(?string $search): callable
{
if ($search === null) {
return function (Builder $query): void {};
}

return function (Builder $query) use ($search): void {
$query->whereLike('username', "%{$search}%");
};
}
}
4 changes: 3 additions & 1 deletion app/Data/Requests/GetCommunityLinksRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public function __construct(
public ?string $search,
public ?string $author,
#[LiteralTypeScriptType('string[]')]
public ?array $tags
public ?array $tags,
public ?string $user,
) {}

/**
Expand All @@ -35,6 +36,7 @@ public static function rules(): array
'search' => ['max:255'],
'author' => ['max:255'],
'tags.*' => ['required', 'string', 'max:255'],
'user' => ['uuid'],
];
}
}
4 changes: 4 additions & 0 deletions app/Http/Controllers/CommunityLinkController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use App\Actions\GetCommunityLinks;
use App\Data\Requests\GetCommunityLinksRequest;
use App\Data\Resources\CommunityLinkResource;
use App\Data\Resources\UserResource;
use App\Models\User;
use Inertia\Inertia;
use Inertia\Response;

Expand All @@ -17,10 +19,12 @@ public function index(
GetCommunityLinks $getCommunityLinks
): Response {
$links = $getCommunityLinks->execute($data);
$user = $data->user !== null ? User::query()->where('uuid', $data->user)->first() : null;

return Inertia::render('community-links/index', [
'request' => $data->onlyNotNull(),
'links' => $links->currentPage() === 1 ? CommunityLinkResource::collect($links) : Inertia::deepMerge(CommunityLinkResource::collect($links)),
'user' => $user !== null ? UserResource::from($user) : null,
]);
}
}
21 changes: 21 additions & 0 deletions app/Http/Controllers/CommunityUserController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Actions\GetCommunityUsers;
use App\Data\Requests\SearchRequest;
use App\Data\Resources\JsonResource;
use App\Data\Resources\UserResource;

class CommunityUserController
{
public function index(SearchRequest $data, GetCommunityUsers $getCommunityUsers): JsonResource
{
return JsonResource::collection(
$getCommunityUsers->execute($data->search),
UserResource::class
);
}
}
11 changes: 9 additions & 2 deletions resources/js/components/community-link-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Datetime } from '@/components/ui/datetime';
import { Pill } from '@/components/ui/pill';
import { cn } from '@/lib/utils';
import { Link } from '@inertiajs/react';
import { ArrowUpRight, User } from 'lucide-react';
import { ArrowUpRight, SquareUser, User } from 'lucide-react';
import CommunityLinkResource = App.Data.Resources.CommunityLinkResource;

export default function CommunityLinkCard({ link, className }: { link: CommunityLinkResource; className?: string }) {
Expand All @@ -18,7 +18,14 @@ export default function CommunityLinkCard({ link, className }: { link: Community
<ArrowUpRight></ArrowUpRight>
</a>
</div>
<CardDescription>By {link.user.username}</CardDescription>
<CardDescription>
<Link href={route('community-links.index', { user: link.user.uuid })}>
<div className="flex items-center space-x-1">
<SquareUser size={16} />
<span> {link.user.username}</span>
</div>
</Link>
</CardDescription>
</CardHeader>

<CardContent className="space-y-4">
Expand Down
6 changes: 3 additions & 3 deletions resources/js/components/ui/autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,14 @@ export default function Autocomplete<T>({className, value, options, onValueChang
{showDropdown && (
<div
ref={dropdownRef}
className="absolute z-10 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-60 overflow-y-auto"
className="absolute z-10 w-full mt-1 bg-background border text-foreground rounded-lg shadow-lg max-h-60 overflow-y-auto"
>
{filteredOptions.map((option, index) => (
<button
key={getValueUsing(option)}
onClick={() => handleOptionSelected(option)}
className={`w-full text-left px-3 py-2 hover:bg-gray-50 ${
index === focusedIndex ? 'bg-primary/5 text-primary' : 'text-gray-700'
className={`w-full text-left px-3 py-2 hover:bg-gray-50 dark:hover:text-background ${
index === focusedIndex ? 'bg-primary/5 text-primary dark:bg-primary/40' : 'text-foreground'
}`}
>
{showUsing(option)}
Expand Down
6 changes: 3 additions & 3 deletions resources/js/components/ui/multi-suggest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,14 @@ export default function MultiSuggest({className, suggestions, selectedSuggestion
{showDropdown && (
<div
ref={dropdownRef}
className="absolute z-10 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-60 overflow-y-auto"
className="absolute z-10 w-full mt-1 bg-background border text-foreground rounded-lg shadow-lg max-h-60 overflow-y-auto"
>
{filteredSuggestions.map((suggestion, index) => (
<button
key={suggestion}
onClick={() => handleSuggestionSelect(suggestion)}
className={`w-full text-left px-3 py-2 hover:bg-gray-50 ${
index === focusedIndex ? 'bg-primary/5 text-primary' : 'text-gray-700'
className={`w-full text-left px-3 py-2 hover:bg-gray-50 dark:hover:text-background ${
index === focusedIndex ? 'bg-primary/5 text-primary dark:bg-primary/40' : 'text-foreground'
}`}
>
{suggestion}
Expand Down
4 changes: 2 additions & 2 deletions resources/js/components/ui/pill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ function Pill({
>
{children}
{ onClose && (
<span onClick={onClose} className="cursor-pointer">
<X height={18} width={18}/>
<span onClick={onClose} className="cursor-pointer text-muted-foreground hover:text-foreground">
<X size={16} />
</span>
)}
</span>
Expand Down
10 changes: 5 additions & 5 deletions resources/js/components/ui/remote-autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const CACHE_INVALIDATION_DURATION_MS = 30000;

export default function RemoteAutocomplete<T>({className, value, fetchOptionsUsing, onValueChanged, showUsing, getValueUsing, resetOnSelected, ...props }: React.ComponentProps<"input"> & {
fetchOptionsUsing: (value: string) => Promise<void|T[]>
onValueChanged: (value: string) => void
onValueChanged: (value: T) => void
showUsing: (value: T) => string
getValueUsing: (value: T) => string
resetOnSelected?: boolean
Expand Down Expand Up @@ -145,7 +145,7 @@ export default function RemoteAutocomplete<T>({className, value, fetchOptionsUsi

const handleOptionSelected = (option: T) => {
setInputValue(resetOnSelected ? '' : showUsing(option));
onValueChanged(getValueUsing(option));
onValueChanged(option);
setShowDropdown(false);
setFocusedIndex(-1);
};
Expand Down Expand Up @@ -177,14 +177,14 @@ export default function RemoteAutocomplete<T>({className, value, fetchOptionsUsi
{showDropdown && (
<div
ref={dropdownRef}
className="absolute z-10 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-60 overflow-y-auto"
className="absolute z-10 w-full mt-1 bg-background border text-foreground rounded-lg shadow-lg max-h-60 overflow-y-auto"
>
{options.map((option, index) => (
<button
key={getValueUsing(option)}
onClick={() => handleOptionSelected(option)}
className={`w-full text-left px-3 py-2 hover:bg-gray-50 ${
index === focusedIndex ? 'bg-primary/5 text-primary' : 'text-gray-700'
className={`w-full text-left px-3 py-2 hover:bg-gray-50 dark:hover:text-background ${
index === focusedIndex ? 'bg-primary/5 text-primary dark:bg-primary/40' : 'text-foreground'
}`}
>
{showUsing(option)}
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/ui/remote-multi-autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function RemoteMultiAutocomplete<T>({
}: React.ComponentProps<'input'> & {
selectedValues: string[]
fetchOptionsUsing: (value: string) => Promise<void | T[]>
onValueAdded: (value: string) => void
onValueAdded: (value: T) => void
showUsing: (value: T) => string
getValueUsing: (value: T) => string
}) {
Expand Down
6 changes: 3 additions & 3 deletions resources/js/components/ui/suggest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,14 @@ export default function Suggest({className, suggestions, onSuggestionSelected, .
{showDropdown && (
<div
ref={dropdownRef}
className="absolute z-10 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-60 overflow-y-auto"
className="absolute z-10 w-full mt-1 bg-background border text-foreground rounded-lg shadow-lg max-h-60 overflow-y-auto"
>
{filteredSuggestions.map((suggestion, index) => (
<button
key={suggestion}
onClick={() => handleSuggestionSelect(suggestion)}
className={`w-full text-left px-3 py-2 hover:bg-gray-50 ${
index === focusedIndex ? 'bg-primary/5 text-primary' : 'text-gray-700'
className={`w-full text-left px-3 py-2 hover:bg-gray-50 dark:hover:text-background ${
index === focusedIndex ? 'bg-primary/5 text-primary dark:bg-primary/40' : 'text-foreground'
}`}
>
{suggestion}
Expand Down
Loading