Skip to content

Commit

Permalink
add back working row actions
Browse files Browse the repository at this point in the history
  • Loading branch information
malmz committed May 17, 2024
1 parent 67cf483 commit 6935e1e
Show file tree
Hide file tree
Showing 19 changed files with 626 additions and 250 deletions.
63 changes: 63 additions & 0 deletions app/components/form/date-picker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { useRef, useState } from 'react';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
import { Button } from '../ui/button';
import { cn } from '~/lib/utils';
import { CalendarIcon } from 'lucide-react';
import { format } from 'date-fns';
import { Calendar } from '../ui/calendar';
import { sv } from 'date-fns/locale';
import { useField } from './form';

interface Props
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'defaultValue'> {
defaultValue?: Date;
}
export function FormDatePicker({ name, defaultValue, ...props }: Props) {
const triggerRef = useRef<HTMLButtonElement>(null);
const { id, errorId } = useField();
const [value, setValue] = useState(defaultValue);

return (
<div>
<input
id={id}
className='sr-only'
aria-hidden
aria-describedby={errorId}
tabIndex={-1}
name={name}
defaultValue={defaultValue?.toISOString() ?? ''}
onFocus={() => {
triggerRef.current?.focus();
}}
/>
<Popover>
<PopoverTrigger asChild>
<Button
ref={triggerRef}
variant={'outline'}
className={cn(
'flex w-full justify-start text-left font-normal focus:ring-2 focus:ring-stone-950 focus:ring-offset-2',
!value && 'text-muted-foreground'
)}
>
<CalendarIcon className='mr-2 h-4 w-4' />
{value ? (
format(value, 'PPP', { locale: sv })
) : (
<span>Pick a date</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className='w-auto p-0'>
<Calendar
mode='single'
selected={value}
onSelect={setValue}
initialFocus
/>
</PopoverContent>
</Popover>
</div>
);
}
49 changes: 49 additions & 0 deletions app/components/form/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { createContext, useContext, useId } from 'react';
import { cn } from '~/lib/utils';
import { Label } from '../ui/label';

const FieldContext = createContext<{ id?: string; errorId?: string }>({});
export const useField = () => useContext(FieldContext);

interface ErrorProps extends React.HTMLAttributes<HTMLDivElement> {}
export function FormError({ className, ...props }: ErrorProps) {
const { id } = useField();
return (
<div
id={id}
className={cn('text-sm font-medium text-destructive', className)}
{...props}
></div>
);
}

interface LabelProps extends React.ComponentProps<typeof Label> {}
export function FormLabel({ ...props }: LabelProps) {
const { id } = useField();
return <Label htmlFor={id} {...props}></Label>;
}

interface DescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {}
export function FormDescription({ className, ...props }: DescriptionProps) {
const { errorId } = useField();
return (
<p
id={errorId}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
);
}

export function FormField({
className,
...props
}: React.HTMLAttributes<HTMLParagraphElement>) {
const id = useId();
const errorId = useId();
return (
<FieldContext.Provider value={{ id, errorId }}>
<div className={cn('space-y-2', className)} {...props}></div>
</FieldContext.Provider>
);
}
9 changes: 9 additions & 0 deletions app/components/form/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Input } from '../ui/input';
import type { ComponentProps } from 'react';
import { useField } from './form';

interface Props extends ComponentProps<typeof Input> {}
export function FormInput({ ...props }: Props) {
const { id, errorId } = useField();
return <Input id={id} aria-describedby={errorId} {...props} />;
}
9 changes: 9 additions & 0 deletions app/components/form/textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Textarea } from '~/components/ui/textarea';
import type { ComponentProps } from 'react';
import { useField } from './form';

interface Props extends ComponentProps<typeof Textarea> {}
export function FormTextarea({ ...props }: Props) {
const { id, errorId } = useField();
return <Textarea id={id} aria-describedby={errorId} {...props} />;
}
20 changes: 15 additions & 5 deletions app/lib/actions.server.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { and, asc, eq, isNull, sql, type InferInsertModel } from 'drizzle-orm';
import { db } from './db.server';
import { album, image, type CreateAlbum } from './schema.server';
import { deleteImageFiles } from './storage/image';

export async function createAlbum(data: CreateAlbum) {
return await db.insert(album).values(data).returning({ id: album.id });
}

export async function updateAlbum(id: number, data: CreateAlbum) {
const moreData: Partial<InferInsertModel<typeof album>> = {
...data,
modified_at: new Date(),
};
await db.update(album).set(moreData).where(eq(album.id, id));
await db.update(album).set(data).where(eq(album.id, id));
}

export async function setThumbnail(id: number, thumbnail_id: number) {
await db.update(album).set({ thumbnail_id }).where(eq(album.id, id));
}

// NOTE: This function lets indirectly set the thumbnail of an album if empty.
Expand Down Expand Up @@ -39,3 +40,12 @@ export async function setPubishedStatus(id: number, published: boolean) {
await db.update(album).set({ published }).where(eq(album.id, id));
});
}

export async function deleteAlbum(id: number) {
await db.delete(album).where(eq(album.id, id));
}

export async function deleteImage(id: number) {
const [data] = await db.delete(image).where(eq(image.id, id)).returning();
deleteImageFiles(data);
}
2 changes: 1 addition & 1 deletion app/lib/auth.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function ensureRole(
...params,
})(request);
if (!ctx.isAuthenticated) {
throw redirect('/login');
throw redirect('/auth/login');
}

