Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Organization Settings Page #183

Merged
merged 51 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
12d9fb0
refactor: remove sign up button
Ziyang-98 Jun 20, 2024
97ba47b
feat: add function to check if user is owner of current org
Ziyang-98 Jun 20, 2024
838023f
refactor: remove unused name
Ziyang-98 Jun 20, 2024
b2a6b56
feat: init settings page
Ziyang-98 Jun 20, 2024
7f022bd
feat: allow patch requests on server
Ziyang-98 Jun 20, 2024
3a1d838
feat: implement org name change feature in settings
Ziyang-98 Jun 20, 2024
9d7cf4a
feat: disable button of change org name form if user is not owner
Ziyang-98 Jun 20, 2024
2542dea
feat: add conditional rendering of message on no org id found
Ziyang-98 Jun 20, 2024
91e5f7e
feat: add message at org selection page
Ziyang-98 Jun 20, 2024
7209918
feat: use main nav layout for settings
Ziyang-98 Jun 20, 2024
a31f07e
chore: remove unused imports
Ziyang-98 Jun 21, 2024
a40c397
feat: update size of section title
Ziyang-98 Jun 21, 2024
03253d4
chore: add org and membership api to bruno
Ziyang-98 Jun 21, 2024
704b2a2
feat: init membership section in orgs setting
Ziyang-98 Jun 21, 2024
194fadd
chore: clean up unused imports and console.logs
Ziyang-98 Jun 21, 2024
7f53c4e
refactor: use custom keys for items
Ziyang-98 Jun 21, 2024
69b245a
refactor: add key to link
Ziyang-98 Jun 21, 2024
4e3fa03
feat: add debouncing for search filter
Ziyang-98 Jun 21, 2024
e67d1bc
feat: update settings sidebar styling
Ziyang-98 Jun 22, 2024
0f94177
feat: init add member to org dialog
Ziyang-98 Jun 22, 2024
24de2fa
fix: backend getting members for org sql statement to not included de…
Ziyang-98 Jun 22, 2024
2795b93
chore: update create membership api in bruno
Ziyang-98 Jun 22, 2024
8877067
feat: add fetching and filtering of users in add member dialog
Ziyang-98 Jun 22, 2024
33b8ca8
feat: add selected member logic
Ziyang-98 Jun 22, 2024
f39a182
feat: implement adding of member
Ziyang-98 Jun 22, 2024
55974b5
Merge branch 'main' into feature/fe-org-settings
Ziyang-98 Jun 22, 2024
2405417
feat: add refreshing of members after adding
Ziyang-98 Jun 22, 2024
257615d
feat: add loading to adding member
Ziyang-98 Jun 22, 2024
b3b4c54
feat: add easy self identification to member section
Ziyang-98 Jun 22, 2024
161cd74
feat: init member actions
Ziyang-98 Jun 22, 2024
f80502f
Merge branch 'main' into feature/fe-org-settings
Ziyang-98 Jun 22, 2024
dd58c67
feat: init member actions UI with conditional rendering
Ziyang-98 Jun 22, 2024
2a01d76
fix: main nav layout style
Ziyang-98 Jun 22, 2024
732f152
feat: add leave organization ui feature
Ziyang-98 Jun 23, 2024
420d1d5
Merge branch 'main' into feature/fe-org-settings
Ziyang-98 Jun 23, 2024
873f4a1
feat: add loading animation to dialogs
Ziyang-98 Jun 24, 2024
916466e
feat: implement leaving of org and transferring of ownership
Ziyang-98 Jun 24, 2024
5da513a
chore: add leave org API to bruno
Ziyang-98 Jun 24, 2024
79f37f4
fix: styling of buttons
Ziyang-98 Jun 24, 2024
2760194
feat: implement removing of member from org
Ziyang-98 Jun 24, 2024
aef56e5
feat: implement promote and demote actions
Ziyang-98 Jun 24, 2024
755a2b5
feat: remove separator at end
Ziyang-98 Jun 24, 2024
c0672d1
Merge branch 'main' into feature/fe-org-settings
Ziyang-98 Jun 24, 2024
8d43e3c
fix: get org sql statement
Ziyang-98 Jun 26, 2024
e826456
fix: to use username in toast after first login user creation
Ziyang-98 Jun 27, 2024
2e7e979
refactor: update style of create org success toast
Ziyang-98 Jun 27, 2024
dc8c6e5
feat: add success variant to start, approve and reject sr toast
Ziyang-98 Jun 27, 2024
c12e125
feat: update create org toast description
Ziyang-98 Jun 27, 2024
1573e3d
feat: add success variant to cancel sr success toast
Ziyang-98 Jun 27, 2024
50b0e34
fix: update step to be cancelled instead of completed on cancelling o…
Ziyang-98 Jun 27, 2024
81db6ca
fix: update step cancelled enum value
Ziyang-98 Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/src/database/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ var (
INNER JOIN public."membership" m
ON o.org_id = m.org_id
WHERE user_id = $1
AND o.deleted = false`
AND o.deleted = false
AND m.deleted = false`

SelectOrganizationByOrgIdAndOwnerStatement = `SELECT * FROM public."organization"
WHERE org_id = $1
Expand Down
2 changes: 1 addition & 1 deletion backend/src/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func New(c *ServerConfig) http.Server {
return http.Server{
Addr: c.Address,
Handler: handlers.CORS(
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE"}),
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "PATCH", "DELETE"}),
handlers.AllowedOrigins([]string{"http://localhost:3000"}),
handlers.AllowedHeaders([]string{
"Content-Type",
Expand Down
38 changes: 19 additions & 19 deletions flowforge_api_bruno/membership/update membership.bru
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
meta {
name: update membership
type: http
seq: 3
}
patch {
url: {{HOST}}/membership
body: json
auth: inherit
}
body:json {
{
"user_id": "auth0|345678",
"org_id": 4,
"role": "Owner"
}
}
meta {
name: update membership
type: http
seq: 3
}

patch {
url: {{HOST}}/membership
body: json
auth: inherit
}

body:json {
{
"user_id": "auth0|345678",
"org_id": 4,
"role": "Owner"
}
}
15 changes: 15 additions & 0 deletions flowforge_api_bruno/organization/leave organization.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
meta {
name: leave organization
type: http
seq: 6
}

delete {
url: {{HOST}}/organization/{{org_id}}/membership
body: none
auth: none
}

vars:pre-request {
org_id:
}
5 changes: 5 additions & 0 deletions frontend/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ const nextConfig = {
destination: "/service-catalog",
permanent: true,
},
{
source: "/settings",
destination: "/settings/organization",
permanent: true,
},
]
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const useServiceRequestForm = ({
router.push("/your-service-request-dashboard")
})
.catch((err) => {
console.log(err)
console.error(err)
toast({
title: "Request Submission Error",
description: "Failed to submit the service request.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import usePipeline from "@/hooks/use-pipeline"
import { createServiceRequest } from "@/lib/service"
import { generateUiSchema } from "@/lib/rjsf-utils"
import { convertServiceRequestFormToRJSFSchema } from "@/lib/rjsf-utils"
import { FormFieldType, JsonFormComponents } from "@/types/json-form-components"
import { IChangeEvent } from "@rjsf/core"
import { RJSFSchema } from "@rjsf/utils"
import { useMemo, useState } from "react"
Expand Down Expand Up @@ -35,17 +34,16 @@ const useServiceRequestForm = ({
return
}
createServiceRequest(organizationId, pipelineId, formData, service?.version)
.then((data) => {
.then(() => {
toast({
title: "Request Submission Successful",
description:
"Please check the dashboard for the status of the request.",
variant: "success",
})
console.log("Response: ", data)
})
.catch((err) => {
console.log(err)
console.error(err)
toast({
title: "Request Submission Error",
description: "Failed to submit the service request.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const useServices = () => {
queryKey: ["pipelines"],
queryFn: () =>
getAllPipeline(organizationId).catch((err) => {
console.log(err)
console.error(err)
toast({
title: "Fetching Services Error",
description: "Failed to fetch the services. Please try again later.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client"

import { cn } from "@/lib/utils"
import Link from "next/link"
import { usePathname } from "next/navigation"

interface SettingsSidebarProps {
className?: string
}

const linkBaseStyle = "transition-colors w-full flex pl-2 py-1"
const linkInactiveStyle = `${linkBaseStyle} hover:rounded-md hover:bg-muted hover:text-muted-foreground`
const linkActiveStyle = `${linkInactiveStyle} rounded-md bg-gray-50`

const links = [{ name: "Organization", href: "/settings/organization" }]

export default function SettingsSidebar({ className }: SettingsSidebarProps) {
const pathname = usePathname()

return (
<div className={cn("flex flex-col gap-4 py-2", className)}>
<p className="font-bold">Settings</p>
<nav>
{links.map((link) => (
<Link
key={link.name}
href={link.href}
className={cn(
`${linkBaseStyle}, ${pathname === link.href ? linkActiveStyle : linkInactiveStyle}`
)}
>
{link.name}
</Link>
))}
</nav>
</div>
)
}
21 changes: 21 additions & 0 deletions frontend/src/app/(authenticated)/(main)/settings/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use client"

import MainNavigationLayout from "@/components/layouts/main-navigation-layout"
import SettingsSidebar from "./_components/settings-sidebar"

export default function SettingsLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<MainNavigationLayout enableOrgName={false}>
<div className="flex justify-center pt-10">
<div className="w-[90%] flex space-x-10">
<SettingsSidebar className="w-[20%]" />
<div className="w-[80%]">{children}</div>
</div>
</div>
</MainNavigationLayout>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { Button, ButtonWithSpinner } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Search, X } from "lucide-react"
import useAddMembers from "../_hooks/use-add-members"
import { UserInfo } from "@/types/user-profile"
import { useState } from "react"
import useDebounce from "@/hooks/use-debounce"
import { Role } from "@/types/membership"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Separator } from "@/components/ui/separator"

interface AddMemberDialogProps {
children: React.ReactNode
existingMembers: UserInfo[]
organizationId: number
refetchMembers: () => void
}

export default function AddMemberDialog({
children,
existingMembers,
organizationId,
refetchMembers,
}: AddMemberDialogProps) {
const [searchFilter, setSearchFilter] = useState("")
// Delay filter execution by 0.5s at each filter change
const { debouncedValue: debouncedFilter } = useDebounce(searchFilter, 500)
const {
allUsers,
selectedMember,
setSelectedMember,
handleAddMember,
isAddingMember,
} = useAddMembers({
existingMembers,
filter: debouncedFilter,
organizationId,
refetchMembers,
})

return (
<Dialog>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Add member to the organization</DialogTitle>
</DialogHeader>

{selectedMember ? (
<>
<div className="border rounded-md flex items-center">
<p className="px-4 py-2">{selectedMember.name}</p>
<X
size="20"
className="ml-auto mr-2 cursor-pointer"
onClick={() => setSelectedMember(undefined)}
/>
</div>
<p className="text-sm px-1">
Select a role for {selectedMember.name}
</p>
<Select
defaultValue={selectedMember.role}
onValueChange={(value) =>
setSelectedMember({ ...selectedMember, role: value as Role })
}
>
<SelectTrigger value={selectedMember.role} className="">
<SelectValue placeholder="Select role for member" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value={Role.Member}>{Role.Member}</SelectItem>
<SelectItem value={Role.Admin}>{Role.Admin}</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<Separator className="w-full" />
</>
) : (
<>
<div className="relative">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search User by Username"
className="pl-8"
onChange={(e) => setSearchFilter(e.target.value)}
/>
</div>
<div className="max-h-[20rem] overflow-y-auto">
<div className="border rounded-md">
<ul className="divide-y divide-slate-200">
{allUsers?.map((user) => (
<li
key={user.user_id}
className="px-4 py-2 cursor-pointer hover:text-blue-500"
onClick={() => {
// Set default role as Member
setSelectedMember({ ...user, role: Role.Member })
setSearchFilter("")
}}
>
<p>{user.name}</p>
</li>
))}
</ul>
</div>
</div>
</>
)}
<DialogFooter>
<ButtonWithSpinner
type="submit"
className="w-full"
disabled={!selectedMember || isAddingMember}
onClick={() => handleAddMember()}
isLoading={isAddingMember}
>
{!selectedMember
? "Select a user"
: `Add ${selectedMember.name} to organization`}
</ButtonWithSpinner>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"use client"

import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import useUpdateOrgNameForm from "../_hooks/use-update-org-name-form"
import { Input } from "@/components/ui/input"
import { useUserMemberships } from "@/contexts/user-memberships-context"
import { ButtonWithSpinner } from "@/components/ui/button"

interface ChangeOrgNameFormProps {
organizationId: number
updateOrgNameInCookie: (name: string) => void
}

export default function ChangeOrgNameForm({
organizationId,
updateOrgNameInCookie,
}: ChangeOrgNameFormProps) {
const { updateOrgNameLoading, form, onFormSubmit } = useUpdateOrgNameForm({
organizationId,
updateOrgNameInCookie,
})

const { isOwner } = useUserMemberships()
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)} className="space-y-5">
<h1 className="text-xl">Change Organization Name</h1>
<FormField
control={form.control}
name="orgName"
render={({ field }) => (
<FormItem className="space-y-2">
<FormDescription>
Only the owner of the organization can change the name.
</FormDescription>
<FormControl>
<Input {...field} disabled={!isOwner} className="max-w-md" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<ButtonWithSpinner
isLoading={updateOrgNameLoading}
disabled={updateOrgNameLoading || !isOwner}
>
Change
</ButtonWithSpinner>
</form>
</Form>
)
}
Loading
Loading