Skip to content

Commit

Permalink
Merge branch 'Shelf-nu:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
anatolinicolae authored Feb 12, 2024
2 parents 256518b + 4a2a2e7 commit e17a04e
Show file tree
Hide file tree
Showing 168 changed files with 10,391 additions and 2,739 deletions.
14 changes: 10 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Most of the connection information can be found within the Supabase dashboard. Navigate to your project > Project Settings > Database.
# There you will be able to find the values you need to use below
# You can either copy the connection string and insert your password or use the connection parameters to build the string yourself
DATABASE_URL="postgres://{USER}:{PASSWORD}@{HOST}:6543/{DB_NAME}?pgbouncer=true&connection_limit=1"
DATABASE_URL="postgres://{USER}:{PASSWORD}@{HOST}:6543/{DB_NAME}?pgbouncer=true"

# Direct URL is used by prisma to run migrations. Depending on how you run your migrations, you could skip this in your procution environment
# Direct URL is used by prisma to run migrations and pg-boss connection.
# More info here: https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/databases-connections#external-connection-poolers
# and here: https://www.prisma.io/docs/orm/reference/prisma-schema-reference#fields
DIRECT_URL="postgres://{USER}:{PASSWORD}@{HOST}:5432/{DB_NAME}"
Expand All @@ -16,9 +16,10 @@ SERVER_URL="http://localhost:3000"


# Set this to false to disable requirement of subscription for premium features. This will make premium features available for all users
ENABLE_PREMIUM_FEATURES="true"
ENABLE_PREMIUM_FEATURES="true"

# The Stripe keys are needed only if you want to enable premium features
# If you want to completely deactivate the premium features, you can adjust the ENV variable above or adjust it in the shelf.config.ts file
STRIPE_SECRET_KEY="stripe-secret-key"
STRIPE_PUBLIC_KEY="stripe-public-key"
STRIPE_WEBHOOK_ENDPOINT_SECRET="stripe-endpoint-secret"
Expand All @@ -32,4 +33,9 @@ MAPTILER_TOKEN="maptiler-token"
MICROSOFT_CLARITY_ID="microsoft-clarity-id"

INVITE_TOKEN_SECRET="secret-test-invite"
GEOCODE_API_KEY="geocode-api-key"
GEOCODE_API_KEY="geocode-api-key"

# Used for Sentry error logging
SENTRY_DSN="sentry-dsn"
SENTRY_ORG="sentry-org"
SENTRY_PROJECT="sentry-project"
10 changes: 10 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ jobs:
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new

# - name: Create Sentry release
# uses: getsentry/action-release@v1
# env:
# SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
# SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
# SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
# with:
# environment: production
# sourcemaps: "./build"

# This ugly bit is necessary if you don't want your cache to grow forever
# till it hits GitHub's limit of 5GB.
# Temp fix
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ node_modules
/build
/public/build
.env
.env.local
.env.staging
.env.production
.env.staging
.cache
Expand Down Expand Up @@ -31,3 +33,6 @@ desktop-app/portable
.npmrc

.vscode

# Sentry Config File
.sentryclirc
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run db:generate-type && npm run lint:fix && npm run format && npm run typecheck
npm run precommit
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# base node image
FROM node:18-bullseye-slim as base
FROM node:20-bookworm-slim as base

# set for base and all layer that inherit from it
ENV NODE_ENV production
Expand Down
3 changes: 3 additions & 0 deletions app/atoms/switching-workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { atom } from "jotai";

