This is an automated email from the ASF dual-hosted git repository.

ganning pushed a commit to branch author-roles
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git

commit 0e96d24a10fa7a49a86463fc674d955924f58215
Author: ganning127 <[email protected]>
AuthorDate: Sat Jul 19 14:13:35 2025 -0700

    support adding author roles
---
 airavata-research-portal/src/App.tsx               |  19 ---
 .../src/components/add/ConfirmRepoDetails.tsx      | 190 +++++++++++----------
 .../src/components/home/ResourceCard.tsx           |  31 ++--
 .../src/components/resources/ResourceDetails.tsx   |  17 +-
 .../interfaces/Requests/CreateResourceRequest.tsx  |   5 +-
 .../src/interfaces/ResourceAuthor.ts               |  12 ++
 .../src/interfaces/ResourceType.ts                 |  12 +-
 7 files changed, 144 insertions(+), 142 deletions(-)

diff --git a/airavata-research-portal/src/App.tsx 
b/airavata-research-portal/src/App.tsx
index 4df295780..7e9a503a6 100644
--- a/airavata-research-portal/src/App.tsx
+++ b/airavata-research-portal/src/App.tsx
@@ -1,22 +1,3 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 {useColorMode} from "./components/ui/color-mode";
 import {Route, Routes, useLocation, useNavigate} from "react-router";
 import Home from "./components/home";
diff --git a/airavata-research-portal/src/components/add/ConfirmRepoDetails.tsx 
b/airavata-research-portal/src/components/add/ConfirmRepoDetails.tsx
index 360eda2f9..8ad90c96e 100644
--- a/airavata-research-portal/src/components/add/ConfirmRepoDetails.tsx
+++ b/airavata-research-portal/src/components/add/ConfirmRepoDetails.tsx
@@ -1,10 +1,11 @@
-import { PrivacyEnum } from "@/interfaces/PrivacyEnum";
-import { CreateResourceRequest } from 
"@/interfaces/Requests/CreateResourceRequest";
+import {PrivacyEnum} from "@/interfaces/PrivacyEnum";
+import {CreateResourceRequest} from 
"@/interfaces/Requests/CreateResourceRequest";
 import api from "@/lib/api";
