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
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React, { useCallback, useEffect, useState } from "react";
import {
FileInputComponent,
TableComponent,
UploadIconComponent,
} from "@openhotel/web-components";
import { Collection, CollectionManifest } from "shared/types";
import { useCollections } from "shared/hooks";
import dayjs from "dayjs";
import styles from "./collections.module.scss";

type Props = {} & React.HTMLProps<HTMLDivElement>;

export const CollectionsComponent: React.FC<Props> = () => {
const { get, upload } = useCollections();

const [collections, setCollections] = useState<Collection[]>([]);
const [isAvailable, setIsAvailable] = useState<boolean>(true);

const [isUploading, setIsUploading] = useState<boolean>(false);
const [published, setPublished] = useState<CollectionManifest>(null);
const [errors, setErrors] = useState<string[]>([]);

const $reload = useCallback(
() =>
get()
.then((collections) => {
setCollections(collections);
setIsAvailable(true);
})
.catch(() => setIsAvailable(false)),
[get],
);

useEffect(() => {
$reload();
}, []);

const onUploadCollection = useCallback(
async ([file]: File[]) => {
if (!file?.size || isUploading) return;

setIsUploading(true);
setPublished(null);
setErrors([]);

try {
const manifest = await upload(file);
setPublished(manifest);
await $reload();
} catch (e) {
setErrors(
e?.data?.errors?.length
? e.data.errors
: ["The collection could not be uploaded, try again later"],
);
}

setIsUploading(false);
},
[upload, $reload, isUploading],
);

return (
<div className={styles.collections}>
<h2>Collections</h2>
{!isAvailable ? (
<label>Collections are not available right now</label>
) : (
<div className={styles.upload}>
<FileInputComponent
accept=".collection"
disabled={isUploading}
onChange={onUploadCollection}
>
<UploadIconComponent className={styles.icon} />
<span>
{isUploading ? (
"Uploading..."
) : (
<>
Upload <b>*.collection</b> file
</>
)}
</span>
</FileInputComponent>
{published ? (
<label>
Published {published.id} version {published.version}
</label>
) : null}
{errors.length ? (
<ul className={styles.errors}>
{errors.map((error) => (
<li key={error}>{error}</li>
))}
</ul>
) : null}
</div>
)}

{isAvailable ? (
<TableComponent
title="Collections"
searchable={true}
pageRows={20}
data={collections.map((collection) => ({
...collection,
name: collection.category?.label ?? "-",
version: collection.latestVersion || "-",
updated: dayjs(collection.updatedAt).format("YYYY/MM/DD HH:mm"),
}))}
columns={[
{
key: "id",
label: "Id",
},
{
key: "name",
label: "Name",
},
{
key: "version",
label: "Version",
},
{
key: "furnitureCount",
label: "Furniture",
},
{
key: "updated",
label: "Updated",
},
]}
/>
) : null}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.collections {
width: 100%;
gap: 1rem;
display: flex;
flex-direction: column;

.upload {
padding: 2rem;
background-color: var(--text-2-bg);
border-radius: 1rem;
gap: 1rem;
display: flex;
flex-direction: column;

.icon {
height: 2rem;
}

.errors {
margin: 0;
padding-left: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./collections.component";
1 change: 1 addition & 0 deletions app/client/src/modules/account/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./actions";
export * from "./connections";
export * from "./my-hotels";
export * from "./collections";
export * from "./otp";
export * from "./navigator";
export * from "./languages";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
HotelIconComponent,
KeysIconComponent,
NavItemComponent,
UploadIconComponent,
} from "@openhotel/web-components";
import { LinkComponent } from "shared/components";
import { useAccount, useUser } from "shared/hooks";
Expand Down Expand Up @@ -43,6 +44,11 @@ export const HomeNavigatorComponent: React.FC = () => {
My Hotels
</NavItemComponent>
</LinkComponent>
<LinkComponent to="/account/collections">
<NavItemComponent icon={<UploadIconComponent />}>
Collections
</NavItemComponent>
</LinkComponent>
<LinkComponent to="/account/connections">
<NavItemComponent icon={<ConnectionsIconComponent />}>
Connections
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { RecoverPasswordComponent } from "modules/recover-password";
import { ProvidersComponent } from "modules/application/components/providers";
import {
AccountComponent,
CollectionsComponent,
ConnectionsComponent,
DeleteComponent,
GithubComponent,
Expand Down Expand Up @@ -104,6 +105,10 @@ const router = createBrowserRouter([
path: "my-hotels",
element: <MyHotelsComponent />,
},
{
path: "collections",
element: <CollectionsComponent />,
},
{
path: "connections",
element: <ConnectionsComponent />,
Expand Down
1 change: 1 addition & 0 deletions app/client/src/shared/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from "./useConnection";
export * from "./useAdmin";
export * from "./useHotel";
export * from "./useMyHotels";
export * from "./useCollections";
export * from "./useCookies";
export * from "./useHotels";
export * from "./useLanguages";
Expand Down
6 changes: 4 additions & 2 deletions app/client/src/shared/hooks/useApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ export const useApi = () => {
rawResponse = false,
preventReload = false,
}: Request) => {
const isFile = body instanceof Blob;

const response = await fetch(`/api/v3${pathname}`, {
method,
headers: new Headers({
"Content-Type": "application/json",
...(isFile ? {} : { "Content-Type": "application/json" }),
fingerprint,
...headers,
}),
body: body ? JSON.stringify(body) : undefined,
body: isFile ? body : body ? JSON.stringify(body) : undefined,
credentials: "include",
cache: cache ? "default" : "no-store",
}).then(async (data) => {
Expand Down
39 changes: 39 additions & 0 deletions app/client/src/shared/hooks/useCollections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useApi } from "./useApi";
import { useCallback } from "react";
import { RequestMethod } from "shared/enums";
import { useAccount } from "./useAccount";
import { Collection, CollectionManifest } from "shared/types";

export const useCollections = () => {
const { fetch } = useApi();
const { getAccountHeaders } = useAccount();

const get = useCallback(async (): Promise<Collection[]> => {
const { data } = await fetch({
method: RequestMethod.GET,
pathname: `/user/@me/collections`,
headers: getAccountHeaders(),
});

return data.collections;
}, [fetch, getAccountHeaders]);

const upload = useCallback(
async (file: File): Promise<CollectionManifest> => {
const { data } = await fetch({
method: RequestMethod.POST,
pathname: `/user/@me/collections`,
headers: getAccountHeaders(),
body: file,
});

return data.manifest;
},
[fetch, getAccountHeaders],
);

return {
get,
upload,
};
};
22 changes: 22 additions & 0 deletions app/client/src/shared/types/collections.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type CollectionCategory = {
label: string;
description?: string;
};

export type Collection = {
id: string;
category: CollectionCategory | null;
latestVersion: number;
furnitureCount: number;
createdAt: number;
updatedAt: number;
};

export type CollectionManifest = {
id: string;
version: number;
category: CollectionCategory;
furniture: {
id: string;
}[];
};
1 change: 1 addition & 0 deletions app/client/src/shared/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from "./connections.types";
export * from "./user.types";
export * from "./tokens.types";
export * from "./hotels.types";
export * from "./collections.types";
export * from "./backup.types";
export * from "./apps.types";
export * from "./login.types";
Loading
Loading