mirror of
https://github.com/hex248/sprint.git
synced 2026-02-07 18:23:03 +00:00
toggle features per organisation
This commit is contained in:
@@ -1,4 +1,9 @@
|
|||||||
import { DEFAULT_STATUS_COLOUR, ISSUE_STATUS_MAX_LENGTH, type SprintRecord } from "@sprint/shared";
|
import {
|
||||||
|
DEFAULT_FEATURES,
|
||||||
|
DEFAULT_STATUS_COLOUR,
|
||||||
|
ISSUE_STATUS_MAX_LENGTH,
|
||||||
|
type SprintRecord,
|
||||||
|
} from "@sprint/shared";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { type ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
import { type ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -44,7 +49,8 @@ import {
|
|||||||
} from "@/lib/query/hooks";
|
} from "@/lib/query/hooks";
|
||||||
import { queryKeys } from "@/lib/query/keys";
|
import { queryKeys } from "@/lib/query/keys";
|
||||||
import { issue } from "@/lib/server";
|
import { issue } from "@/lib/server";
|
||||||
import { capitalise } from "@/lib/utils";
|
import { capitalise, unCamelCase } from "@/lib/utils";
|
||||||
|
import { Switch } from "./ui/switch";
|
||||||
|
|
||||||
function Organisations({ trigger }: { trigger?: ReactNode }) {
|
function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||||
const { user } = useAuthenticatedSession();
|
const { user } = useAuthenticatedSession();
|
||||||
@@ -440,7 +446,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
|||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|
||||||
<DialogContent className="max-w-sm">
|
<DialogContent className="w-md max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Organisations</DialogTitle>
|
<DialogTitle>Organisations</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
@@ -457,6 +463,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
|||||||
<TabsTrigger value="users">Users</TabsTrigger>
|
<TabsTrigger value="users">Users</TabsTrigger>
|
||||||
<TabsTrigger value="projects">Projects</TabsTrigger>
|
<TabsTrigger value="projects">Projects</TabsTrigger>
|
||||||
<TabsTrigger value="issues">Issues</TabsTrigger>
|
<TabsTrigger value="issues">Issues</TabsTrigger>
|
||||||
|
<TabsTrigger value="features">Features</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -917,6 +924,37 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
<TabsContent value="features">
|
||||||
|
<div className="border p-2 min-w-0 overflow-hidden">
|
||||||
|
<h2 className="text-xl font-600 mb-2">Features</h2>
|
||||||
|
<div className="flex flex-col gap-2 w-full">
|
||||||
|
{Object.keys(DEFAULT_FEATURES).map((feature) => (
|
||||||
|
<div key={feature}>
|
||||||
|
{unCamelCase(feature)}:{" "}
|
||||||
|
<Switch
|
||||||
|
checked={Boolean(selectedOrganisation?.Organisation.features[feature])}
|
||||||
|
onCheckedChange={async (checked) => {
|
||||||
|
if (!selectedOrganisation) return;
|
||||||
|
const newFeatures = selectedOrganisation.Organisation.features;
|
||||||
|
newFeatures[feature] = checked;
|
||||||
|
|
||||||
|
await updateOrganisation.mutateAsync({
|
||||||
|
id: selectedOrganisation.Organisation.id,
|
||||||
|
features: newFeatures,
|
||||||
|
});
|
||||||
|
toast.success(
|
||||||
|
`${capitalise(unCamelCase(feature))} ${
|
||||||
|
checked ? "enabled" : "disabled"
|
||||||
|
} for ${selectedOrganisation.Organisation.name}`,
|
||||||
|
);
|
||||||
|
await invalidateOrganisations();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-2 w-full min-w-0">
|
<div className="flex flex-col gap-2 w-full min-w-0">
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export function SprintForm({
|
|||||||
isEdit && existingSprint ? sprints.filter((s) => s.id !== existingSprint.id) : sprints;
|
isEdit && existingSprint ? sprints.filter((s) => s.id !== existingSprint.id) : sprints;
|
||||||
|
|
||||||
const dialogContent = (
|
const dialogContent = (
|
||||||
<DialogContent className={cn("w-md", (error || dateError) && "border-destructive")}>
|
<DialogContent className={cn("w-sm", (error || dateError) && "border-destructive")}>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{isEdit ? "Edit Sprint" : "Create Sprint"}</DialogTitle>
|
<DialogTitle>{isEdit ? "Edit Sprint" : "Create Sprint"}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -65,3 +65,7 @@ export const isLight = (hex: string): boolean => {
|
|||||||
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||||
return luminance > THRESHOLD;
|
return luminance > THRESHOLD;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const unCamelCase = (str: string): string => {
|
||||||
|
return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (char) => char.toUpperCase());
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user