for (const role of roles) {
Expand Down
7 changes: 4 additions & 3 deletions app/lib/schema.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { Serializable } from '@remix-run/react/future/single-fetch';
import type { InferInsertModel, InferSelectModel } from 'drizzle-orm';
import { relations } from 'drizzle-orm';
import {
Expand All @@ -12,6 +11,7 @@ import {
timestamp,
} from 'drizzle-orm/pg-core';
import type { Exif } from 'exif-reader';
import type { Jsonify } from '@remix-run/server-runtime/dist/jsonify';

export const album = pgTable('album', {
id: serial('id').primaryKey(),
Expand All @@ -26,7 +26,8 @@ export const album = pgTable('album', {
.notNull(),
modified_at: timestamp('modified_at', { withTimezone: true })
.defaultNow()
.notNull(),
.notNull()
.$onUpdateFn(() => new Date()),
});

export const albumRelation = relations(album, ({ many }) => ({
Expand All @@ -42,7 +43,7 @@ export type CreateAlbum = Omit<
export const image = pgTable('image', {
id: serial('id').primaryKey(),
legacy_id: text('legacy_id').unique(),
exif_data: jsonb('exif_data').$type<Serializable>(),
exif_data: jsonb('exif_data').$type<Jsonify<Exif>>(),
mimetype: text('mimetype'),
album_id: integer('album_id')
.references(() => album.id)
Expand Down
25 changes: 20 additions & 5 deletions app/lib/storage/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { dirname } from 'node:path';
import { ImageError, type ImageRecord, type ImageStream } from './types';
import { safeStat } from './utils';
import { getLegacyImageStream } from './legacy';
import { stat, mkdir, rename } from 'node:fs/promises';
import { stat, mkdir, rename, rm } from 'node:fs/promises';
import { createOptimized } from './optimizer';
import { getImagePath, getPreviewPath, getThumbnailPath } from './paths';

Expand Down Expand Up @@ -42,8 +42,23 @@ export async function getImageStream(
}

export async function commitUpload(stagePath: string, image: ImageRecord) {
const path = getImagePath(image);
await mkdir(dirname(path), { recursive: true });
await rename(stagePath, path);
createOptimized(path, getThumbnailPath(image), getPreviewPath(image));
const imagePath = getImagePath(image);
const thumbnailPath = getThumbnailPath(image);
const previewPath = getPreviewPath(image);
await mkdir(dirname(imagePath), { recursive: true });
await rename(stagePath, imagePath);
mkdir(dirname(thumbnailPath), { recursive: true });
mkdir(dirname(previewPath), { recursive: true });
createOptimized(imagePath, getThumbnailPath(image), getPreviewPath(image));
}

export async function deleteImageFiles(image: ImageRecord) {
const imagePath = getImagePath(image);
const thumbnailPath = getThumbnailPath(image);
const previewPath = getPreviewPath(image);
await Promise.all(
[imagePath, thumbnailPath, previewPath].map((path) =>
rm(path, { force: true })
)
);
}
48 changes: 8 additions & 40 deletions app/routes/_main.admin.$id/columns.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,16 @@
import { SortButton } from '~/components/data-table';
import { Button } from '~/components/ui/button';
import { Checkbox } from '~/components/ui/checkbox';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '~/components/ui/dropdown-menu';
import type { Image as ImageType } from '~/lib/schema.server';
import type { ColumnDef } from '@tanstack/react-table';
import { createColumnHelper } from '@tanstack/react-table';
import { format } from 'date-fns';
import { Check, Link as LinkIcon, MoreHorizontal, Trash2 } from 'lucide-react';
import { Check } from 'lucide-react';
import { Link } from '@remix-run/react';
import { RowActions } from './row-actions';

type ItemType = ImageType & { thumbnail: boolean };
const cb = createColumnHelper<ItemType>();

function RowActions() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant='ghost' className='h-8 w-8 p-0'>
<span className='sr-only'>Öppna meny</span>
<MoreHorizontal className='h-4 w-4' />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuSeparator></DropdownMenuSeparator>
<DropdownMenuItem onClick={async () => {}}>
<Check className='mr-2 h-4 w-4'></Check>
<span>Sätt som omslag</span>
</DropdownMenuItem>
<DropdownMenuItem>
<LinkIcon className='mr-2 h-4 w-4'></LinkIcon>
<span>Kopiera länk</span>
</DropdownMenuItem>
<DropdownMenuItem className='text-destructive'>
<Trash2 className='mr-2 h-4 w-4'></Trash2>
<span>Ta bort</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

export function createColumns() {
return [
cb.display({
Expand Down Expand Up @@ -116,7 +79,12 @@ export function createColumns() {
}),
cb.display({
id: 'actions',
cell: (info) => <RowActions></RowActions>,
cell: (info) => (
<RowActions
id={info.row.original.id}
album_id={info.row.original.album_id}
></RowActions>
),
enableSorting: false,
enableHiding: false,
}),
Expand Down
14 changes: 4 additions & 10 deletions app/routes/_main.admin.$id/publish-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ import {
import { Button } from '~/components/ui/button';

interface Props {
formId?: string;
dirty: boolean;
form?: string;
album: {
id: number;
published: boolean;
};
}
export function PublishButton({ formId, album, dirty }: Props) {
export function PublishButton({ form, album }: Props) {
return (
<AlertDialog>
<AlertDialogTrigger asChild>
Expand All @@ -35,18 +34,13 @@ export function PublishButton({ formId, album, dirty }: Props) {
</AlertDialogTitle>
<AlertDialogDescription>
Ändringar du gjort sparas inte automatisk innan publicering. Va
säker på att du spara innan.{' '}
{dirty && (
<span className='font-semibold text-destructive'>
Du har osparade ändringar!
</span>
)}
säker på att du spara innan.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Avbryt</AlertDialogCancel>
<AlertDialogAction
form={formId}
form={form}
type='submit'
name='intent'
value='publish'
Expand Down
Loading

0 comments on commit 6935e1e

Please sign in to comment.