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 { type ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
@@ -44,7 +49,8 @@ import {
|
||||
} from "@/lib/query/hooks";
|
||||
import { queryKeys } from "@/lib/query/keys";
|
||||
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 }) {
|
||||
const { user } = useAuthenticatedSession();
|
||||
@@ -440,7 +446,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="max-w-sm">
|
||||
<DialogContent className="w-md max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Organisations</DialogTitle>
|
||||
</DialogHeader>
|
||||
@@ -457,6 +463,7 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
<TabsTrigger value="users">Users</TabsTrigger>
|
||||
<TabsTrigger value="projects">Projects</TabsTrigger>
|
||||
<TabsTrigger value="issues">Issues</TabsTrigger>
|
||||
<TabsTrigger value="features">Features</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
@@ -917,6 +924,37 @@ function Organisations({ trigger }: { trigger?: ReactNode }) {
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
) : (
|
||||
<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;
|
||||
|
||||
const dialogContent = (
|
||||
<DialogContent className={cn("w-md", (error || dateError) && "border-destructive")}>
|
||||
<DialogContent className={cn("w-sm", (error || dateError) && "border-destructive")}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{isEdit ? "Edit Sprint" : "Create Sprint"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -65,3 +65,7 @@ export const isLight = (hex: string): boolean => {
|
||||
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
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