diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx index c6f0ebd0..f77ffc93 100644 --- a/src/app/(auth)/layout.tsx +++ b/src/app/(auth)/layout.tsx @@ -33,14 +33,14 @@ export default function AuthLayout({children}: Readonly<{ children: React.ReactN width={40} height={40}/> - + display={"inline"}> Every great idea starts at zero. {" "} - - Start with CodeZero. - + + + Start with CodeZero. {children} diff --git a/src/app/(dashboard)/@bar/default.tsx b/src/app/(dashboard)/@bar/default.tsx index 99055c6e..11a620f6 100644 --- a/src/app/(dashboard)/@bar/default.tsx +++ b/src/app/(dashboard)/@bar/default.tsx @@ -1,7 +1,7 @@ "use client"; import React from "react"; -import {ApplicationBarView} from "@edition/application/ApplicationBarView"; +import {ApplicationBarView} from "@edition/application/views/ApplicationBarView"; const Page = () => { return diff --git a/src/app/(dashboard)/@tab/organizations/default.tsx b/src/app/(dashboard)/@tab/organizations/default.tsx index 3ef9f84b..5251309f 100644 --- a/src/app/(dashboard)/@tab/organizations/default.tsx +++ b/src/app/(dashboard)/@tab/organizations/default.tsx @@ -1,7 +1,7 @@ "use client" import React from "react"; -import {ApplicationTabView} from "@edition/application/ApplicationTabView"; +import {ApplicationTabView} from "@edition/application/views/ApplicationTabView"; const Page: React.FC = () => { return diff --git a/src/app/(dashboard)/@tab/page.tsx b/src/app/(dashboard)/@tab/page.tsx index 3ef9f84b..5251309f 100644 --- a/src/app/(dashboard)/@tab/page.tsx +++ b/src/app/(dashboard)/@tab/page.tsx @@ -1,7 +1,7 @@ "use client" import React from "react"; -import {ApplicationTabView} from "@edition/application/ApplicationTabView"; +import {ApplicationTabView} from "@edition/application/views/ApplicationTabView"; const Page: React.FC = () => { return diff --git a/src/app/(dashboard)/@tab/runtimes/default.tsx b/src/app/(dashboard)/@tab/runtimes/default.tsx index 3ef9f84b..5251309f 100644 --- a/src/app/(dashboard)/@tab/runtimes/default.tsx +++ b/src/app/(dashboard)/@tab/runtimes/default.tsx @@ -1,7 +1,7 @@ "use client" import React from "react"; -import {ApplicationTabView} from "@edition/application/ApplicationTabView"; +import {ApplicationTabView} from "@edition/application/views/ApplicationTabView"; const Page: React.FC = () => { return diff --git a/src/app/(dashboard)/@tab/settings/page.tsx b/src/app/(dashboard)/@tab/settings/page.tsx index 3ef9f84b..5251309f 100644 --- a/src/app/(dashboard)/@tab/settings/page.tsx +++ b/src/app/(dashboard)/@tab/settings/page.tsx @@ -1,7 +1,7 @@ "use client" import React from "react"; -import {ApplicationTabView} from "@edition/application/ApplicationTabView"; +import {ApplicationTabView} from "@edition/application/views/ApplicationTabView"; const Page: React.FC = () => { return diff --git a/src/app/(dashboard)/@tab/users/page.tsx b/src/app/(dashboard)/@tab/users/page.tsx index 3ef9f84b..5251309f 100644 --- a/src/app/(dashboard)/@tab/users/page.tsx +++ b/src/app/(dashboard)/@tab/users/page.tsx @@ -1,7 +1,7 @@ "use client" import React from "react"; -import {ApplicationTabView} from "@edition/application/ApplicationTabView"; +import {ApplicationTabView} from "@edition/application/views/ApplicationTabView"; const Page: React.FC = () => { return diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx index 3e15e148..0bb2d45c 100644 --- a/src/app/(dashboard)/layout.tsx +++ b/src/app/(dashboard)/layout.tsx @@ -27,6 +27,7 @@ import {RuntimeService} from "@edition/runtime/services/Runtime.service"; import {ProjectService} from "@edition/project/services/Project.service"; import {RoleService} from "@edition/role/services/Role.service"; import Image from "next/image"; +import {Application, ApplicationService} from "@edition/application/services/Application.service"; interface ApplicationLayoutProps { children: React.ReactNode @@ -49,10 +50,11 @@ const ApplicationLayout: React.FC = ({children, bar, tab const runtime = usePersistentReactiveArrayService(`dashboard::global_runtimes::${currentSession?.id}`, (store) => new RuntimeService(graphqlClient, store)) const project = usePersistentReactiveArrayService(`dashboard::projects::${currentSession?.id}`, (store) => new ProjectService(graphqlClient, store)) const role = usePersistentReactiveArrayService(`dashboard::roles::${currentSession?.id}`, (store) => new RoleService(graphqlClient, store)) + const application = usePersistentReactiveArrayService(`dashboard::application::${currentSession?.id}`, (store) => new ApplicationService(graphqlClient, store)) if (currentSession === null) router.push("/login") - return + return
diff --git a/src/app/(dashboard)/settings/page.tsx b/src/app/(dashboard)/settings/page.tsx index 47813a23..da581244 100644 --- a/src/app/(dashboard)/settings/page.tsx +++ b/src/app/(dashboard)/settings/page.tsx @@ -1,7 +1,7 @@ "use client" import React from "react"; -import {ApplicationSettingsPage} from "@edition/application/ApplicationSettingsPage"; +import {ApplicationSettingsPage} from "@edition/application/pages/ApplicationSettingsPage"; export default function Page() { diff --git a/src/app/(flow)/@bar/default.tsx b/src/app/(flow)/@bar/default.tsx index 99055c6e..11a620f6 100644 --- a/src/app/(flow)/@bar/default.tsx +++ b/src/app/(flow)/@bar/default.tsx @@ -1,7 +1,7 @@ "use client"; import React from "react"; -import {ApplicationBarView} from "@edition/application/ApplicationBarView"; +import {ApplicationBarView} from "@edition/application/views/ApplicationBarView"; const Page = () => { return diff --git a/src/packages/ce/src/application/ApplicationPage.tsx b/src/packages/ce/src/application/pages/ApplicationPage.tsx similarity index 100% rename from src/packages/ce/src/application/ApplicationPage.tsx rename to src/packages/ce/src/application/pages/ApplicationPage.tsx diff --git a/src/packages/ce/src/application/ApplicationSettingsPage.tsx b/src/packages/ce/src/application/pages/ApplicationSettingsPage.tsx similarity index 52% rename from src/packages/ce/src/application/ApplicationSettingsPage.tsx rename to src/packages/ce/src/application/pages/ApplicationSettingsPage.tsx index 93c4a382..772c4d3b 100644 --- a/src/packages/ce/src/application/ApplicationSettingsPage.tsx +++ b/src/packages/ce/src/application/pages/ApplicationSettingsPage.tsx @@ -1,6 +1,6 @@ "use client" -import React from "react"; +import React, {startTransition} from "react"; import { Badge, Button, @@ -11,7 +11,9 @@ import { Flex, Spacing, SwitchInput, - Text, + Text, TextInput, + toast, + useForm, useService, useStore, useUserSession @@ -21,18 +23,68 @@ import {notFound} from "next/navigation"; import {Tab, TabContent, TabList, TabTrigger} from "@code0-tech/pictor/dist/components/tab/Tab"; import {IconLayoutSidebar} from "@tabler/icons-react"; import CardSection from "@code0-tech/pictor/dist/components/card/CardSection"; -import process from "node:process"; +import {ApplicationService} from "@edition/application/services/Application.service"; export const ApplicationSettingsPage: React.FC = () => { - const currentSession = useUserSession() + const userStore = useStore(UserService) const userService = useService(UserService) - const currentUser = React.useMemo(() => userService.getById(currentSession?.user?.id), [userStore, currentSession]) + const applicationService = useService(ApplicationService) + const applicationStore = useStore(ApplicationService) + + const currentSession = useUserSession() + + const currentUser = React.useMemo( + () => userService.getById(currentSession?.user?.id), + [userStore, currentSession] + ) + + const application = React.useMemo( + () => applicationService.get(), + [applicationStore] + ) if (currentUser && !currentUser.admin) { notFound() } + const initialValues = React.useMemo( + () => ({ + adminStatusVisible: application?.settings?.adminStatusVisible, + organizationCreationRestricted: application?.settings?.organizationCreationRestricted, + userRegistrationEnabled: application?.settings?.userRegistrationEnabled, + legalNoticeUrl: !!application?.settings?.legalNoticeUrl ? application?.settings?.legalNoticeUrl : null, + privacyUrl: !!application?.settings?.privacyUrl ? application?.settings?.privacyUrl : null, + termsAndConditionsUrl: !!application?.settings?.termsAndConditionsUrl ? application?.settings?.termsAndConditionsUrl : null, + }), + [application] + ) + + const [inputs, validate] = useForm({ + initialValues: initialValues, + validate: {}, + onSubmit: (values) => { + startTransition(() => { + applicationService.applicationUpdate({ + adminStatusVisible: values.adminStatusVisible, + organizationCreationRestricted: values.organizationCreationRestricted, + userRegistrationEnabled: values.userRegistrationEnabled, + legalNoticeUrl: !!values.legalNoticeUrl ? values.legalNoticeUrl : null, + privacyUrl: !!values.privacyUrl ? values.privacyUrl : null, + termsAndConditionsUrl: !!values.termsAndConditionsUrl ? values.termsAndConditionsUrl : null, + }).then(payload => { + if ((payload?.errors?.length ?? 0) <= 0) { + toast({ + title: "The application was successfully updated.", + color: "success", + dismissible: true, + }) + } + }) + }) + } + }) + return { library {process.env.NEXT_PUBLIC_pictorVersion ?? "v0.0.0-mvp.10"} + color={"info"}>v0.0.0-mvp.10 @@ -101,9 +153,44 @@ export const ApplicationSettingsPage: React.FC = () => { + + + Legal url's + + + + + + + + Legal notice url + + + + + + + + Privacy information url + + + + + + + + Terms and conditions url + + + + + - Restrictions + + Restrictions + + @@ -115,7 +202,8 @@ export const ApplicationSettingsPage: React.FC = () => { to administrators. - + @@ -125,7 +213,18 @@ export const ApplicationSettingsPage: React.FC = () => { Set if user registration is enabled. - + + + + + + + Admin status + Set if users can se who is admin + + diff --git a/src/packages/ce/src/application/services/Application.service.ts b/src/packages/ce/src/application/services/Application.service.ts new file mode 100644 index 00000000..210f5255 --- /dev/null +++ b/src/packages/ce/src/application/services/Application.service.ts @@ -0,0 +1,67 @@ +import {ReactiveArrayService, ReactiveArrayStore} from "@code0-tech/pictor"; +import { + Application as SApplication, + ApplicationSettingsUpdateInput, + ApplicationSettingsUpdatePayload, + Mutation, + Query +} from "@code0-tech/sagittarius-graphql-types"; +import {Payload, View} from "@code0-tech/pictor/dist/utils/view"; +import {GraphqlClient} from "@core/util/graphql-client"; +import applicationQuery from "@edition/application/services/queries/Application.query.graphql" +import applicationUpdateMutation from "@edition/application/services/mutations/Application.update.mutation.graphql" + +export type Application = SApplication & Payload + +export class ApplicationService extends ReactiveArrayService { + + private readonly client: GraphqlClient + + constructor(client: GraphqlClient, store: ReactiveArrayStore>) { + super(store); + this.client = client + } + + + get(): SApplication { + + const application = super.get(0) + + if (!application) { + this.client.query({ + query: applicationQuery + }).then(res => { + const app = res.data?.application + if (app) this.set(0, new View(app as Application)) + }) + } + + return application + + } + + async applicationUpdate(payload: ApplicationSettingsUpdateInput): Promise { + const result = await this.client.mutate({ + mutation: applicationUpdateMutation, + variables: { + ...payload + } + }) + + if (result.data && result.data.applicationSettingsUpdate && result.data.applicationSettingsUpdate.applicationSettings) { + const application = this.get() + this.set(0, new View({ + ...application, + legalNoticeUrl: result.data.applicationSettingsUpdate.applicationSettings.legalNoticeUrl, + privacyUrl: result.data.applicationSettingsUpdate.applicationSettings.privacyUrl, + termsAndConditionsUrl: result.data.applicationSettingsUpdate.applicationSettings.termsAndConditionsUrl, + settings: { + ...result.data.applicationSettingsUpdate.applicationSettings + } + } as Application)) + + } + + return result.data?.applicationSettingsUpdate ?? undefined + } +} \ No newline at end of file diff --git a/src/packages/ce/src/application/services/fragments/Application.fragment.graphql b/src/packages/ce/src/application/services/fragments/Application.fragment.graphql new file mode 100644 index 00000000..38dc6bfb --- /dev/null +++ b/src/packages/ce/src/application/services/fragments/Application.fragment.graphql @@ -0,0 +1,29 @@ +fragment Application on Application { + __typename + metadata { + __typename + extensions + version + } + legalNoticeUrl + privacyUrl + termsAndConditionsUrl + settings { + __typename + termsAndConditionsUrl + privacyUrl + legalNoticeUrl + adminStatusVisible + organizationCreationRestricted + userRegistrationEnabled + } + userAbilities { + __typename + createOrganization + createRuntime + deleteRuntime + rotateRuntimeToken + updateApplicationSetting + updateRuntime + } +} \ No newline at end of file diff --git a/src/packages/ce/src/application/services/mutations/Application.update.mutation.graphql b/src/packages/ce/src/application/services/mutations/Application.update.mutation.graphql new file mode 100644 index 00000000..018940ab --- /dev/null +++ b/src/packages/ce/src/application/services/mutations/Application.update.mutation.graphql @@ -0,0 +1,29 @@ +mutation ApplicationUpdate($userRegistrationEnabled: Boolean, $organizationCreationRestricted: Boolean, $adminStatusVisible: Boolean, $termsAndConditionsUrl: String, $privacyUrl: String, $legalNoticeUrl: String) { + applicationSettingsUpdate(input: { + userRegistrationEnabled: $userRegistrationEnabled + organizationCreationRestricted: $organizationCreationRestricted + adminStatusVisible: $adminStatusVisible + termsAndConditionsUrl: $termsAndConditionsUrl + privacyUrl: $privacyUrl + legalNoticeUrl: $legalNoticeUrl + }) { + errors { + ...on Error { + errorCode, + details { + ...on ActiveModelError { attribute type } + __typename + } + } + } + applicationSettings { + __typename + termsAndConditionsUrl + privacyUrl + legalNoticeUrl + adminStatusVisible + organizationCreationRestricted + userRegistrationEnabled + } + } +} \ No newline at end of file diff --git a/src/packages/ce/src/application/services/queries/Application.query.graphql b/src/packages/ce/src/application/services/queries/Application.query.graphql new file mode 100644 index 00000000..b92812c7 --- /dev/null +++ b/src/packages/ce/src/application/services/queries/Application.query.graphql @@ -0,0 +1,7 @@ +#import "../fragments/Application.fragment.graphql" + +query Application { + application { + ...Application + } +} \ No newline at end of file diff --git a/src/packages/ce/src/application/ApplicationBarView.tsx b/src/packages/ce/src/application/views/ApplicationBarView.tsx similarity index 96% rename from src/packages/ce/src/application/ApplicationBarView.tsx rename to src/packages/ce/src/application/views/ApplicationBarView.tsx index f16099f9..9390a835 100644 --- a/src/packages/ce/src/application/ApplicationBarView.tsx +++ b/src/packages/ce/src/application/views/ApplicationBarView.tsx @@ -18,7 +18,7 @@ import React from "react"; import DUserMenu from "@code0-tech/pictor/dist/components/d-user/DUserMenu"; import Link from "next/link"; import {IconBuilding, IconFolders, IconInbox, IconLogout, IconSearch} from "@tabler/icons-react"; -import {ApplicationBreadcrumbView} from "@edition/application/ApplicationBreadcrumbView"; +import {ApplicationBreadcrumbView} from "@edition/application/views/ApplicationBreadcrumbView"; export const ApplicationBarView: React.FC = () => { diff --git a/src/packages/ce/src/application/ApplicationBreadcrumbView.tsx b/src/packages/ce/src/application/views/ApplicationBreadcrumbView.tsx similarity index 100% rename from src/packages/ce/src/application/ApplicationBreadcrumbView.tsx rename to src/packages/ce/src/application/views/ApplicationBreadcrumbView.tsx diff --git a/src/packages/ce/src/application/ApplicationTabView.tsx b/src/packages/ce/src/application/views/ApplicationTabView.tsx similarity index 100% rename from src/packages/ce/src/application/ApplicationTabView.tsx rename to src/packages/ce/src/application/views/ApplicationTabView.tsx diff --git a/src/packages/ce/src/runtime/services/Runtime.service.ts b/src/packages/ce/src/runtime/services/Runtime.service.ts index ac090117..ef77ce72 100644 --- a/src/packages/ce/src/runtime/services/Runtime.service.ts +++ b/src/packages/ce/src/runtime/services/Runtime.service.ts @@ -36,6 +36,8 @@ export class RuntimeService extends DRuntimeReactiveService { return runtime !== undefined } + //TODO: rework to be able to get all runtimes that you can access. If no namespace id is provided just get the global runtiimes + // TODO: if namespace id is provided get the runtimes for this namespace and also the global runtimes values(dependencies?: DRuntimeDependencies): DRuntimeView[] { const runtimes = super.values()