export const switchingWorkspaceAtom = atom(true);
52 changes: 31 additions & 21 deletions app/components/assets/actions-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useState } from "react";
import type { Asset } from "@prisma/client";
import { useSearchParams } from "@remix-run/react";
import { useLoaderData, useSearchParams } from "@remix-run/react";
import { useHydrated } from "remix-utils/use-hydrated";
import {
ChevronRight,
Expand All @@ -16,24 +15,19 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "~/components/shared/dropdown";
import type { loader } from "~/routes/_layout+/assets.$assetId";
import { tw } from "~/utils/tw-classes";
import { DeleteAsset } from "./delete-asset";
import { Button } from "../shared";

interface Props {
asset: {
title: Asset["title"];
mainImage: Asset["mainImage"];
status: Asset["status"];
};
}

const ConditionalActionsDropdown = ({ asset }: Props) => {
const assetIsAvailable = asset.status === "AVAILABLE";
const ConditionalActionsDropdown = () => {
const { asset } = useLoaderData<typeof loader>();
const assetCanBeReleased = asset.custody;
let [searchParams] = useSearchParams();
const refIsQrScan = searchParams.get("ref") === "qr";
const defaultOpen = window.innerWidth <= 640 && refIsQrScan;
const [open, setOpen] = useState(defaultOpen);
const assetIsCheckedOut = asset.status === "CHECKED_OUT";

return (
<>
Expand All @@ -42,7 +36,7 @@ const ConditionalActionsDropdown = ({ asset }: Props) => {
onOpenChange={(open) => setOpen(open)}
open={open}
>
<DropdownMenuTrigger className="asset-actions hidden sm:block">
<DropdownMenuTrigger className="asset-actions hidden sm:flex">
<Button variant="secondary" data-test-id="assetActionsButton">
<span className="flex items-center gap-2">
Actions <ChevronRight className="chev" />
Expand Down Expand Up @@ -82,8 +76,11 @@ const ConditionalActionsDropdown = ({ asset }: Props) => {
className="order actions-dropdown static w-screen rounded-b-none rounded-t-[4px] bg-white p-0 text-right md:static md:w-[180px] md:rounded-t-[4px]"
>
<div className="order fixed bottom-0 left-0 w-screen rounded-b-none rounded-t-[4px] bg-white p-0 text-right md:static md:w-[180px] md:rounded-t-[4px]">
<DropdownMenuItem className="border-b p-4 md:mb-0 md:p-0">
{!assetIsAvailable ? (
<DropdownMenuItem
className="border-b p-4 md:mb-0 md:p-0"
disabled={assetIsCheckedOut && !assetCanBeReleased}
>
{assetCanBeReleased ? (
<Button
to="release-custody"
role="link"
Expand Down Expand Up @@ -112,12 +109,17 @@ const ConditionalActionsDropdown = ({ asset }: Props) => {
</Button>
)}
</DropdownMenuItem>
<DropdownMenuItem className="mb-2.5 border-b p-4 md:mb-0 md:p-0">
<DropdownMenuItem
className={tw("mb-2.5 border-b p-4 md:mb-0 md:p-0")}
disabled={assetIsCheckedOut}
>
<Button
to="update-location"
role="link"
variant="link"
className="justify-start px-4 py-3 text-gray-700 hover:text-gray-700"
className={tw(
"justify-start px-4 py-3 text-gray-700 hover:text-gray-700"
)}
width="full"
onClick={() => setOpen(false)}
>
Expand Down Expand Up @@ -160,6 +162,7 @@ const ConditionalActionsDropdown = ({ asset }: Props) => {
onSelect={(e) => {
e.preventDefault();
}}
disabled={assetIsCheckedOut}
>
<DeleteAsset asset={asset} />
</DropdownMenuItem>
Expand All @@ -174,6 +177,11 @@ const ConditionalActionsDropdown = ({ asset }: Props) => {
Close
</Button>
</DropdownMenuItem>
{assetIsCheckedOut ? (
<div className=" border-t p-2 text-left text-xs">
Some actions are disabled due to the asset being checked out.
</div>
) : null}
</div>
</DropdownMenuContent>
</DropdownMenu>
Expand All @@ -189,19 +197,21 @@ const ConditionalActionsDropdown = ({ asset }: Props) => {
);
};

const ActionsDopdown = ({ asset }: Props) => {
const ActionsDopdown = () => {
const isHydrated = useHydrated();

if (!isHydrated)
return (
<Button variant="secondary" to="#" data-test-id="assetActionsButton">
<span className="flex items-center gap-2">
Actions <ChevronRight className="chev" />
Actions <ChevronRight className="chev rotate-90" />
</span>
</Button>
);

return (
<div className="actions-dropdown">
<ConditionalActionsDropdown asset={asset} />
<div className="actions-dropdown flex">
<ConditionalActionsDropdown />
</div>
);
};
Expand Down
92 changes: 92 additions & 0 deletions app/components/assets/asset-status-badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { SVGProps } from "react";
import { AssetStatus } from "@prisma/client";
import { Badge } from "../shared";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../shared/tooltip";

export const userFriendlyAssetStatus = (status: AssetStatus) => {
switch (status) {
case AssetStatus.IN_CUSTODY:
return "In custody";
case AssetStatus.CHECKED_OUT:
return "Checked out";
default:
return "Available";
}
};

export const assetStatusColorMap = (status: AssetStatus) => {
switch (status) {
case AssetStatus.IN_CUSTODY:
return "#2E90FA";
case AssetStatus.CHECKED_OUT:
return "#5925DC";
default:
return "#12B76A";
}
};

export function AssetStatusBadge({
status,
availableToBook = true,
}: {
status: AssetStatus;
availableToBook: boolean;
}) {
// If the asset is not available to book, it is unavailable
// We handle this on front-end as syncing status with the flag is very complex on backend and error prone so this is the lesser evil
return (
<div className="flex items-center gap-[6px]">
<Badge color={assetStatusColorMap(status)}>
{userFriendlyAssetStatus(status)}
</Badge>
{!availableToBook && <UnavailableBadge />}
</div>
);
}

const UnavailableBadge = (props: SVGProps<SVGSVGElement>) => (
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger asChild>
<svg
xmlns="http://www.w3.org/2000/svg"
width={22}
height={22}
fill="none"
{...props}
>
<g
style={{
mixBlendMode: "multiply",
}}
>
<rect width={22} height={22} fill="#F2F4F7" rx={11} />
<rect width={22} height={22} stroke="#EAECF0" rx={11} />
<g clipPath="url(#a)">
<path
stroke="#667085"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M15.5 10.75V9.4c0-.84 0-1.26-.164-1.581a1.5 1.5 0 0 0-.655-.656C14.361 7 13.941 7 13.1 7H8.9c-.84 0-1.26 0-1.581.163a1.5 1.5 0 0 0-.656.656c-.163.32-.163.74-.163 1.581v4.2c0 .84 0 1.26.163 1.581a1.5 1.5 0 0 0 .656.655c.32.164.74.164 1.581.164h2.35m4.25-6h-9M13 6v2M9 6v2m6.5 7.5L13 13m.1 2.5 2.4-2.5"
/>
</g>
</g>
<defs>
<clipPath id="a">
<path fill="#fff" d="M5 5h12v12H5z" />
</clipPath>
</defs>
</svg>
</TooltipTrigger>
<TooltipContent side="bottom">
<p>This asset is marked as unavailable for bookings</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
2 changes: 1 addition & 1 deletion app/components/assets/delete-asset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const DeleteAsset = ({
{asset.mainImage && (
<input type="hidden" value={asset.mainImage} name="mainImage" />
)}

<input type="hidden" value="delete" name="intent" />
<Button
className="border-error-600 bg-error-600 hover:border-error-800 hover:bg-error-800"
type="submit"
Expand Down
10 changes: 6 additions & 4 deletions app/components/assets/export-button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useLoaderData } from "@remix-run/react";
import type { loader } from "~/routes/_layout+/assets._index";
import { PremiumFeatureButton } from "../subscription/premium-feature-button";
import { ControlledActionButton } from "../shared/controlled-action-button";

export const ExportButton = ({
canExportAssets,
Expand All @@ -9,14 +9,16 @@ export const ExportButton = ({
}) => {
const { totalItems } = useLoaderData<typeof loader>();
return (
<PremiumFeatureButton
<ControlledActionButton
canUseFeature={canExportAssets}
buttonContent={{
title: "Export",
title: "Download CSV",
message: "Exporting is not available on the free tier of shelf.",
}}
buttonProps={{
to: `export/assets-${new Date().toISOString().slice(0, 10)}.csv`,
to: `/assets/export/assets-${new Date()
.toISOString()
.slice(0, 10)}.csv`,
variant: "secondary",
role: "link",
download: true,
Expand Down
4 changes: 2 additions & 2 deletions app/components/assets/import-button.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { PremiumFeatureButton } from "../subscription/premium-feature-button";
import { ControlledActionButton } from "../shared/controlled-action-button";

export const ImportButton = ({
canImportAssets,
}: {
canImportAssets: boolean;
}) => (
<PremiumFeatureButton
<ControlledActionButton
canUseFeature={canImportAssets}
buttonContent={{
title: "Import",
Expand Down
Loading

0 comments on commit e17a04e

Please sign in to comment.