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

CMS Rooms v2 #167

Merged
merged 1 commit into from
Sep 5, 2024
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
55 changes: 47 additions & 8 deletions client/src/app/_components/_base/CardButton.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,66 @@
import { clmx } from '@/util/classConcat';
import { IconType } from '@/util/iconType';
import { Transition } from '@headlessui/react';
import { IconLoader2 } from '@tabler/icons-react';

import { clmx, clx } from '@/util/classConcat';
import { IconType, IconTypeProps } from '@/util/iconType';

export default function CardButton({
icon: Icon,
stroke,

loading,
children,
className,
...props
}: { icon: IconType; stroke?: number } & JSX.IntrinsicElements['button']) {
}: {
icon: IconType;
stroke?: number;
loading?: boolean;
} & JSX.IntrinsicElements['button']) {
const iconProps: IconTypeProps = {
className: 'size-10',
stroke: stroke ?? 1.5,
};

return (
<>
<button
className={clmx(
'group flex flex-col items-center justify-center gap-2 rounded-md border border-transparent bg-emerald-500/10 p-5 text-emerald-700 transition hover:border-emerald-500 focus:outline-emerald-600',
'group relative flex flex-col items-center justify-center rounded-md border border-transparent bg-emerald-500/10 p-5 text-emerald-700 transition focus:outline-emerald-600 data-[l]:cursor-default hover:data-[nl]:border-emerald-500',
className,
)}
disabled={loading}
data-l={loading || null}
data-nl={!loading || null}
{...props}
>
<div className="-m-2 rounded-full p-2 transition ease-in-out group-hover:scale-110 group-hover:bg-white/30">
<Icon className="size-10" stroke={stroke ?? 1.5} />
{/* button contents */}
<div
className={clx(
'flex translate-y-0 flex-col items-center gap-2 transition',
/* transition */ 'group-data-[l]:-translate-y-8 group-data-[l]:opacity-0',
)}
>
<div className="-m-2 rounded-full p-2 transition ease-in-out group-hover:scale-110 group-hover:bg-white/30">
<Icon {...iconProps} />
</div>
<div className="transition group-hover:translate-y-1.5">
{children}
</div>
</div>
<div className="transition group-hover:translate-y-1.5">{children}</div>

{/* loader */}
<Transition show={loading}>
<div
className={clx(
'absolute inset-0 flex flex-col items-center justify-center',
/* transition */ 'translate-y-0 transition data-[closed]:translate-y-8 data-[closed]:opacity-0',
)}
>
<div className="animate-spin">
<IconLoader2 {...iconProps} />
</div>
</div>
</Transition>
</button>
</>
);
Expand Down
4 changes: 2 additions & 2 deletions client/src/app/_components/_base/modals.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
'use client';

import { DefaultMantineColor } from '@mantine/core';
import { modals } from '@mantine/modals';
import { ExtendedMantineColors } from '@/theme/mantine';

import { clx } from '@/util/classConcat';

export type ModalProps = {
/** button color */
color?: DefaultMantineColor;
color?: ExtendedMantineColors;
/** modal title */
title?: React.ReactNode;
/** only customize the inner prose */
Expand Down
64 changes: 59 additions & 5 deletions client/src/app/cms/rooms/_components/CabinsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import { ResultOf } from '@graphql-typed-document-node/core';

import { IconDoor, IconHomePlus } from '@tabler/icons-react';

import { graphql } from '@/query/graphql';
import { graphAuth, graphql } from '@/query/graphql';
import { useGraphQuery } from '@/query/query';
import { Inside } from '@/util/inferTypes';
import { UseQueryResult } from '@tanstack/react-query';

import RoomCabinPanel from './RoomCabinPanel';
import CardButton from '@/app/_components/_base/CardButton';
import { useCallback, useTransition } from 'react';
import { err } from '../_functions/errors';
import { ROOT_CABIN_ID } from '@@/db/schema/Room/CABIN_DATA';

const ROOM_FRAGMENT = graphql(`
fragment RoomData on Room @_unmask {
Expand Down Expand Up @@ -50,9 +53,9 @@ export type CMSRoom = Inside<CMSCabin['rooms']> & {};

export default function CabinsList() {
const query = useGraphQuery(CABINS_QUERY);
function refetch() {
query.refetch();
}
const refetch = useCallback(async () => {
await query.refetch();
}, [query]);

const cabins = query.data?.cabins;
const rootRooms = query.data?.roomsNoCabin;
Expand All @@ -62,6 +65,53 @@ export default function CabinsList() {
refetch,
};

const [isLoadingNC, loadingNC] = useTransition();
const newCabin = useCallback(() => {
loadingNC(async () => {
const { data, errors } = await graphAuth(
graphql(`
mutation CabinCreate($name: String!, $aliases: [String!]!) {
cabinCreate(name: $name, aliases: $aliases) {
id
}
}
`),
{ name: '', aliases: [] },
);
if (errors || !data?.cabinCreate) return err(errors?.[0].code ?? errors);
await refetch();
});
}, [refetch]);

const [isLoadingNR, loadingNR] = useTransition();
const newRootRoom = useCallback(() => {
loadingNR(async () => {
const { data, errors } = await graphAuth(
graphql(`
mutation RoomCreate(
$name: String!
$aliases: [String!]!
$cabinId: String!
$beds: Int!
) {
roomCreate(
name: $name
aliases: $aliases
cabinId: $cabinId
beds: $beds
) {
id
}
}
`),
{ name: '', aliases: [], beds: 0, cabinId: ROOT_CABIN_ID },
);
if (errors || !data?.roomCreate) return err(errors?.[0].code ?? errors);

await refetch();
});
}, [refetch]);

return (
<>
<div className="flex-1 columns-1 gap-4 space-y-4 md:columns-2 md:py-4">
Expand All @@ -79,12 +129,16 @@ export default function CabinsList() {
<CardButton
className="flex-1 rounded-sm rounded-t-lg"
icon={IconHomePlus}
onClick={newCabin}
loading={isLoadingNC}
>
Add a cabin
</CardButton>
<CardButton
className="flex-1 rounded-sm rounded-b-lg"
icon={IconDoor}
onClick={newRootRoom}
loading={isLoadingNR}
>
Add a root room
</CardButton>
Expand All @@ -111,5 +165,5 @@ export default function CabinsList() {

export type CabinRoomProps = {
query: UseQueryResult<ResultOf<typeof CABINS_QUERY>>;
refetch: () => void;
refetch: () => Promise<void>;
};
49 changes: 49 additions & 0 deletions client/src/app/cms/rooms/_components/NameInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { CloseButton, TextInput, TextInputProps } from '@mantine/core';
import { IconTrash } from '@tabler/icons-react';
import { ForwardedRef, forwardRef } from 'react';

const NameInput = forwardRef<HTMLInputElement, NameInputProps>((p, r) => (
<NameInputComponent {...p} ref={r} />
));
NameInput.displayName = 'NameInput';
export default NameInput;

type NameInputProps = {
onUpdate?: (s: string) => void;
onDelete?: () => void;
value: string;
ref: ForwardedRef<HTMLInputElement>;
} & TextInputProps;

// COMPONENT
function NameInputComponent({
onUpdate,
onDelete,
onChange,
rightSection: _,
...props
}: NameInputProps) {
const { value } = props;
return (
<>
<TextInput
{...props}
onChange={(e) => {
onUpdate?.(e.currentTarget.value);
onChange?.(e);
}}
rightSection={
<CloseButton
icon={
value.length ? undefined : <IconTrash size={20} stroke={1.5} />
}
onClick={() => {
if (value.length) onUpdate?.('');
else onDelete?.();
}}
/>
}
/>
</>
);
}
18 changes: 18 additions & 0 deletions client/src/app/cms/rooms/_components/OptionSwitch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Switch, SwitchProps } from '@mantine/core';

export default function OptionSwitch({ ...props }: SwitchProps) {
return (
<>
<Switch
{...props}
classNames={{
input: 'peer',
track:
'[.peer:not(:checked)~&]:border-slate-300 [.peer:not(:checked)~&]:bg-slate-300',
thumb: 'border-0',
body: 'items-center',
}}
/>
</>
);
}
38 changes: 38 additions & 0 deletions client/src/app/cms/rooms/_components/PlusButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { forwardRef } from 'react';

import { Button, ButtonProps } from '@mantine/core';
import { IconLoader2, IconPlus } from '@tabler/icons-react';

type Props = Omit<JSX.IntrinsicElements['button'], 'ref'> & ButtonProps;

const PlusButton = forwardRef<HTMLButtonElement, Props>(
({ children, loading, ...props }, r) => {
return (
<>
<Button
ref={r}
size="compact"
justify="center"
variant="light"
leftSection={
loading ? (
<>
<div className="flex flex-col justify-center">
<IconLoader2 className="size-4 animate-spin" />
</div>
</>
) : (
<IconPlus className="size-4" />
)
}
{...props}
>
{children}
</Button>
</>
);
},
);
PlusButton.displayName = 'PlusButton';

export default PlusButton;
Loading