-import { CONTROLLER } from "@/lib/controller";
+import {CONTROLLER} from "@/lib/controller";
 import {
   Button,
   Code,
+  createListCollection,
   Field,
   HStack,
   Input,
@@ -13,11 +14,10 @@ import {
   Text,
   Textarea,
   VStack,
-  createListCollection,
 } from "@chakra-ui/react";
-import { toaster } from "../ui/toaster";
-import { useNavigate } from "react-router";
-import { useState } from "react";
+import {toaster} from "../ui/toaster";
+import {useNavigate} from "react-router";
+import {useState} from "react";
 
 const privacyOptions = createListCollection({
   items: Object.keys(PrivacyEnum).map((key) => ({
@@ -27,10 +27,10 @@ const privacyOptions = createListCollection({
 });
 
 export const ConfirmRepoDetails = ({
-  createResourceRequest,
-  setCreateResourceRequest,
-  githubUrl,
-}: {
+                                     createResourceRequest,
+                                     setCreateResourceRequest,
+                                     githubUrl,
+                                   }: {
   createResourceRequest: CreateResourceRequest;
   setCreateResourceRequest: (data: CreateResourceRequest) => void;
   githubUrl: string;
@@ -42,8 +42,8 @@ export const ConfirmRepoDetails = ({
     try {
       setLoading(true);
       await api.post(
-        `${CONTROLLER.resources}/repository?githubUrl=${githubUrl}`,
-        createResourceRequest
+          `${CONTROLLER.resources}/repository?githubUrl=${githubUrl}`,
+          createResourceRequest
       );
 
       toaster.create({
@@ -52,7 +52,7 @@ export const ConfirmRepoDetails = ({
         type: "success",
       });
       navigate(
-        "/resources?resourceTypes=REPOSITORY%2CNOTEBOOK%2CDATASET%2CMODEL"
+          "/resources?resourceTypes=REPOSITORY%2CNOTEBOOK%2CDATASET%2CMODEL"
       );
     } catch (error) {
       console.error("Error adding repository:", error);
@@ -66,87 +66,89 @@ export const ConfirmRepoDetails = ({
     }
   };
 
+  console.log(createResourceRequest);
+
   return (
-    <>
-      <VStack gap={4}>
-        <Text>
-          To make any changes, please modify the 
<Code>cybershuttle.yml</Code>{" "}
-          file in your GitHub repository.
-        </Text>
-        <Field.Root disabled>
-          <Field.Label>Repository Name</Field.Label>
-          <Input value={createResourceRequest.name} />
-        </Field.Root>
-        <Field.Root disabled>
-          <Field.Label>Repository URL</Field.Label>
-          <Input value={githubUrl} />
-        </Field.Root>
-        <Field.Root disabled>
-          <Field.Label>Description</Field.Label>
-          <Textarea maxH="5lh" value={createResourceRequest.description} />
-        </Field.Root>
-        <Field.Root disabled>
-          <Field.Label>Tags</Field.Label>
-          <HStack flexWrap={"wrap"} gap={2}>
-            {createResourceRequest.tags.map((tag) => (
-              <Code key={tag} colorScheme="blue">
-                {tag}
-              </Code>
-            ))}
-          </HStack>
-        </Field.Root>
-        <Field.Root disabled>
-          <Field.Label>Authors</Field.Label>
-          <HStack flexWrap={"wrap"} gap={2}>
-            {createResourceRequest.authors.map((author) => (
-              <Code key={author} colorScheme="blue">
-                {author}
-              </Code>
-            ))}
-          </HStack>
-        </Field.Root>
-        <Field.Root>
-          <Select.Root
-            value={[createResourceRequest.privacy]} // ✅ value must be an array
-            onValueChange={(value) => {
-              console.log(value);
-              setCreateResourceRequest({
-                ...createResourceRequest,
-                privacy: value.value[0] as PrivacyEnum, // ✅ value is a 
string[]
-              });
-            }}
-            collection={privacyOptions}
-            width="full"
-            disabled={true}
-          >
-            <Select.HiddenSelect />
-            <Select.Label>Privacy</Select.Label>
-            <Select.Control width="full">
-              <Select.Trigger width="full">
-                <Select.ValueText placeholder="Select privacy" width="full" />
-              </Select.Trigger>
-              <Select.IndicatorGroup>
-                <Select.Indicator />
-              </Select.IndicatorGroup>
-            </Select.Control>
-            <Portal>
-              <Select.Positioner>
-                <Select.Content width="full">
-                  {privacyOptions.items.map((item) => (
-                    <Select.Item item={item} key={item.value} width="full">
-                      {item.label}
-                    </Select.Item>
-                  ))}
-                </Select.Content>
-              </Select.Positioner>
-            </Portal>
-          </Select.Root>
-        </Field.Root>
+      <>
+        <VStack gap={4}>
+          <Text>
+            To make any changes, please modify the 
<Code>cybershuttle.yml</Code>{" "}
+            file in your GitHub repository.
+          </Text>
+          <Field.Root disabled>
+            <Field.Label>Repository Name</Field.Label>
+            <Input value={createResourceRequest.name}/>
+          </Field.Root>
+          <Field.Root disabled>
+            <Field.Label>Repository URL</Field.Label>
+            <Input value={githubUrl}/>
+          </Field.Root>
+          <Field.Root disabled>
+            <Field.Label>Description</Field.Label>
+            <Textarea maxH="5lh" value={createResourceRequest.description}/>
+          </Field.Root>
+          <Field.Root disabled>
+            <Field.Label>Tags</Field.Label>
+            <HStack flexWrap={"wrap"} gap={2}>
+              {createResourceRequest.tags.map((tag) => (
+                  <Code key={tag} colorScheme="blue">
+                    {tag}
+                  </Code>
+              ))}
+            </HStack>
+          </Field.Root>
+          <Field.Root disabled>
+            <Field.Label>Authors</Field.Label>
+            <VStack alignItems={'flex-start'} flexWrap={"wrap"} gap={2}>
+              {createResourceRequest.authors.map((author) => (
+                  <Code key={author.authorId + author.role} colorScheme="blue">
+                    {author.authorId} ({author.role})
+                  </Code>
+              ))}
+            </VStack>
+          </Field.Root>
+          <Field.Root>
+            <Select.Root
+                value={[createResourceRequest.privacy]}
+                onValueChange={(value) => {
+                  console.log(value);
+                  setCreateResourceRequest({
+                    ...createResourceRequest,
+                    privacy: value.value[0] as PrivacyEnum,
+                  });
+                }}
+                collection={privacyOptions}
+                width="full"
+                disabled={true}
+            >
+              <Select.HiddenSelect/>
+              <Select.Label>Privacy</Select.Label>
+              <Select.Control width="full">
+                <Select.Trigger width="full">
+                  <Select.ValueText placeholder="Select privacy" width="full"/>
+                </Select.Trigger>
+                <Select.IndicatorGroup>
+                  <Select.Indicator/>
+                </Select.IndicatorGroup>
+              </Select.Control>
+              <Portal>
+                <Select.Positioner>
+                  <Select.Content width="full">
+                    {privacyOptions.items.map((item) => (
+                        <Select.Item item={item} key={item.value} width="full">
+                          {item.label}
+                        </Select.Item>
+                    ))}
+                  </Select.Content>
+                </Select.Positioner>
+              </Portal>
+            </Select.Root>
+          </Field.Root>
 
-        <Button w="full" loading={loading} onClick={onSubmit}>
-          Add Repository
-        </Button>
-      </VStack>
-    </>
+          <Button w="full" loading={loading} onClick={onSubmit}>
+            Add Repository
+          </Button>
+        </VStack>
+      </>
   );
 };
diff --git a/airavata-research-portal/src/components/home/ResourceCard.tsx 
b/airavata-research-portal/src/components/home/ResourceCard.tsx
index 6846a1909..fa8c399b1 100644
--- a/airavata-research-portal/src/components/home/ResourceCard.tsx
+++ b/airavata-research-portal/src/components/home/ResourceCard.tsx
@@ -20,7 +20,7 @@
 import {ModelResource, Resource} from "@/interfaces/ResourceType";
 import {Tag} from "@/interfaces/TagType";
 import {isValidImaage, resourceTypeToColor} from "@/lib/util";
-import {Avatar, Badge, Box, Card, HStack, Image, Text,} from 
"@chakra-ui/react";
+import {Avatar, Badge, Box, Card, HStack, Image, Text, VStack,} from 
"@chakra-ui/react";
 import {ResourceTypeBadge} from "../resources/ResourceTypeBadge";
 import {ResourceTypeEnum} from "@/interfaces/ResourceTypeEnum";
 import {ModelCardButton} from "../models/ModelCardButton";
@@ -40,7 +40,6 @@ export const ResourceCard = ({
   removeOnUnStar?: boolean;
 }) => {
   const [hideCard, setHideCard] = useState(false);
-  const author = resource.authors[0];
 
   const isValidImage = isValidImaage(resource.headerImage);
 
@@ -122,18 +121,22 @@ export const ResourceCard = ({
             </Card.Body>
 
             <Card.Footer justifyContent="space-between" pt={4}>
-              {author && (
-                  <HStack>
-                    <Avatar.Root shape="full" size="sm">
-                      <Avatar.Fallback name={author}/>
-                      <Avatar.Image src={author}/>
-                    </Avatar.Root>
-
-                    <Box>
-                      <Text fontWeight="bold">{author}</Text>
-                    </Box>
-                  </HStack>
-              )}
+              <VStack alignItems={'flex-start'}>
+                {resource.authors.map(author => (
+                        <HStack>
+                          <Avatar.Root shape="full" size="xs">
+                            <Avatar.Fallback name={author.authorId}/>
+                            <Avatar.Image src={author.authorId}/>
+                          </Avatar.Root>
+
+                          <Box>
+                            <Text fontWeight="bold" 
fontSize={'sm'}>{author.authorId}</Text>
+                          </Box>
+                        </HStack>
+                    )
+                )}
+              </VStack>
+
 
               {(resource.type as ResourceTypeEnum) === ResourceTypeEnum.MODEL 
&& (
                   <ModelCardButton model={resource as ModelResource}/>
diff --git 
a/airavata-research-portal/src/components/resources/ResourceDetails.tsx 
b/airavata-research-portal/src/components/resources/ResourceDetails.tsx
index fdb3c4a35..b293a5e56 100644
--- a/airavata-research-portal/src/components/resources/ResourceDetails.tsx
+++ b/airavata-research-portal/src/components/resources/ResourceDetails.tsx
@@ -54,6 +54,7 @@ import {CONTROLLER} from "@/lib/controller";
 import {DatasetSpecificDetails} from "../datasets/DatasetSpecificDetails";
 import {ResourceOptions} from "@/components/resources/ResourceOptions.tsx";
 import {toaster} from "@/components/ui/toaster.tsx";
+import {ResourceAuthor} from "@/interfaces/ResourceAuthor.ts";
 
 async function getResource(id: string) {
   const response = await api.get(`${CONTROLLER.resources}/public/${id}`);
@@ -171,20 +172,20 @@ const ResourceDetails = () => {
                 ))}
               </HStack>
 
-              <HStack mt={8}>
-                {resource.authors.map((author: string) => {
+              <HStack mt={8} wrap={'wrap'}>
+                {resource.authors.map((author: ResourceAuthor) => {
                   return (
-                      <HStack key={author}>
-                        <Avatar.Root shape="full" size="xl">
-                          <Avatar.Fallback name={author}/>
-                          <Avatar.Image src={author}/>
+                      <HStack>
+                        <Avatar.Root shape="full" size="xs">
+                          <Avatar.Fallback name={author.authorId}/>
+                          <Avatar.Image src={author.authorId}/>
                         </Avatar.Root>
 
                         <Box>
-                          <Text fontWeight="bold">{author}</Text>
+                          <Text fontWeight="bold" 
fontSize={'sm'}>{author.authorId}</Text>
                         </Box>
                       </HStack>
-                  );
+                  )
                 })}
               </HStack>
             </Box>
diff --git 
a/airavata-research-portal/src/interfaces/Requests/CreateResourceRequest.tsx 
b/airavata-research-portal/src/interfaces/Requests/CreateResourceRequest.tsx
index 21b027f4d..d8b95b3be 100644
--- a/airavata-research-portal/src/interfaces/Requests/CreateResourceRequest.tsx
+++ b/airavata-research-portal/src/interfaces/Requests/CreateResourceRequest.tsx
@@ -1,10 +1,11 @@
-import { PrivacyEnum } from "../PrivacyEnum";
+import {PrivacyEnum} from "../PrivacyEnum";
+import {ResourceAuthor} from "@/interfaces/ResourceAuthor.ts";
 
 export interface CreateResourceRequest {
   name: string;
   description: string;
   tags: string[];
   headerImage: string;
-  authors: string[];
+  authors: ResourceAuthor[];
   privacy: PrivacyEnum;
 }
diff --git a/airavata-research-portal/src/interfaces/ResourceAuthor.ts 
b/airavata-research-portal/src/interfaces/ResourceAuthor.ts
new file mode 100644
index 000000000..05de6f1d7
--- /dev/null
+++ b/airavata-research-portal/src/interfaces/ResourceAuthor.ts
@@ -0,0 +1,12 @@
+export interface ResourceAuthor {
+  authorId: string;
+  role: AuthorRoleEnum;
+}
+
+enum AuthorRoleEnum {
+  PRIMARY,
+  SECONDARY,
+  TERTIARY,
+  QUATERNARY,
+  QUINARY
+}
diff --git a/airavata-research-portal/src/interfaces/ResourceType.ts 
b/airavata-research-portal/src/interfaces/ResourceType.ts
index 32d4f697a..f1b03272e 100644
--- a/airavata-research-portal/src/interfaces/ResourceType.ts
+++ b/airavata-research-portal/src/interfaces/ResourceType.ts
@@ -1,7 +1,9 @@
-import { PrivacyEnum } from "./PrivacyEnum";
-import { ResourceTypeEnum } from "./ResourceTypeEnum";
-import { StatusEnum } from "./StatusEnum";
-import { Tag } from "./TagType";
+import {PrivacyEnum} from "./PrivacyEnum";
+import {ResourceTypeEnum} from "./ResourceTypeEnum";
+import {StatusEnum} from "./StatusEnum";
+import {Tag} from "./TagType";
+import {ResourceAuthor} from "@/interfaces/ResourceAuthor.ts";
+
 // import { User } from "./UserType";
 
 export interface Resource {
@@ -9,7 +11,7 @@ export interface Resource {
   name: string;
   description: string;
   headerImage: string;
-  authors: string[];
+  authors: ResourceAuthor[];
   tags: Tag[];
   status: StatusEnum;
   privacy: PrivacyEnum;

Reply via email to