diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 5b2bf693..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Release - -on: - push: - branches: - - main - - beta - -jobs: - release: - runs-on: ubuntu-latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 - with: - node-version: 20 - - run: yarn install - - name: Build - run: yarn run build - - name: Release - run: yarn workspaces run semantic-release -e semantic-release-monorepo diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 00000000..342a9a94 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,19 @@ +{ + "extends": "semantic-release-monorepo", + "branches": [ + "+([0-9])?(.{+([0-9]),x}).x", + "main", + "next", + "next-major", + {"name": "beta", "prerelease": true}, + {"name": "alpha", "prerelease": true} + ], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] +} diff --git a/examples/my-gatsby-site/.env b/examples/my-gatsby-site/.env index b52c1ac0..2c5b3ada 100644 --- a/examples/my-gatsby-site/.env +++ b/examples/my-gatsby-site/.env @@ -1,2 +1,2 @@ -GATSBY_LENS_API_URL=https://api.lens.walther.exp.univie.ac.at:8080/graphql +GATSBY_LENS_API_URL=https://api.photonq.lens.atsnek.com/graphql #SENTRY_AUTH_TOKEN=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 diff --git a/examples/my-gatsby-site/package.json b/examples/my-gatsby-site/package.json index 7b21d84d..f0754262 100644 --- a/examples/my-gatsby-site/package.json +++ b/examples/my-gatsby-site/package.json @@ -10,17 +10,18 @@ "scripts": { "develop": "gatsby develop", "start": "gatsby develop", + "build": "NODE_OPTIONS=--max-old-space-size=8192 gatsby build", "serve": "gatsby serve", "clean": "gatsby clean", "typecheck": "tsc --noEmit" }, "dependencies": { - "jaen": "*", - "jaen-fields-mdx": "*", + "@atsnek/jaen": "^1.0.0-rc.1", + "@atsnek/jaen-fields-mdx": "^1.0.0-rc.1", "@radix-ui/react-icons": "^1.3.0", "@react-icons/all-files": "https://github.com/react-icons/react-icons/releases/download/v4.11.0/react-icons-all-files-4.11.0.tgz", "gatsby": "^5.11.0", - "gatsby-plugin-jaen": "*", + "gatsby-plugin-jaen": "^1.0.0-rc.1", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/examples/my-gatsby-site/src/gatsby-plugin-jaen/components/Layout.tsx b/examples/my-gatsby-site/src/gatsby-plugin-jaen/components/Layout.tsx index f89311c7..7eba9aec 100644 --- a/examples/my-gatsby-site/src/gatsby-plugin-jaen/components/Layout.tsx +++ b/examples/my-gatsby-site/src/gatsby-plugin-jaen/components/Layout.tsx @@ -1,4 +1,4 @@ -import {LayoutProps, useWidget} from 'jaen' +import {LayoutProps, useWidget} from '@atsnek/jaen' import {Box, Heading, HStack, Button} from '@chakra-ui/react' import {useEffect} from 'react' diff --git a/examples/my-gatsby-site/src/images/icon.png b/examples/my-gatsby-site/src/images/icon.png new file mode 100644 index 00000000..38b2fb0e Binary files /dev/null and b/examples/my-gatsby-site/src/images/icon.png differ diff --git a/examples/my-gatsby-site/src/pages/404.tsx b/examples/my-gatsby-site/src/pages/404.tsx index aeeb0bea..31ab8d72 100644 --- a/examples/my-gatsby-site/src/pages/404.tsx +++ b/examples/my-gatsby-site/src/pages/404.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import {Link, HeadFC, PageProps} from 'gatsby' -import {PageConfig} from 'jaen' +import {PageConfig} from '@atsnek/jaen' import {LightMode, GlobalStyle} from '@chakra-ui/react' const pageStyles = { @@ -55,7 +55,7 @@ const NotFoundPage: React.FC = () => { export default NotFoundPage -export {Head} from 'jaen' +export {Head} from '@atsnek/jaen' export const pageConfig: PageConfig = { label: 'Oops! Page not found', diff --git a/examples/my-gatsby-site/src/pages/contact.tsx b/examples/my-gatsby-site/src/pages/contact.tsx index 07723834..99df281d 100644 --- a/examples/my-gatsby-site/src/pages/contact.tsx +++ b/examples/my-gatsby-site/src/pages/contact.tsx @@ -1,4 +1,4 @@ -import {PageConfig, PageProps, useNotificationsContext} from 'jaen' +import {PageConfig, PageProps, useNotificationsContext} from '@atsnek/jaen' import { Button, diff --git a/examples/my-gatsby-site/src/pages/editor.tsx b/examples/my-gatsby-site/src/pages/editor.tsx index 225326e8..161a2fad 100644 --- a/examples/my-gatsby-site/src/pages/editor.tsx +++ b/examples/my-gatsby-site/src/pages/editor.tsx @@ -1,4 +1,4 @@ -import {Field, PageConfig, PageProps} from 'jaen' +import {Field, PageConfig, PageProps} from '@atsnek/jaen' const Page: React.FC = ({location, pageContext}) => { return diff --git a/examples/my-gatsby-site/src/pages/hidden-node.tsx b/examples/my-gatsby-site/src/pages/hidden-node.tsx index f754d7bd..0a714c3e 100644 --- a/examples/my-gatsby-site/src/pages/hidden-node.tsx +++ b/examples/my-gatsby-site/src/pages/hidden-node.tsx @@ -1,4 +1,4 @@ -import {PageConfig, PageProps} from 'jaen' +import {PageConfig, PageProps} from '@atsnek/jaen' const Page: React.FC = ({location, pageContext}) => { return ( diff --git a/examples/my-gatsby-site/src/pages/index.tsx b/examples/my-gatsby-site/src/pages/index.tsx index 46e7a2d4..1eb8cdc2 100644 --- a/examples/my-gatsby-site/src/pages/index.tsx +++ b/examples/my-gatsby-site/src/pages/index.tsx @@ -10,14 +10,14 @@ import { useMediaModal, usePageContext, useSiteMetadataContext -} from 'jaen' +} from '@atsnek/jaen' import {Link, useJaenFrameMenuContext} from 'gatsby-plugin-jaen' import {Box, Button, LightMode, Text} from '@chakra-ui/react' import {graphql} from 'gatsby' import * as React from 'react' -import {UncontrolledMdxField} from 'jaen-fields-mdx' +import {UncontrolledMdxField} from '@atsnek/jaen-fields-mdx' import {FaCogs} from '@react-icons/all-files/fa/FaCogs' const pageStyles = { @@ -403,4 +403,4 @@ export const query = graphql` } ` -export {Head} from 'jaen' +export {Head} from '@atsnek/jaen' diff --git a/examples/my-gatsby-site/src/pages/mdx.tsx b/examples/my-gatsby-site/src/pages/mdx.tsx index 02de2874..0258f494 100644 --- a/examples/my-gatsby-site/src/pages/mdx.tsx +++ b/examples/my-gatsby-site/src/pages/mdx.tsx @@ -1,8 +1,8 @@ -import {Field, PageConfig, PageProps} from 'jaen' +import {Field, PageConfig, PageProps} from '@atsnek/jaen' import {Box} from '@chakra-ui/react' -import {MdxField, UncontrolledMdxField} from 'jaen-fields-mdx' +import {MdxField, UncontrolledMdxField} from '@atsnek/jaen-fields-mdx' import {Link} from 'gatsby-plugin-jaen' -import {usePage} from 'jaen' +import {usePage} from '@atsnek/jaen' import {useState} from 'react' const QASMPlayground: React.FC<{ diff --git a/examples/my-gatsby-site/src/pages/section.tsx b/examples/my-gatsby-site/src/pages/section.tsx index 688e60c1..242349a7 100644 --- a/examples/my-gatsby-site/src/pages/section.tsx +++ b/examples/my-gatsby-site/src/pages/section.tsx @@ -1,4 +1,4 @@ -import {connectBlock, Field, PageConfig, PageProps} from 'jaen' +import {connectBlock, Field, PageConfig, PageProps} from '@atsnek/jaen' import {VStack} from '@chakra-ui/react' const Block = connectBlock( diff --git a/examples/my-gatsby-site/src/pages/tree.tsx b/examples/my-gatsby-site/src/pages/tree.tsx index b373236f..efa0c40e 100644 --- a/examples/my-gatsby-site/src/pages/tree.tsx +++ b/examples/my-gatsby-site/src/pages/tree.tsx @@ -3,7 +3,7 @@ import { PageConfig, PageProps, useCMSManagementContext -} from 'jaen' +} from '@atsnek/jaen' import {CMSManagement} from 'gatsby-plugin-jaen' const Page: React.FC = ({location, pageContext}) => { diff --git a/examples/my-gatsby-site/src/pages/user/[...].tsx b/examples/my-gatsby-site/src/pages/user/[...].tsx index 19c7f79b..7f60711e 100644 --- a/examples/my-gatsby-site/src/pages/user/[...].tsx +++ b/examples/my-gatsby-site/src/pages/user/[...].tsx @@ -1,4 +1,4 @@ -import {PageConfig, PageProps} from 'jaen' +import {PageConfig, PageProps} from '@atsnek/jaen' const Page: React.FC = ({location, pageContext}) => { // everything after /user/ is the handle diff --git a/examples/my-gatsby-site/src/pages/wholesale.tsx b/examples/my-gatsby-site/src/pages/wholesale.tsx index 7cf7a9e4..7d47d5f1 100644 --- a/examples/my-gatsby-site/src/pages/wholesale.tsx +++ b/examples/my-gatsby-site/src/pages/wholesale.tsx @@ -1,4 +1,4 @@ -import {PageConfig, PageProps} from 'jaen' +import {PageConfig, PageProps} from '@atsnek/jaen' const Page: React.FC = ({location, pageContext}) => { return ( diff --git a/examples/my-gatsby-site/src/templates/BlogPage.tsx b/examples/my-gatsby-site/src/templates/BlogPage.tsx index 6bff1b0e..26a735ce 100644 --- a/examples/my-gatsby-site/src/templates/BlogPage.tsx +++ b/examples/my-gatsby-site/src/templates/BlogPage.tsx @@ -1,8 +1,8 @@ import * as React from 'react' import {Link, HeadFC, PageProps, navigate, graphql} from 'gatsby' -import {Field, PageConfig, useField, useJaenPageIndex} from 'jaen' +import {Field, PageConfig, useField, useJaenPageIndex} from '@atsnek/jaen' import {Button} from '@chakra-ui/react' -import {MdxField} from 'jaen-fields-mdx' +import {MdxField} from '@atsnek/jaen-fields-mdx' const BlogPage: React.FC = props => { const index = useJaenPageIndex() @@ -67,4 +67,4 @@ export const query = graphql` } ` -export {Head} from 'jaen' +export {Head} from '@atsnek/jaen' diff --git a/lerna.json b/lerna.json new file mode 100644 index 00000000..89c5be33 --- /dev/null +++ b/lerna.json @@ -0,0 +1,5 @@ +{ + "version": "independent", + "npmClient": "yarn", + "useWorkspaces": true +} diff --git a/package.json b/package.json index 8d4f86a8..8c5f017a 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,12 @@ "description": "The Webapps framework for creating scalable and dynamic applications with ease.", "private": true, "version": "1.0.0", - "repository": "https://github.com/jaenjs/jaen", - "author": "Nico Schett ", + "repository": "https://github.com/atsnek/jaen", + "author": "Nico Schett ", "scripts": { "lint:fix": "yarn eslint packages/jaen/src/ --fix", "prettier:fix": "yarn prettier packages/jaen/src/ --write", - "format": "yarn prettier:fix && yarn lint:fix", - "build": "yarn workspace jaen run build && yarn workspace gatsby-source-jaen run build && yarn workspace gatsby-plugin-jaen run build && yarn workspace gatsby-jaen-mailpress run build && yarn workspace gatsby-jaen-lens run build && yarn workspace jaen-fields-mdx run build" + "format": "yarn prettier:fix && yarn lint:fix" }, "workspaces": [ "packages/jaen", @@ -26,9 +25,6 @@ "prettier-plugin-organize-imports": "^3.2.2" }, "devDependencies": { - "@semantic-release/changelog": "^6.0.3", - "@semantic-release/commit-analyzer": "^13.0.0", - "@semantic-release/release-notes-generator": "^14.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0", "eslint": "^8.0.1", "eslint-config-standard-with-typescript": "^27.0.1", @@ -36,8 +32,6 @@ "eslint-plugin-n": "^15.0.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-promise": "^6.0.0", - "eslint-plugin-react": "^7.32.0", - "semantic-release": "^24.0.0", - "semantic-release-monorepo": "^8.0.2" + "eslint-plugin-react": "^7.32.0" } } diff --git a/packages/gatsby-jaen-lens/gatsby/gatsby-node.ts b/packages/gatsby-jaen-lens/gatsby/gatsby-node.ts index 84ca20c4..97a057b9 100644 --- a/packages/gatsby-jaen-lens/gatsby/gatsby-node.ts +++ b/packages/gatsby-jaen-lens/gatsby/gatsby-node.ts @@ -1,4 +1,4 @@ -import {PageConfig} from 'jaen' +import {PageConfig} from '@atsnek/jaen' import {GatsbyNode, PluginOptions} from 'gatsby' export interface JaenLensPluginOptions extends PluginOptions { diff --git a/packages/gatsby-jaen-lens/package.json b/packages/gatsby-jaen-lens/package.json index 867bf51a..69f59bfc 100644 --- a/packages/gatsby-jaen-lens/package.json +++ b/packages/gatsby-jaen-lens/package.json @@ -1,6 +1,6 @@ { "name": "gatsby-jaen-lens", - "version": "1.0.0", + "version": "0.0.17", "description": "Snek lens plugin for Jaen to render proxied views of internal services behind a firewall.", "main": "index.js", "scripts": { @@ -14,7 +14,7 @@ ], "keywords": [ "gatsby", - "jaen", + "@atsnek/jaen", "snek-lens", "proxy" ], @@ -27,6 +27,7 @@ }, "peerDependencies": { "@chakra-ui/react": "^2.8.0", + "@snek-functions/origin": "^0.9.0", "react": "^18.2.0" }, "devDependencies": { diff --git a/packages/gatsby-jaen-lens/src/clients/lens/src/index.ts b/packages/gatsby-jaen-lens/src/clients/lens/src/index.ts index 5110558d..bd71f740 100644 --- a/packages/gatsby-jaen-lens/src/clients/lens/src/index.ts +++ b/packages/gatsby-jaen-lens/src/clients/lens/src/index.ts @@ -1,7 +1,4 @@ import {makeSnekQuery} from 'snek-query' -import {User} from 'oidc-client-ts' -import 'jaen/dist/types' - import {Query, Mutation} from './schema.generated' const apiURL = process.env.GATSBY_LENS_API_URL @@ -13,19 +10,6 @@ if (!apiURL) { export const sq = makeSnekQuery( {Query, Mutation}, { - apiURL, - middlewares: [ - ({context}) => { - const oidcStorage = sessionStorage.getItem( - `oidc.user:${__JAEN_ZITADEL__.authority}:${__JAEN_ZITADEL__.clientId}` - ) - - if (oidcStorage) { - const user = User.fromStorageString(oidcStorage) - - context.headers['Authorization'] = `Bearer ${user.access_token}` - } - } - ] + apiURL } ) diff --git a/packages/gatsby-jaen-lens/src/components/PasswordUpdateForm/PasswordUpdateForm.tsx b/packages/gatsby-jaen-lens/src/components/PasswordUpdateForm/PasswordUpdateForm.tsx index 6ce47357..7440bddb 100644 --- a/packages/gatsby-jaen-lens/src/components/PasswordUpdateForm/PasswordUpdateForm.tsx +++ b/packages/gatsby-jaen-lens/src/components/PasswordUpdateForm/PasswordUpdateForm.tsx @@ -1,163 +1,174 @@ +import React, {useState} from 'react' import { + Box, Button, - ButtonGroup, - FormControl, // Added FormHelperText + FormControl, + FormErrorMessage, + FormHelperText, // Added FormHelperText FormLabel, - HStack, Input, - List, - ListIcon, - ListItem, - Stack + VStack, + Progress, + Text, + Stack, + Checkbox } from '@chakra-ui/react' -import {FaCheck} from '@react-icons/all-files/fa/FaCheck' -import {FaX} from '@react-icons/all-files/fa6/FaX' -import React, {useState} from 'react' +import {useForm, Controller} from 'react-hook-form' +import zxcvbn from 'zxcvbn' + +type PasswordResetFormValues = { + newPassword: string + confirmPassword: string + changeResourcePassword: boolean +} export interface PasswordUpdateFormProps { - passwordPolicy: { - minLength: number - hasSymbol: boolean - hasNumber: boolean - hasUppercase: boolean - hasLowercase: boolean - } - onPasswordUpdate: (currentPassword: string, password: string) => Promise + onSubmit: (data: PasswordResetFormValues) => Promise + resource: {name: string} } export const PasswordUpdateForm: React.FC = props => { - const [currentPassword, setCurrentPassword] = useState('') - const [password, setPassword] = useState('') - const [passwordConfirmation, setPasswordConfirmation] = useState('') + const [passwordStrength, setPasswordStrength] = useState(null) + const [passwordSuggestions, setPasswordSuggestions] = useState([]) - const [isPasswordChanging, setIsPasswordChanging] = useState(false) + const { + register, + handleSubmit, + getValues, + control, + formState: {errors, isSubmitting} + } = useForm({ + defaultValues: { + changeResourcePassword: true + } + }) - const handlePasswordChange = async () => { - setIsPasswordChanging(true) - await props.onPasswordUpdate(currentPassword, password) - setIsPasswordChanging(false) + const onSubmit = async (data: PasswordResetFormValues) => { + await props.onSubmit(data) } - return ( - - - Enter the new password according to the policy below. - - - Current Password - setCurrentPassword(e.target.value)} - /> - - - - {props.passwordPolicy.minLength && ( - - {password.length >= props.passwordPolicy.minLength ? ( - - ) : ( - - )} - Has to be at least {props.passwordPolicy.minLength} characters long. - ({password.length} / {props.passwordPolicy.minLength}) - - )} - {props.passwordPolicy.hasSymbol && ( - - {/[\p{P}\p{S}]/u.test(password) ? ( - - ) : ( - - )} - Must include a symbol or punctuation mark. - - )} - - {props.passwordPolicy.hasNumber && ( - - {/\d/.test(password) ? ( - - ) : ( - - )} - Must include a number. - - )} + const checkPasswordStrength = (password: string) => { + const result = zxcvbn(password) + setPasswordStrength(result.score) + setPasswordSuggestions(result.feedback.suggestions) + } - {props.passwordPolicy.hasUppercase && ( - - {/[A-Z]/.test(password) ? ( - - ) : ( - - )} - Must include an uppercase letter. - - )} + const getPasswordStrengthColor = () => { + if (passwordStrength === null) { + return 'gray' + } + // Define color codes for different password strengths + const colors = ['red', 'orange', 'yellow', 'green', 'teal'] + // Map the password strength score (0-4) to the colors + return colors[passwordStrength] + } - {props.passwordPolicy.hasLowercase && ( - - {/[a-z]/.test(password) ? ( - - ) : ( - - )} - Must include a lowercase letter. - - )} + return ( + +
+ + + + New password + { + // Custom validation for password strength + const strengthResult = zxcvbn(value) + if (strengthResult.score < 2) { + return 'Password strength is insufficient' + } + return true + } + }} + render={({field}) => ( + + { + field.onChange(e) + checkPasswordStrength(e.target.value) + }} + /> + + + )} + /> + {/* + Password Strength:{' '} + { + ['Very Weak', 'Weak', 'Fair', 'Strong', 'Very Strong'][ + passwordStrength || 0 + ] + } + */} + {passwordSuggestions.length > 0 && ( + + Suggestions: {passwordSuggestions.join(' ')} + + )} + + {errors.newPassword && errors.newPassword.message} + + - - {password && password === passwordConfirmation ? ( - - ) : ( - - )} - Passwords match. - - + + Confirm password + + value === getValues('newPassword') || + 'Passwords do not match' + }} + render={({field}) => } + /> + + {errors.confirmPassword && errors.confirmPassword.message} + + + - - - New Password - setPassword(e.target.value)} - /> - - - Confirm Password - setPasswordConfirmation(e.target.value)} - /> - - + + + + Change {props.resource.name} password + + + + When checked, the password reset will change all internal + passwords and the main password. + + - - - - - + + +
+
) } diff --git a/packages/gatsby-jaen-lens/src/pages/lens/index.tsx b/packages/gatsby-jaen-lens/src/pages/lens/index.tsx index 71b8a130..965ffcf5 100644 --- a/packages/gatsby-jaen-lens/src/pages/lens/index.tsx +++ b/packages/gatsby-jaen-lens/src/pages/lens/index.tsx @@ -1,9 +1,8 @@ import { PageConfig, - checkUserRoles, - useAuth, + useAuthenticationContext, useNotificationsContext -} from 'jaen' +} from '@atsnek/jaen' import { Button, Heading, @@ -24,6 +23,7 @@ import { Thead, Tr } from '@chakra-ui/react' +import {getTokenPair, sq as origin} from '@snek-functions/origin' import {graphql, Link as GatsbyLink} from 'gatsby' import {useEffect, useState} from 'react' import {FaEdit} from 'react-icons/fa' @@ -37,13 +37,13 @@ import { import {IconChooser} from '../../components/IconChooser' const Page: React.FC = () => { - const {isAuthenticated, user} = useAuth() + const {isAuthenticated, user} = useAuthenticationContext() const {toast} = useNotificationsContext() const [isLoading, setIsLoading] = useState(true) const [services, setServices] = useState([]) - const isAdmin = checkUserRoles(user, ['268418173034829430:lens:admin']) + const isAdmin = user?.isAdmin useEffect(() => { const fetchServices = async () => { @@ -87,23 +87,45 @@ const Page: React.FC = () => { meta: LensServiceMetaInput } ): Promise => { + // refresh by calling userMe on origin + const [_, errors] = await origin.query(q => q.userMe.id) + + if (errors) { + toast({ + title: 'Error', + description: + 'Failed to refresh token. This is likely a bug or a network issue. Please try again later.', + status: 'error' + }) + return + } + + const {accessToken} = getTokenPair() + try { - const [data, errors] = await sq.mutate(m => { - const service = m.serviceUpdate({ - id, - meta: inputData.meta - }) + const [data, errors] = await sq.mutate( + m => { + const service = m.serviceUpdate({ + id, + meta: inputData.meta + }) - return { - id: service?.id, - fqdn: service?.fqdn, - host: service?.host, - port: service?.port, - meta: service?.meta, - isSecure: service?.isSecure, - __typename: service?.__typename + return { + id: service?.id, + fqdn: service?.fqdn, + host: service?.host, + port: service?.port, + meta: service?.meta, + isSecure: service?.isSecure, + __typename: service?.__typename + } + }, + { + headers: { + Authorization: `Bearer ${accessToken}` + } } - }) + ) if (errors) { throw new Error(errors[0]?.message) @@ -299,4 +321,4 @@ export const query = graphql` } ` -export {Head} from 'jaen' +export {Head} from '@atsnek/jaen' diff --git a/packages/gatsby-jaen-lens/src/pages/lens/password.tsx b/packages/gatsby-jaen-lens/src/pages/lens/password.tsx deleted file mode 100644 index 06500967..00000000 --- a/packages/gatsby-jaen-lens/src/pages/lens/password.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import {Heading, Progress, Stack, StackDivider, Text} from '@chakra-ui/react' -import {navigate} from 'gatsby' -import { - AuthUserProvider, - PageConfig, - PageProps, - useAuthUser, - useNotificationsContext -} from 'jaen' - -import {sq} from '../../clients/lens/src' -import {PasswordUpdateForm} from '../../components/PasswordUpdateForm' - -const Page: React.FC = () => { - const {user, passwordPolicy, passwordUpdate} = useAuthUser() - const {toast} = useNotificationsContext() - - const handlePasswordChange = async ( - oldPassword: string, - newPassword: string - ) => { - const [_, errors] = await sq.mutate(m => - m.updateInternalPassword({password: newPassword}) - ) - - const success = !errors || errors.length === 0 - - if (success) { - toast({ - title: 'Success', - description: 'Password updated successfully', - status: 'success' - }) - - navigate('/lens/') - - await passwordUpdate(oldPassword, newPassword) - } else { - toast({ - title: 'Error', - description: - 'Failed to update password. This is likely a bug or a network issue. Please try again later.', - status: 'error' - }) - } - } - - if (!user) { - return - } - - return ( - } - id="coco" - spacing="4"> - - - Set internal password - - - - This sets the passwords for the internal services accessed through - Lens. After updating the password, you can access the internal - services using your username{' '} - <{user?.preferredLoginName}> and the password - you have set. - - - - - - - Note: This password is only valid for services connected to Lens - authentication. If you have any questions or issues, please reach out to - your administrator. - - - ) -} - -export const pageConfig: PageConfig = { - label: 'Lens Password', - icon: 'FaKey', - layout: { - name: 'jaen', - type: 'form' - }, - menu: { - type: 'user', - order: 500 - }, - auth: { - isRequired: true - }, - breadcrumbs: [ - { - label: 'Lens', - path: '/lens/' - }, - { - label: 'Password', - path: '/lens/password/' - } - ], - withoutJaenFrameStickyHeader: true -} - -export default props => { - return ( - - - - ) -} - -export {Head} from 'jaen' diff --git a/packages/gatsby-jaen-lens/src/pages/lens/service.tsx b/packages/gatsby-jaen-lens/src/pages/lens/service.tsx index de0c88dc..cf817a5f 100644 --- a/packages/gatsby-jaen-lens/src/pages/lens/service.tsx +++ b/packages/gatsby-jaen-lens/src/pages/lens/service.tsx @@ -1,5 +1,5 @@ import {Box} from '@chakra-ui/react' -import {PageConfig, PageProps} from 'jaen' +import {PageConfig, PageProps} from '@atsnek/jaen' import {useEffect, useState} from 'react' const Page: React.FC = ({data, location}) => { @@ -52,4 +52,4 @@ export const pageConfig: PageConfig = { export default Page -export {Head} from 'jaen' +export {Head} from '@atsnek/jaen' diff --git a/packages/gatsby-jaen-mailpress/package.json b/packages/gatsby-jaen-mailpress/package.json index 8c6037b2..5494be5d 100644 --- a/packages/gatsby-jaen-mailpress/package.json +++ b/packages/gatsby-jaen-mailpress/package.json @@ -13,7 +13,7 @@ "gatsby-node.js" ], "scripts": { - "build": "tsc gatsby/* --outDir dist/gatsby --esModuleInterop --skipLibCheck --resolveJsonModule && tsc" + "build": "tsc gatsby/* --outDir dist/gatsby --esModuleInterop --skipLibCheck --resolveJsonModule" }, "dependencies": { "@monaco-editor/react": "^4.6.0", @@ -29,7 +29,7 @@ "@snek-functions/origin": "^0.9.0", "gatsby": "^5.12.4", "react": "^18.2.0", - "jaen": "^1.0.0" + "@atsnek/jaen": "^1.0.0-rc.55" }, "devDependencies": {} } diff --git a/packages/gatsby-jaen-mailpress/src/client/src/index.ts b/packages/gatsby-jaen-mailpress/src/client/src/index.ts index 9f477f32..28bab904 100644 --- a/packages/gatsby-jaen-mailpress/src/client/src/index.ts +++ b/packages/gatsby-jaen-mailpress/src/client/src/index.ts @@ -1,6 +1,6 @@ import {makeSnekQuery} from 'snek-query' import {User} from 'oidc-client-ts' -import 'jaen/dist/types' +import '@atsnek/jaen/dist/types' import {Query, Mutation} from './schema.generated' diff --git a/packages/gatsby-jaen-mailpress/src/client/src/schema.generated.ts b/packages/gatsby-jaen-mailpress/src/client/src/schema.generated.ts index 3665d86a..ce371cab 100644 --- a/packages/gatsby-jaen-mailpress/src/client/src/schema.generated.ts +++ b/packages/gatsby-jaen-mailpress/src/client/src/schema.generated.ts @@ -1,4 +1,4 @@ -// @ts-nocheck + import { proxy, arrayProxy, fnProxy, fnArrayProxy, t } from "snek-query"; export enum OAuthProvider { diff --git a/packages/gatsby-jaen-mailpress/src/hooks.ts b/packages/gatsby-jaen-mailpress/src/hooks.ts new file mode 100644 index 00000000..61e4c1db --- /dev/null +++ b/packages/gatsby-jaen-mailpress/src/hooks.ts @@ -0,0 +1,239 @@ +import {snekResourceId, useNotificationsContext} from '@atsnek/jaen' +import {sq} from '@snek-functions/origin' + +import React, {useCallback, useEffect, useState} from 'react' + +import {Mutation} from '@snek-functions/origin/dist/schema.generated' + +type UserCreate = Parameters[0] +type UserUpdate = Parameters[0] + +export const useUser = (userId: string) => { + const {toast} = useNotificationsContext() + const [isLoading, setIsLoading] = useState(true) + const [user, setUser] = React.useState<{ + id: string + primaryEmailAddress: string + username: string + createdAt: string + details?: { + avatarURL?: string + firstName?: string + lastName?: string + } + isActive: boolean + isAdmin: boolean + roles: Array<{id: string; description: string}> + }>() + + const checkErrors = (errors: Array<{message: string}>) => { + if (errors?.length > 0) { + toast({ + title: 'Error', + description: errors[0]?.message, + status: 'error', + duration: 5000, + isClosable: true + }) + } + + return !errors || errors.length === 0 + } + + const fetchUser = useCallback(async () => { + const [user, errors] = await sq.query(Query => { + const user = Query.user({id: userId, resourceId: snekResourceId}) + + console.log('USER', user) + + return { + id: user.id, + primaryEmailAddress: user.primaryEmailAddress, + username: user.username, + createdAt: user.createdAt, + details: { + avatarURL: user.details?.avatarURL || undefined, + firstName: user.details?.firstName || undefined, + lastName: user.details?.lastName || undefined + }, + isActive: user.isActive, + isAdmin: user.isAdmin, + roles: user.roles.map(role => ({ + id: role.id, + description: role.description + })) + } + }) + + const ok = checkErrors(errors) + + setUser(user) + setIsLoading(false) + }, []) + + useEffect(() => { + fetchUser() + }, []) + + return { + user, + isLoading + } +} + +export const useUsers = () => { + const {toast} = useNotificationsContext() + const [isLoading, setIsLoading] = useState(true) + + const [users, setUsers] = React.useState< + { + id: string + primaryEmailAddress: string + username: string + createdAt: string + details?: { + avatarURL?: string + firstName?: string + lastName?: string + } + isActive: boolean + isAdmin: boolean + }[] + >([]) + + const checkErrors = (errors: Array<{message: string}>) => { + if (errors?.length > 0) { + toast({ + title: 'Error', + description: errors[0]?.message, + status: 'error', + duration: 5000, + isClosable: true + }) + } + + return !errors || errors.length === 0 + } + + const fetchUsers = useCallback(async () => { + const [users, errors] = await sq.query(Query => + Query.allUser({resourceId: snekResourceId}).map(user => ({ + id: user?.id, + primaryEmailAddress: user?.primaryEmailAddress, + username: user?.username, + createdAt: user?.createdAt, + details: { + avatarURL: user?.details?.avatarURL || undefined, + firstName: user?.details?.firstName || undefined, + lastName: user?.details?.lastName || undefined + }, + isActive: user?.isActive, + isAdmin: user?.isAdmin + })) + ) + + const ok = checkErrors(errors) + + setUsers(users as any) + setIsLoading(false) + }, []) + + useEffect(() => { + fetchUsers() + }, []) + + const addUser = async (values: UserCreate['values']) => { + const [newUser, errors] = await sq.mutate(Mutation => { + const user = Mutation.userCreate({ + resourceId: snekResourceId, + values, + skipEmailVerification: true + }) + + return { + id: user?.id, + primaryEmailAddress: user?.primaryEmailAddress, + username: user?.username, + createdAt: user?.createdAt, + details: { + firstName: user?.details?.firstName || undefined, + lastName: user?.details?.lastName || undefined + }, + isActive: user?.isActive, + isAdmin: user?.isAdmin + } + }) + + const ok = checkErrors(errors) + + if (ok) { + setUsers([...users, newUser] as any) + + toast({ + title: 'Success', + description: 'User created', + status: 'success', + duration: 5000, + isClosable: true + }) + } + + return ok + } + + const updateUser = async ( + id: UserUpdate['id'], + values: UserUpdate['values'] + ) => { + const [updatedUser, errors] = await sq.mutate(Mutation => { + const user = Mutation.userUpdate({ + id, + values + }) + + return { + id: user?.id, + primaryEmailAddress: user?.primaryEmailAddress, + username: user?.username, + createdAt: user?.createdAt, + details: { + firstName: user?.details?.firstName || undefined, + lastName: user?.details?.lastName || undefined + }, + isActive: user?.isActive, + isAdmin: user?.isAdmin + } + }) + + const ok = checkErrors(errors) + + if (ok) { + setUsers(users.map(u => (u.id === id ? updatedUser : u)) as any) + } + + return ok + } + + const deleteUser = async (userId: string) => { + const [deletedUser, errors] = await sq.mutate(Mutation => + Mutation.userDelete({id: userId}) + ) + + const ok = checkErrors(errors) + + if (ok) { + setUsers(users.filter(u => u.id !== userId)) + } + + return ok + } + + return { + users, + addUser, + updateUser, + deleteUser, + + isLoading + } +} diff --git a/packages/gatsby-jaen-mailpress/src/index.ts b/packages/gatsby-jaen-mailpress/src/index.ts deleted file mode 100644 index e2668173..00000000 --- a/packages/gatsby-jaen-mailpress/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {sq} from './client/src/index' diff --git a/packages/gatsby-jaen-mailpress/src/pages/mailpress/index.tsx b/packages/gatsby-jaen-mailpress/src/pages/mailpress/index.tsx index b85878c0..d6679265 100644 --- a/packages/gatsby-jaen-mailpress/src/pages/mailpress/index.tsx +++ b/packages/gatsby-jaen-mailpress/src/pages/mailpress/index.tsx @@ -1,4 +1,4 @@ -import {PageConfig, PageProps} from 'jaen' +import {PageConfig, PageProps} from '@atsnek/jaen' import {useEffect} from 'react' import {navigate} from 'gatsby' diff --git a/packages/gatsby-jaen-mailpress/src/pages/mailpress/templates/[templateId].tsx b/packages/gatsby-jaen-mailpress/src/pages/mailpress/templates/[templateId].tsx index 6d136dcb..59ffbea0 100644 --- a/packages/gatsby-jaen-mailpress/src/pages/mailpress/templates/[templateId].tsx +++ b/packages/gatsby-jaen-mailpress/src/pages/mailpress/templates/[templateId].tsx @@ -1,8 +1,13 @@ -import {PageConfig, PageProps, useNotificationsContext} from 'jaen' +import {PageConfig, PageProps, useNotificationsContext} from '@atsnek/jaen' import {useEffect, useMemo} from 'react' import {CopyIcon, DeleteIcon} from '@chakra-ui/icons' import { + Accordion, + AccordionButton, + AccordionIcon, + AccordionItem, + AccordionPanel, Box, Button, ButtonGroup, @@ -14,6 +19,7 @@ import { FormControl, FormErrorMessage, FormLabel, + HStack, Heading, IconButton, Input, @@ -34,7 +40,8 @@ import { Th, Thead, Tr, - UnorderedList + UnorderedList, + VStack } from '@chakra-ui/react' import {Editor} from '@monaco-editor/react' import {Link as GatsbyLink, navigate} from 'gatsby' @@ -42,6 +49,11 @@ import {sanitize} from 'isomorphic-dompurify' import {Controller, useFieldArray, useForm} from 'react-hook-form' import {useQuery} from 'snek-query/react-hooks' import {sq} from '../../../client/src' +import { + EmailTemplate, + VariableDefinition +} from '../../../client/src/schema.generated' +import templates from '.' const Page: React.FC = ({params}) => { const templateId = params.templateId @@ -221,7 +233,7 @@ const Page: React.FC = ({params}) => { }) if (confirmed) { // delete using sq - const [_, errors] = await sq.mutate(m => + const [data, errors] = await sq.mutate(m => m.templateDelete({ id: templateId }) @@ -355,7 +367,7 @@ const Page: React.FC = ({params}) => { To - {envelopeToField.fields.map((_, index) => ( + {envelopeToField.fields.map((field, index) => ( = ({params}) => { height="var(--chakra-sizes-xs)" defaultLanguage="javascript" defaultValue={field.value || undefined} - onChange={(value, _) => field.onChange(value)} + onChange={(value, event) => field.onChange(value)} /> )} /> @@ -442,7 +454,7 @@ const Page: React.FC = ({params}) => { height="var(--chakra-sizes-md)" defaultLanguage="html" defaultValue={field.value || undefined} - onChange={(value, _) => field.onChange(value)} + onChange={(value, event) => field.onChange(value)} /> )} /> @@ -565,6 +577,304 @@ const Page: React.FC = ({params}) => { ) + + // Form with chakraui components + return ( + + Email Template + + {/* Template ID with copy button */} + + + Template ID + + } + variant="outline" + onClick={onCopy}> + + + +
+ + + {/* Description Field */} + + + Description + + + {errors.description?.message} + + + + + {/* Parent Field */} + + + Parent + + + + + {/* Linked Field just for display and link to template */} + + + Linked + {template?.linked?.length ? ( + + {template?.linked?.map(t => ( + + + {t.description} ({t.id}) + + + ))} + + ) : ( + No linked templates + )} + + + + {/* Envelope Card */} + + + Envelope + + + + {/* Subject Field */} + + + Subject + + + + + {/* From Field */} + + + From + + + + + + + + + {/* To Field */} + + + To + + + + + {/* Reply To Field */} + + + Reply To + + + + + + + + + + + + {/* Transformer Card */} + + + Transformer + + + + {/* Transformer Field */} + + + ( + field.onChange(value)} + /> + )} + /> + + + + + + + {/* Content Card */} + + + Content + + + + + {/* Content Field */} + + + ( + field.onChange(value)} + /> + )} + /> + + + + + {/* Preview */} + + Preview + + + + + + + + + {/* Variables Card */} + + + Variables + + + + {/* Variables Field */} + + +