Skip to content

Commit

Permalink
Merge pull request #824 from Shelf-nu/replace-magic-link-with-otp
Browse files Browse the repository at this point in the history
Replace magic link with otp
  • Loading branch information
DonKoko authored Mar 6, 2024
2 parents b2d25ad + 46fef7e commit eea90a7
Show file tree
Hide file tree
Showing 88 changed files with 2,047 additions and 560 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Shelf.nu

<a href="https://www.shelf.nu/" target="_blank">
<img width="100%" src="https://cdn.discordapp.com/attachments/1143882281069592658/1143949082017153044/Frame_102.jpg" />
<img width="100%" src="./public/static/images/readme-cover.jpg" />
</a>
<h4 align="center">
✨ Open Source Asset Management Infrastructure for everyone. ✨
Expand Down Expand Up @@ -48,4 +48,4 @@ With Shelf, you can take a picture of any item you own and store it in your own

### Looking for contributing in Shelf?

- check out our [contributing guidelines](https://github.com/hrutik7/shelf.nu/blob/docs_improvement/docs/get-started.md)
- check out our [contributing guidelines](./CONTRIBUTING.md)
35 changes: 24 additions & 11 deletions app/components/assets/actions-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { useLoaderData, useSearchParams } from "@remix-run/react";
import { useHydrated } from "remix-utils/use-hydrated";
import {
Expand Down Expand Up @@ -27,16 +27,37 @@ const ConditionalActionsDropdown = () => {
const refIsQrScan = searchParams.get("ref") === "qr";
const defaultOpen = window.innerWidth <= 640 && refIsQrScan;
const [open, setOpen] = useState(defaultOpen);
const [defaultApplied, setDefaultApplied] = useState(false);
const assetIsCheckedOut = asset.status === "CHECKED_OUT";

useEffect(() => {
if (defaultOpen && !defaultApplied) {
setOpen(true);
setDefaultApplied(true);
}
}, [defaultOpen, defaultApplied]);

return (
<>
{open && (
<div
className={tw(
"fixed right-0 top-0 z-10 h-screen w-screen cursor-pointer bg-gray-700/50 transition duration-300 ease-in-out md:hidden"
)}
/>
)}
<DropdownMenu
modal={false}
onOpenChange={(open) => setOpen(open)}
onOpenChange={(open) => {
if (defaultApplied && window.innerWidth <= 640) setOpen(open);
}}
open={open}
defaultOpen={defaultOpen}
>
<DropdownMenuTrigger className="asset-actions hidden sm:flex">
<DropdownMenuTrigger
className="asset-actions hidden sm:flex"
onClick={() => setOpen(!open)}
>
<Button variant="secondary" data-test-id="assetActionsButton">
<span className="flex items-center gap-2">
Actions <ChevronRight className="chev" />
Expand Down Expand Up @@ -185,14 +206,6 @@ const ConditionalActionsDropdown = () => {
</div>
</DropdownMenuContent>
</DropdownMenu>

{/* overlay on mobile */}
<div
className={tw(
"size-screen fixed right-0 top-0 z-50 cursor-pointer bg-[#344054]/50 transition duration-300 ease-in-out md:hidden",
open ? "visible" : "invisible opacity-0"
)}
></div>
</>
);
};
Expand Down
138 changes: 96 additions & 42 deletions app/components/assets/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,18 @@ import { z } from "zod";
import { updateDynamicTitleAtom } from "~/atoms/dynamic-title-atom";
import { fileErrorAtom, validateFileAtom } from "~/atoms/file";
import type { loader } from "~/routes/_layout+/assets.$assetId_.edit";
import { isFormProcessing } from "~/utils";
import { isFormProcessing, tw } from "~/utils";

import type { CustomFieldZodSchema } from "~/utils/custom-fields";
import { mergedSchema } from "~/utils/custom-fields";
import { zodFieldIsRequired } from "~/utils/zod";
import AssetCustomFields from "./custom-fields-inputs";

import { CategorySelect } from "../category/category-select";
import DynamicSelect from "../dynamic-select/dynamic-select";
import FormRow from "../forms/form-row";
import Input from "../forms/input";
import { LocationSelect } from "../location/location-select";
import { Button } from "../shared";
import { Card } from "../shared/card";
import { Image } from "../shared/image";
import { Spinner } from "../shared/spinner";
import { TagsAutocomplete } from "../tag/tags-autocomplete";

Expand Down Expand Up @@ -64,6 +63,7 @@ interface Props {
export const AssetForm = ({
title,
category,
location,
description,
valuation,
qrId,
Expand Down Expand Up @@ -113,13 +113,14 @@ export const AssetForm = ({
className="flex w-full flex-col gap-2"
encType="multipart/form-data"
>
{qrId ? (
<input type="hidden" name={zo.fields.qrId()} value={qrId} />
) : null}
<div className=" border-b pb-5">
<h2 className="mb-1 text-[18px] font-semibold">Basic fields</h2>
<p>Basic information about your asset.</p>
</div>
{qrId ? (
<input type="hidden" name={zo.fields.qrId()} value={qrId} />
) : null}

<FormRow
rowLabel={"Name"}
className="border-b-0 pb-[10px]"
Expand Down Expand Up @@ -164,8 +165,37 @@ export const AssetForm = ({
</div>
</FormRow>

<div>
<FormRow
rowLabel={"Description"}
subHeading={
<p>
This is the initial object description. It will be shown on the
asset’s overview page. You can always change it. Maximum 1000
characters.
</p>
}
className="border-b-0"
required={zodFieldIsRequired(FormSchema.shape.description)}
>
<Input
inputType="textarea"
maxLength={1000}
label={zo.fields.description()}
name={zo.fields.description()}
defaultValue={description || ""}
hideLabel
placeholder="Add a description for your asset."
disabled={disabled}
data-test-id="assetDescription"
className="w-full"
required={zodFieldIsRequired(FormSchema.shape.description)}
/>
</FormRow>
</div>

<FormRow
rowLabel={"Category"}
rowLabel="Category"
subHeading={
<p>
Make it unique. Each asset can have 1 category. It will show on
Expand All @@ -175,11 +205,28 @@ export const AssetForm = ({
className="border-b-0 pb-[10px]"
required={zodFieldIsRequired(FormSchema.shape.category)}
>
<CategorySelect defaultValue={category || "uncategorized"} />
<DynamicSelect
disabled={disabled}
defaultValue={category ?? undefined}
model={{ name: "category", key: "name" }}
label="Categories"
initialDataKey="categories"
countKey="totalCategories"
extraContent={
<Button
to="/categories/new"
variant="link"
icon="plus"
className="w-full justify-start pt-4"
>
Create new category
</Button>
}
/>
</FormRow>

<FormRow
rowLabel={"Tags"}
rowLabel="Tags"
subHeading={
<p>
Tags can help you organise your database. They can be combined.{" "}
Expand All @@ -191,11 +238,11 @@ export const AssetForm = ({
className="border-b-0 py-[10px]"
required={zodFieldIsRequired(FormSchema.shape.tags)}
>
<TagsAutocomplete existingTags={tags || []} />
<TagsAutocomplete existingTags={tags ?? []} />
</FormRow>

<FormRow
rowLabel={"Location"}
rowLabel="Location"
subHeading={
<p>
A location is a place where an item is supposed to be located.
Expand All @@ -208,7 +255,43 @@ export const AssetForm = ({
className="border-b-0 py-[10px]"
required={zodFieldIsRequired(FormSchema.shape.newLocationId)}
>
<LocationSelect />
<input
type="hidden"
name="currentLocationId"
value={location || ""}
/>
<DynamicSelect
disabled={disabled}
fieldName="newLocationId"
defaultValue={location || undefined}
model={{ name: "location", key: "name" }}
label="Locations"
initialDataKey="locations"
countKey="totalLocations"
extraContent={
<Button
to="/locations/new"
variant="link"
icon="plus"
className="w-full justify-start pt-4"
>
Create new location
</Button>
}
renderItem={({ name, metadata }) => (
<div className="flex items-center gap-2">
<Image
imageId={metadata.imageId}
alt="img"
className={tw(
"size-6 rounded-[2px] object-cover",
metadata.description ? "rounded-b-none border-b-0" : ""
)}
/>
<div>{name}</div>
</div>
)}
/>
</FormRow>

<FormRow
Expand Down Expand Up @@ -243,35 +326,6 @@ export const AssetForm = ({
</div>
</FormRow>

<div>
<FormRow
rowLabel={"Description"}
subHeading={
<p>
This is the initial object description. It will be shown on the
asset’s overview page. You can always change it. Maximum 1000
characters.
</p>
}
className="border-b-0"
required={zodFieldIsRequired(FormSchema.shape.description)}
>
<Input
inputType="textarea"
maxLength={1000}
label={zo.fields.description()}
name={zo.fields.description()}
defaultValue={description || ""}
hideLabel
placeholder="Add a description for your asset."
disabled={disabled}
data-test-id="assetDescription"
className="w-full"
required={zodFieldIsRequired(FormSchema.shape.description)}
/>
</FormRow>
</div>

<AssetCustomFields zo={zo} schema={FormSchema} />

<FormRow className="border-y-0 pb-0 pt-5" rowLabel="">
Expand Down
2 changes: 1 addition & 1 deletion app/components/assets/notes/note.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Note as NoteType } from "@prisma/client";
import type { Note as NoteType } from "@prisma/client";
import { MarkdownViewer } from "~/components/markdown";
import { Switch } from "~/components/shared/switch";
import { Tag } from "~/components/shared/tag";
Expand Down
3 changes: 2 additions & 1 deletion app/components/booking/availability-label.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BookingStatus, type Booking } from "@prisma/client";
import type { Booking } from "@prisma/client";
import { BookingStatus } from "@prisma/client";
import { Link, useLoaderData } from "@remix-run/react";
import type { AssetWithBooking } from "~/routes/_layout+/bookings.$bookingId.add-assets";
import { SERVER_URL, tw } from "~/utils";
Expand Down
2 changes: 2 additions & 0 deletions app/components/booking/booking-assets-column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export function BookingAssetsColumn() {
title: "Manage Assets",
message: isCompleted
? "Booking is completed. You cannot change the assets anymore"
: isSelfService
? "You are unable to manage assets at this point becasue the booking is already reserved. Cancel this booking and create another one if you need to make changes."
: "You need to select a start and end date and save your booking before you can add assets to your booking",
}}
buttonProps={{
Expand Down
6 changes: 3 additions & 3 deletions app/components/booking/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { BookingWithCustodians } from "~/routes/_layout+/bookings";
import { isFormProcessing, tw } from "~/utils";
import type { BookingFormData } from ".";
import { ActionsDropdown } from "./actions-dropdown";
import CustodianSelect from "../custody/custodian-select";
import CustodianUserSelect from "../custody/custodian-user-select";
import FormRow from "../forms/form-row";
import Input from "../forms/input";
import { Button } from "../shared";
Expand Down Expand Up @@ -276,8 +276,8 @@ export function BookingForm({
<label className="mb-2.5 block font-medium text-gray-700">
<span className="required-input-label">Custodian</span>
</label>
<CustodianSelect
defaultTeamMemberId={custodianUserId}
<CustodianUserSelect
defaultUserId={custodianUserId}
disabled={inputFieldIsDisabled}
className={
isSelfService
Expand Down
21 changes: 14 additions & 7 deletions app/components/booking/status-filter.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BookingStatus } from "@prisma/client";
import { useSearchParams } from "@remix-run/react";
import { useNavigation, useSearchParams } from "@remix-run/react";
import { isFormProcessing } from "~/utils";
import {
Select,
SelectContent,
Expand All @@ -8,7 +8,13 @@ import {
SelectValue,
} from "../forms";

export function StatusFilter() {
export function StatusFilter({
statusItems,
}: {
statusItems: Record<string, string>;
}) {
const navigation = useNavigation();
const disabled = isFormProcessing(navigation.state);
const [searchParams, setSearchParams] = useSearchParams();
const status = searchParams.get("status");

Expand All @@ -30,24 +36,25 @@ export function StatusFilter() {
name={`status`}
defaultValue={status ? status : "ALL"}
onValueChange={handleValueChange}
disabled={disabled}
>
<SelectTrigger className="mt-2 px-3.5 py-3 md:mt-0 md:max-w-fit">
<SelectValue placeholder={`Filter by asset status`} />
<SelectTrigger className="mt-2 px-3.5 py-2 text-left text-base text-gray-500 md:mt-0 md:max-w-fit">
<SelectValue placeholder={`Filter by status`} />
</SelectTrigger>
<SelectContent
position="popper"
className="w-full min-w-[300px] p-0"
align="start"
>
<div className=" max-h-[320px] overflow-auto">
{["ALL", ...Object.values(BookingStatus)].map((value) => (
{["ALL", ...Object.values(statusItems)].map((value) => (
<SelectItem
value={value}
key={value}
className="rounded-none border-b border-gray-200 px-6 py-4 pr-[5px]"
>
<span className="mr-4 block text-[14px] lowercase text-gray-700 first-letter:uppercase">
{value}
{value.split("_").join(" ")}
</span>
</SelectItem>
))}
Expand Down
Loading

0 comments on commit eea90a7

Please sign in to comment.