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
73 changes: 71 additions & 2 deletions apps/platform/src/features/resources/components/resourcesList.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,84 @@
import { LockIcon } from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { Button, Input } from "@opencircle/ui";
import { useState } from "react";
import { useAccount } from "../../../features/auth/hooks/useAccount";
import { api } from "../../../utils/api";
import { useResources } from "../hooks/useResources";
import { ResourceCard } from "./resourceCard";
import { ResourcesListSkeleton } from "./resourcesListSkeleton";

interface ResourcesListProps {
channelId?: string;
}

export const ResourcesList = ({ channelId }: ResourcesListProps) => {
const { resources, isResourcesLoading } = useResources(channelId);
const { resources, isResourcesLoading, error } = useResources(channelId);
const [inviteCode, setInviteCode] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const { account } = useAccount();

const handleRequestAccess = async () => {
if (!inviteCode.trim()) return;

setIsSubmitting(true);
try {
if (!account?.id) {
alert("Please log in to request access");
return;
}

const response = await api.inviteCodes.validate({
code: inviteCode,
user_id: account.id,
});

if (response.valid) {
// Successfully validated - refresh resources to show content
window.location.reload();
} else {
alert(response.message || "Failed to validate invite code");
}
} catch (err) {
console.error("Error validating invite code:", err);
alert("An error occurred while validating the invite code");
} finally {
setIsSubmitting(false);
}
};

if (isResourcesLoading) {
return <div className="p-4">Loading resources...</div>;
return <ResourcesListSkeleton />;
}

if (error) {
return (
<div className="flex h-90 flex-col items-center justify-center gap-4">
<div className="rounded-xl bg-background-secondary p-4">
<HugeiconsIcon icon={LockIcon} className="text-foreground" />
</div>
<div className="text-balance">
You are not eligible to access this content.
</div>
{account?.id && (
<div className="space-y-2">
<Input
placeholder="Invite Code"
className="w-full"
value={inviteCode}
onChange={(e) => setInviteCode(e.target.value)}
/>
<Button
className="w-full"
onClick={handleRequestAccess}
disabled={isSubmitting}
>
{isSubmitting ? "Processing..." : "Request Access"}
</Button>
</div>
)}
</div>
);
}

if (!resources || resources.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const ResourcesListSkeleton = () => {
return (
<div className="space-y-4 p-4">
{Array.from({ length: 5 }).map((_, index) => (
<div key={index} className="rounded-lg border border-border p-4">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1 space-y-3">
{/* URL link skeleton */}
<div className="flex items-center gap-2">
<div className="h-4 w-3/4 animate-pulse rounded bg-background-secondary" />
<div className="h-3 w-3 animate-pulse rounded bg-background-secondary" />
</div>

{/* Description skeleton - only show if there might be one */}
<div className="space-y-2">
<div className="h-4 w-full animate-pulse rounded bg-background-secondary" />
<div className="h-4 w-5/6 animate-pulse rounded bg-background-secondary" />
</div>

{/* Footer info skeleton */}
<div className="flex items-center gap-2">
<div className="h-3 w-20 animate-pulse rounded bg-background-secondary" />
<div className="h-3 w-2 animate-pulse rounded bg-background-secondary" />
<div className="h-3 w-24 animate-pulse rounded bg-background-secondary" />
</div>
</div>
</div>
</div>
))}
</div>
);
};
11 changes: 10 additions & 1 deletion apps/platform/src/features/resources/hooks/useResources.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import { useQuery } from "@tanstack/react-query";
import { HTTPError } from "ky";
import { api } from "../../../utils/api";

export const useResources = (channelId?: string) => {
const { data, isLoading } = useQuery({
const { data, isLoading, error } = useQuery({
queryKey: ["resources", { channelId }],
queryFn: async () => {
if (channelId) {
return await api.resources.getByChannel(channelId);
}
return await api.resources.getAll();
},
retry: (failureCount, error) => {
if (error instanceof HTTPError && error.response.status === 403) {
console.error("Not Eligible to access");
return false;
}
return failureCount < 3;
},
});

return {
resources: data,
isResourcesLoading: isLoading,
error,
};
};