Skip to content
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

Server comps #6

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
303 changes: 162 additions & 141 deletions src/app/(app)/dashboard/[slug]/credentials/page.tsx

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/components/credentials/credentials-actions.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client"

import { useState } from "react";
import { DotsHorizontalIcon } from "@radix-ui/react-icons";
import { Button } from "@/components/ui/button";
Expand Down
4 changes: 3 additions & 1 deletion src/components/credentials/credentials-card.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"use client"
import { api } from "@/convex/_generated/api";
import { Credentials, CredentialsRequest } from "@/convex/types";
import { useQuery } from "convex/react";
Expand All @@ -7,6 +8,7 @@ import { formatTimestamp } from "@/lib/utils";
import { UserAvatar } from "@/components/global/user-avatar";
import { EyeIcon, KeyIcon, TimerIcon } from "lucide-react";
import { CredentialsActions } from "./credentials-actions";
import { format } from 'date-fns';

interface CredentialsCardProps {
item: Credentials | CredentialsRequest;
Expand Down Expand Up @@ -89,7 +91,7 @@ export function CredentialsStatusInfo({ credentials }: CredentialsStatusInfoProp
<div className="flex justify-start items-start gap-4 text-muted-foreground">
<div className='flex items-center gap-1'>
<TimerIcon className='w-4 h-4' />
<span>{credentials.expiresAt ? new Date(credentials.expiresAt).toLocaleDateString() : 'No expiration'}</span>
<span>{credentials.expiresAt ? format(new Date(credentials.expiresAt), 'yyyy-MM-dd') : 'No expiration'}</span>
</div>
<div className='flex items-center gap-1'>
<EyeIcon className='w-4 h-4' />
Expand Down
21 changes: 11 additions & 10 deletions src/components/credentials/credentials-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import { DialogContent, DialogTitle, DialogDescription, DialogHeader, DialogTrigger, Dialog } from "@/components/ui/dialog";
import { Dispatch, ReactNode, SetStateAction } from "react";
import { ReactNode } from "react";
import { Id } from "@/convex/_generated/dataModel";
import { Credentials } from "@/convex/types";
import { CredentialsForm } from "./credentials-form";

interface CredentialsDialogProps {
isOpen: boolean,
setIsOpen: (isOpen: boolean) => void;
isOpen: boolean;
onOpenChange: (open: boolean) => void;
onCredentialsCreated?: () => void;
onCredentialsUpdated?: () => void;
onDialogClose?: () => void;
editId?: Id<"credentials">;
existingData?: Credentials;
children?: ReactNode;
formType: 'new' | 'request';
editId?: Id<"credentials">;
existingData?: Credentials;
}

export function CredentialsDialog({ children, isOpen, setIsOpen, onCredentialsCreated, onCredentialsUpdated, onDialogClose, editId, existingData, formType }: CredentialsDialogProps) {
export function CredentialsDialog({ children, isOpen, onOpenChange, onCredentialsCreated, onCredentialsUpdated, onDialogClose, editId, existingData, formType }: CredentialsDialogProps) {

function handleDialogClose() {
setIsOpen(false);
onOpenChange(false);
onDialogClose && onDialogClose()
}

return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogTrigger asChild>
{children}
</DialogTrigger>
Expand All @@ -38,11 +38,12 @@ export function CredentialsDialog({ children, isOpen, setIsOpen, onCredentialsCr
<DialogDescription>
{editId ? 'Update the details for this credential.' :
formType === 'request' ? 'Fill in the details to create new credentials request.' :
'Fill in the details to create new credentials.'}
'Create new credentials'}
</DialogDescription>

</DialogHeader>
<CredentialsForm
setIsOpen={setIsOpen}
setIsOpen={onOpenChange}
editId={editId}
existingData={existingData}
onCredentialsCreated={onCredentialsCreated}
Expand Down
4 changes: 2 additions & 2 deletions src/components/credentials/credentials-form.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client"

import { FormEvent, useState, useEffect } from 'react'
import { useAction, useMutation, useQuery } from 'convex/react'
import { useParams } from 'next/navigation'
Expand All @@ -19,8 +21,6 @@ import { ScrollArea } from '@/components/ui/scroll-area'
import { Card } from '@/components/ui/card'
import { credentialsFields } from '@/lib/config/credentials-fields'
import { CREDENTIALS_TYPES } from '@/convex/schema'
import { fetchAction } from 'convex/nextjs'
import { useSession } from 'next-auth/react'

interface CredentialsFormProps {
setIsOpen: (isOpen: boolean) => void;
Expand Down
139 changes: 87 additions & 52 deletions src/components/credentials/credentials-sort-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,111 @@ import { Input } from '@/components/ui/input';
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@/components/ui/select';
import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label';
import { MultiSelect } from '@/components/ui/multi-select';
import { CredentialsType } from '@/convex/types';
import { CREDENTIALS_TYPES } from '@/convex/schema';
import { credentialsFields } from '@/lib/config/credentials-fields';
import { redirect } from 'next/navigation';
import { Button } from '../ui/button';
import { MultiSelect } from '../ui/multi-select';

interface CredentialsSortControlsProps {
searchTerm: string;
onSearchChange: (value: string) => void;
sortOption: string;
onSortChange: (value: string) => void;
selectedTypes: CredentialsType[];
onTypeChange: (types: string[]) => void;
hideExpired: boolean;
onHideExpiredChange: (checked: boolean) => void;
showHideExpired: boolean;
baseUrl: string;
}

export function CredentialsSortControls({ searchTerm, onSearchChange, sortOption, onSortChange, selectedTypes, onTypeChange, hideExpired, onHideExpiredChange, showHideExpired }: CredentialsSortControlsProps) {
export async function CredentialsSortControls({
searchTerm,
sortOption,
selectedTypes,
hideExpired,
showHideExpired,
baseUrl
}: CredentialsSortControlsProps) {
async function updateFilters(formData: FormData) {
'use server';

const params = new URLSearchParams();
const search = formData.get('search')?.toString() || '';
const sort = formData.get('sort')?.toString() || 'name';
const types = formData.getAll('types');
const hideExpired = formData.get('hideExpired') === 'on';

if (search) params.set('search', search);
if (sort !== 'name') params.set('sort', sort);
if (types.length > 0) params.set('types', types.join(','));
if (hideExpired) params.set('hideExpired', 'true');

redirect(`${baseUrl}?${params.toString()}`);
}

const credentialTypeOptions = Object.values(CREDENTIALS_TYPES).map(type => ({
value: type,
label: credentialsFields[type][0]?.label
}));

return (
<>
<div className="flex items-center gap-6 w-full">
<Input
type="text"
placeholder="Search by name"
value={searchTerm}
onChange={(e) => onSearchChange(e.target.value)}
className='flex'
/>
<MultiSelect
options={credentialTypeOptions}
onValueChange={onTypeChange}
defaultValue={selectedTypes}
placeholder="Search by type"
variant="default"
maxCount={1}
/>
<Select value={sortOption} onValueChange={onSortChange}>
<SelectTrigger>
<SelectValue placeholder="Sort by" />
</SelectTrigger>
<SelectContent>
<SelectItem value="name">Sort by name</SelectItem>
<SelectItem value="createdAtAsc">Sort by date created (asc)</SelectItem>
<SelectItem value="createdAtDesc">Sort by date created (desc)</SelectItem>
<SelectItem value="updatedAt">Sort by date updated</SelectItem>
</SelectContent>
</Select>
{showHideExpired && (
<div className="flex items-center space-x-2 whitespace-nowrap">
<Checkbox
id="hideExpired"
checked={hideExpired}
onCheckedChange={(checked) => onHideExpiredChange(checked as boolean)}
className='rounded-[5px]'
/>
<Label
htmlFor="hideExpired"
className="peer-disabled:opacity-70 font-medium text-sm leading-none peer-disabled:cursor-not-allowed"
>
Hide expired
</Label>
</div>
)}
</div>
</>
<form action={updateFilters} className="flex items-center gap-6 w-full">
<Input
type="text"
name="search"
placeholder="Search by name"
defaultValue={searchTerm}
className='flex'
/>

{/* TODO: FIX THIS */}
{/* <MultiSelect
name="types"
options={credentialTypeOptions}
onValueChange={(values) => {
const form = document.querySelector('form');
const input = form?.querySelector('input[name="types"]') as HTMLInputElement;
if (input) {
input.value = values.join(',');
}
}}
defaultValue={selectedTypes}
placeholder="Select types"
maxCount={3}
/>
<input type="hidden" name="types" value={selectedTypes.join(',')} /> */}

<Select name="sort" defaultValue={sortOption}>
<SelectTrigger>
<SelectValue placeholder="Sort by" />
</SelectTrigger>
<SelectContent>
<SelectItem value="name">Sort by name</SelectItem>
<SelectItem value="createdAtAsc">Sort by date created (asc)</SelectItem>
<SelectItem value="createdAtDesc">Sort by date created (desc)</SelectItem>
<SelectItem value="updatedAt">Sort by date updated</SelectItem>
</SelectContent>
</Select>

{showHideExpired && (
<div className="flex items-center space-x-2 whitespace-nowrap">
<Checkbox
id="hideExpired"
name="hideExpired"
defaultChecked={hideExpired}
className='rounded-[5px]'
/>
<Label
htmlFor="hideExpired"
className="peer-disabled:opacity-70 font-medium text-sm leading-none peer-disabled:cursor-not-allowed"
>
Hide expired
</Label>
</div>
)}

<Button type="submit" variant="ghost">
Apply Filters
</Button>
</form>
);
}
17 changes: 7 additions & 10 deletions src/components/global/page-pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,24 @@ import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, Pagi
interface PaginationComponentProps {
currentPage: number;
totalPages: number;
setCurrentPage: (page: number) => void;
hrefBuilder: (page: number) => string;
}

export function PagePagination({ currentPage, totalPages, setCurrentPage }: PaginationComponentProps) {
export function PagePagination({ currentPage, totalPages, hrefBuilder }: PaginationComponentProps) {
return (
<Pagination className="pb-4 text-primary/70">
<PaginationContent>
<PaginationItem className="hover:text-primary">
<PaginationPrevious
href="#"
onClick={() => setCurrentPage(Math.max(currentPage - 1, 1))}
href={hrefBuilder(Math.max(currentPage - 1, 1))}
/>
</PaginationItem>

{[...Array(totalPages)].map((_, index) => (
<PaginationItem key={index} className="hover:text-primary">
<PaginationLink
href="#"
onClick={() => setCurrentPage(index + 1)}
className={currentPage === index + 1 ? "font-bold" : ""}
href={hrefBuilder(index + 1)}
isActive={currentPage === index + 1}
>
{index + 1}
</PaginationLink>
Expand All @@ -37,11 +35,10 @@ export function PagePagination({ currentPage, totalPages, setCurrentPage }: Pagi

<PaginationItem className="hover:text-primary">
<PaginationNext
href="#"
onClick={() => setCurrentPage(Math.min(currentPage + 1, totalPages))}
href={hrefBuilder(Math.min(currentPage + 1, totalPages))}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
}
1 change: 1 addition & 0 deletions src/components/ui/multi-select.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"use client"
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import {
Expand Down
Empty file removed src/lib/actions.ts
Empty file.
3 changes: 3 additions & 0 deletions src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Resend from 'next-auth/providers/resend';
import { ConvexAdapter } from '@/lib/convex-adapter';
import { getURL } from './utils';


if (process.env.CONVEX_AUTH_PRIVATE_KEY === undefined) {
throw new Error('Missing CONVEX_AUTH_PRIVATE_KEY Next.js environment variable');
}
Expand Down Expand Up @@ -68,3 +69,5 @@ declare module 'next-auth' {
convexToken: string;
}
}