Skip to content

Commit

Permalink
feat: permissions and roles (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
manolo-battista authored Oct 26, 2023
1 parent 8c35e1c commit 930f67c
Show file tree
Hide file tree
Showing 34 changed files with 874 additions and 107 deletions.
16 changes: 16 additions & 0 deletions arke/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright 2023 Arkemis S.r.l.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const acceptedRoles = ["super_admin"];
25 changes: 15 additions & 10 deletions components/AppFormConfigProvider/AppFormConfigProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
import { ReactNode } from "react";
import { FormConfigProvider as FCProvider } from "@arkejs/form";
import { Autocomplete, Checkbox, Input, Json } from "@arkejs/ui";
import AutocompleteLink, {
LinkRef,
} from "@/components/AppFormConfigProvider/components/AutocompleteLink";
import AutocompleteLink from "@/components/AppFormConfigProvider/components/AutocompleteLink";
import Dropzone from "@/components/Dropzone/Dropzone";

export default function AppFormConfigProvider(props: { children: ReactNode }) {
return (
Expand Down Expand Up @@ -94,13 +93,19 @@ export default function AppFormConfigProvider(props: { children: ReactNode }) {
onChange={(value) => field.onChange(JSON.parse(value))}
/>
),
link: ({ field }) => (
<AutocompleteLink
{...field}
refLink={field.refLink}
onChange={field.onChange}
/>
),
link: ({ field }: any) =>
field?.link_ref?.id === "arke_file" ? (
<>
<Dropzone
{...field}
onChange={(files) => {
field?.onChange(files?.[0] ?? null);
}}
/>
</>
) : (
<AutocompleteLink {...field} onChange={field.onChange} />
),
default: () => <></>,
}}
>
Expand Down
79 changes: 24 additions & 55 deletions components/AppFormConfigProvider/components/AutocompleteLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,52 +19,19 @@ import { useEffect, useState } from "react";
import { TUnit } from "@arkejs/client";
import { Autocomplete } from "@arkejs/ui";
import toast from "react-hot-toast";

export type LinkRef = { id: string; arke_id: "group" | "arke" };
import { LinkRef } from "@/types/link";
import useLinkRef from "@/hooks/useLinkRef";

type AutocompleteLinkProps = {
refLink: LinkRef;
link_ref: LinkRef;
onChange: (value: any) => void;
value: string;
};

export default function AutocompleteLink(props: AutocompleteLinkProps) {
const { refLink, onChange } = props;
const { link_ref, onChange } = props;
const client = useClient();
const [values, setValues] = useState<TUnit[]>([]);

useEffect(() => {
// getAll: arke / group (id: se é gruppo o arke)
// filter_keys [OR]
// params: load_links: true => getAll
if (refLink?.arke_id === "group") {
// TODO: implement getAll by group and add filters with filter_keys
// client.unit.getAll(refLink.id).then((res) => {
client.group
.getAllUnits(refLink.id)
.then((res) => {
setValues(res.data.content.items);
})
.catch(() =>
toast.error("Something went wrong during group retrieval", {
id: "error_link_group",
})
);
}
if (refLink?.arke_id === "arke") {
client.unit
.getAll(refLink.id)
.then((res) => {
setValues(res.data.content.items);
})
.catch(() =>
toast.error("Something went wrong during arke retrieval", {
id: "error_link_arke",
})
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const { values } = useLinkRef(link_ref);

function getValue() {
if (Array.isArray(props.value)) {
Expand All @@ -77,22 +44,24 @@ export default function AutocompleteLink(props: AutocompleteLinkProps) {
}

return (
<Autocomplete
{...props}
onChange={(value) => {
if (Array.isArray(value)) {
onChange((value as TUnit[]).map((item) => item.id));
} else {
onChange((value as TUnit).id);
}
}}
renderValue={(value) => {
return `[${(value as TUnit).arke_id}] ${
(value as TUnit).label ?? (value as TUnit).id
}`;
}}
values={values}
value={getValue()}
/>
<>
<Autocomplete
{...props}
onChange={(value) => {
if (Array.isArray(value)) {
onChange((value as TUnit[]).map((item) => item.id));
} else {
onChange((value as TUnit).id);
}
}}
renderValue={(value) => {
return `[${(value as TUnit).arke_id}] ${
(value as TUnit).label ?? (value as TUnit).id
}`;
}}
values={values}
value={getValue()}
/>
</>
);
}
115 changes: 115 additions & 0 deletions components/Dropzone/Dropzone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* Copyright 2023 Arkemis S.r.l.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React, { useState } from "react";
import { useDropzone } from "react-dropzone";
import { ArrowUpTrayIcon, XMarkIcon } from "@heroicons/react/24/outline";
import toast from "react-hot-toast";
import { formatBytes } from "@/utils/file";
interface DropzoneProps {
value?: string;
label?: string;
onChange?(files: File[]): void;
}

interface AcceptedFile extends File {
path?: string;
}

const maxSize = 5243000; // 5Mb as bytes
export default function Dropzone(props: DropzoneProps) {
const { value, label, onChange } = props;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
multiple: false,
maxSize,
onDrop: (acceptedFiles, fileRejections) => {
setSelectedFiles([...selectedFiles, ...acceptedFiles]);
onChange?.([...selectedFiles, ...acceptedFiles]);
fileRejections.forEach((file) => {
file.errors.forEach((err) => {
if (err.code === "file-too-large") {
toast.error(`${err.message}`);
}

if (err.code === "file-invalid-type") {
toast.error(`${err.message}`);
}
});
});
},
});

const removeFile = (file: File) => {
const newFiles = [...selectedFiles];
newFiles.splice(newFiles.indexOf(file), 1);
setSelectedFiles(newFiles);
onChange?.(newFiles);
};

const removeAll = () => {
setSelectedFiles([]);
onChange?.([]);
};

const File = (file: { path?: string; size: number }) => (
<li className="flex gap-2 text-xs" key={file.path}>
<XMarkIcon
className="w-4 cursor-pointer text-error"
onClick={() => removeFile(file as File)}
/>
{file.path} - {formatBytes(file.size)}
</li>
);

return (
<>
{label}
{selectedFiles.length === 0 && !value && (
<section>
<div
{...getRootProps({ className: "dropzone" })}
className="min-h-20 flex cursor-pointer flex-col items-center justify-center
gap-1 rounded-xl border-2 border-dashed border-[rgba(255,255,255,0.2)] p-12 text-center duration-75
hover:border-primary"
>
<input {...getInputProps()} />
<ArrowUpTrayIcon className="h-5 w-5" />
<p className="font-semibold">Upload file</p>
<p className="text-xs">{formatBytes(maxSize)} max. file size</p>
</div>
</section>
)}
<aside className="grid gap-2">
{selectedFiles.length > 0 ? (
<ul>
{acceptedFiles.map((file: AcceptedFile, index) => (
<File key={index} path={file.path} size={file.size} />
))}
</ul>
) : (
value && (
<ul>
{/*// TODO: check value of backend with load_images */}
{/*// @ts-ignore */}
<File path={value.path} size={1000000} />
</ul>
)
)}
</aside>
</>
);
}
36 changes: 36 additions & 0 deletions components/Icon/ExpandIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright 2023 Arkemis S.r.l.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export function ExpandIcon({ className }: { className?: string }) {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M19 9L12.7071 15.2929C12.3166 15.6834 11.6834 15.6834 11.2929 15.2929L5 9"
stroke="#fff"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
47 changes: 47 additions & 0 deletions components/Permissions/PermissionInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright 2023 Arkemis S.r.l.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import useClient from "@/arke/useClient";
import { Input } from "@arkejs/ui";
import { useRef, useState } from "react";
import useOnClickOutside from "@/hooks/useOnClickOutside";

interface PermissionInputProps {
role: string;
value: string;
}
export function PermissionInput(props: PermissionInputProps) {
const client = useClient();
const [value, setValue] = useState(props.value);
const inputRef = useRef<HTMLDivElement>(null);

function onUpdateData() {
console.log(value);
}

useOnClickOutside(inputRef, onUpdateData);
return (
<Input
{...props}
label=""
// @ts-ignore
itemRef={inputRef}
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && onUpdateData()}
onBlur={onUpdateData}
/>
);
}
Loading

0 comments on commit 930f67c

Please sign in to comment.