├── .gitignore
├── LICENSE.md
├── README.md
├── lerna.json
├── package.json
├── packages
├── core
│ ├── README.md
│ ├── package.json
│ ├── rollup.config.mjs
│ ├── src
│ │ ├── config
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── services
│ │ │ ├── authService.ts
│ │ │ ├── fileStorage.ts
│ │ │ ├── gqlService.ts
│ │ │ └── index.ts
│ │ └── utils
│ │ │ └── index.ts
│ └── tsconfig.json
├── ui-react
│ ├── README.md
│ ├── package.json
│ ├── rollup.config.mjs
│ ├── src
│ │ ├── components
│ │ │ ├── Auth
│ │ │ │ ├── Login.tsx
│ │ │ │ ├── Signup.tsx
│ │ │ │ ├── VerifyEmail.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Theme
│ │ │ │ ├── ThemeProvider.tsx
│ │ │ │ ├── ThemeProvider.types.ts
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ └── styles
│ │ │ │ └── index.ts
│ │ ├── context
│ │ │ ├── Auth
│ │ │ │ ├── QAuth.tsx
│ │ │ │ ├── QAuth.types.ts
│ │ │ │ └── index.ts
│ │ │ ├── Gql
│ │ │ │ ├── QGql.tsx
│ │ │ │ ├── QGql.types.ts
│ │ │ │ └── index.ts
│ │ │ ├── Theme
│ │ │ │ └── ThemeContext.tsx
│ │ │ └── index.ts
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ ├── useAuth.ts
│ │ │ ├── useGql.ts
│ │ │ └── useTheme.ts
│ │ ├── index.ts
│ │ ├── themes
│ │ │ ├── defaultTheme.ts
│ │ │ └── index.ts
│ │ └── utils
│ │ │ └── index.ts
│ └── tsconfig.json
└── ui-vue
│ ├── README.md
│ ├── package.json
│ ├── rollup.config.mjs
│ ├── src
│ └── index.ts
│ └── tsconfig.json
├── tsconfig.base.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore root node_modules (Yarn workspace will use a single node_modules at the root)
2 | node_modules/
3 |
4 | # Ignore node_modules in all subdirectories
5 | **/node_modules/
6 |
7 | # Ignore Yarn and npm cache files
8 | .yarn/
9 | .pnp.*
10 | .yarnrc.yml
11 | npm-debug.log*
12 | yarn-error.log
13 |
14 | # Ignore build outputs
15 | **/dist/
16 |
17 | # Ignore TypeScript build artifacts
18 | **/*.tsbuildinfo
19 |
20 | # Ignore IDE or OS-specific files
21 | .DS_Store
22 | .idea/
23 | .vscode/
24 | *.swp
25 | *.log
26 |
27 |
28 | # env variables
29 | .env
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [2024] [Quorini]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | 1. The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | 2. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 | 3. The Software may not be used to endorse or promote products derived from
24 | this Software without prior written permission.
25 |
26 | 4. For any distribution, you must include the original copyright notice and a
27 | copy of this license.
28 |
29 | 5. For modifications to this Software, you must include a prominent notice
30 | stating that you have changed the Software and the date of the change.
31 |
32 | [Optional additional clauses can be added here based on your needs]
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Quorini SDK
2 |
This is JS SDK to integrate your Quorini project with web app.
3 |
4 | [](https://quorini.com/)
5 | Define your data model visually, and deploy a fully-managed serverless backend in minutes.
6 |
7 | Visit quorini.com for more details.
8 | Visit quorini.app to start building new project.
9 |
10 | [](https://www.producthunt.com/products/quorini#quorini)
11 |
12 | [](https://quorini.app/)
13 |
14 | ---
15 |
16 | # Getting Started
17 |
18 | For more detail about packages
19 |
20 | [npm package @quorini/core](https://www.npmjs.com/package/@quorini/core) – is used to configure a project with the backend published by quorini.app. Quorini SDK enables developers to develop Quorini Backend-powered mobile and web apps.
21 |
22 | [README.md](/packages/core/README.md)
23 |
24 |
25 | [npm package @quorini/ui-react](https://www.npmjs.com/package/@quorini/ui-react) – leverages a range of functions and React hooks designed to seamlessly integrate with your React application configured using the `@quorini/core` package in conjunction with the backend services provided by [quorini.app](quorini.app)
26 |
27 | [README.md](/packages/ui-react/README.md)
28 |
29 | ## Installation
30 |
31 | ```ts
32 | npm install @quorini/core
33 | npm install @quorini/ui-react
34 | ```
35 |
36 | ## Configuration of SDK
37 |
38 | 1. Go to [quorini.app](http://quorini.app) **"Live API"**.
39 | 2. Navigate to **"Tech Docs"** tab.
40 | 3. Copy types, queries and mutations and place in you codebase/repository.\
41 | 3.1. `./src/quorini-types.d.ts`\
42 | 3.2. `./src/quorini-queries.ts`\
43 | 3.3. `./src/quorini-mutations.ts`
44 | 4. Inside `index.tsx` globally configure you SDK integration.\
45 | 4.1. `projectId` can be copied from URL path of **"Live API"**.\
46 | 4.2. `env` (optional) can be **"production"** or **"development"**. By default, it’s **"production"**.\
47 | 4.3. `qglPaths` (optional) and values are from step 3.
48 |
49 | ```tsx
50 | // index.tsx
51 | ...
52 | import { QClient } from "@quorini/core"
53 | import * as queries from './src/quorini-queries'
54 | import * as mutations from './src/quorini-mutations'
55 |
56 | QClient.configure({
57 | projectId: "YOUR_PROJECT_ID",
58 | env: "YOUR_PROJECT_ENV",
59 | gqlPaths: {
60 | queries,
61 | mutations,
62 | },
63 | })
64 |
65 | ...
66 | ```
67 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json",
3 | "version": "1.1.65",
4 | "npmClient": "yarn"
5 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quorini-ui",
3 | "private": true,
4 | "version": "1.1.18",
5 | "workspaces": [
6 | "packages/*"
7 | ],
8 | "devDependencies": {
9 | "@types/node": "^22.8.7",
10 | "lerna": "^8.1.9",
11 | "rollup": "^2.60.0",
12 | "typescript": "^4.5.0"
13 | },
14 | "scripts": {
15 | "build": "yarn workspaces run build"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # @quorini/core
2 |
3 | ## Description
4 | This package is used to configure a project with the backend published by [quorini.app](https://quorini.app/).\
5 | Quorini SDK enables developers to develop Quorini Backend-powered mobile and web apps.
6 |
7 | For more information, visit [github repository readme](https://github.com/quorini/quorini-js-sdk#quorini-sdk).
8 |
9 | ## Installing
10 | To install this package, simply type add or install `@quorini/core` using your favorite package manager:
11 |
12 | ```ts
13 | npm install @quorini/core
14 |
15 | or
16 | yarn add @quorini/core
17 |
18 | or
19 | pnpm add @quorini/core
20 | ```
21 |
22 | ## Getting Started
23 | The Quorini SDK is modulized by clients and functions\
24 | To send a request, you only need to import the QClient and the functions you need:
25 |
26 | ```tsx
27 | // ES5 example
28 | const { QClient, login, logout } = require("@quorini/core");
29 | ```
30 | ```tsx
31 | // ES6+ example
32 | import { QClient, login, logout } from "@quorini/core";
33 | ```
34 |
35 | ## Usage
36 | ```tsx
37 | import { QClient } from "@quorini/core";
38 |
39 | QClient.configure({
40 | projectId: "YOUR_PROJECT_ID", // specify your project id
41 | env: 'YOUR_PROJECT_ENVIRONMENT', // for example, 'production' or 'development'
42 | // add other optional configuration
43 | })
44 | ```
45 |
46 | ### Async/await
47 | We recommend using await operator to wait for the promise returned by `login` operation as follows:
48 | ```tsx
49 | // async/await.
50 | try {
51 | const session = await login(username, password);
52 | // process session.
53 | } catch (error) {
54 | // error handling.
55 | } finally {
56 | // finally.
57 | }
58 | ```
59 |
60 | Async-await is clean, concise, intuitive, easy to debug and has better error handling as compared to using Promise chains or callbacks.
61 | #### Promises
62 | You can also use Promise chaining to execute `login` operation.
63 | ```tsx
64 | login(username, password).then(
65 | (response) => {
66 | // process response.
67 | },
68 | (error) => {
69 | // error handling.
70 | }
71 | );
72 | ```
73 | Promises can also be called using .catch() and .finally() as follows:
74 | ```tsx
75 | login(username, password)
76 | .then((data) => {
77 | // process data.
78 | })
79 | .catch((error) => {
80 | // error handling.
81 | })
82 | .finally(() => {
83 | // finally.
84 | });
85 | ```
86 |
87 | ## **Subscriptions module (coming soon)**
88 |
89 | ```tsx
90 | import { QStream } from "@quorini/core"
91 |
92 | const callbackFnEvent = (obj) => { user's code }
93 |
94 | QStream.on("event", callbackFnEvent)
95 |
96 | QStrema.on("connect", callbackFnConnect)
97 | QStrema.on("close", callbackFnClose)
98 |
99 | ...
100 |
101 | QStream.subscribe("onObjectCreate", "OPTIONAL_GQL_QUERY_FILTER", "OPTIONAL_SELECTORS")
102 | ```
103 |
104 | ## **Storage (coming soon)**
105 | - **Public file (e.g. Image)**
106 |
107 | ```tsx
108 | import { Storage } from '@quorini/core'
109 |
110 | export function App() {
111 | return
112 | }
113 | ```
114 | - **Private or Protected file (e.g. Image)**
115 |
116 | ```tsx
117 | import { Storage } from '@quorini/core'
118 |
119 | export function App() {
120 | return (
121 | `protected/${identityId}/dog.jpg`}
124 | />
125 | )
126 | }
127 | ```
128 | - **Error Handling (e.g. Image)**
129 |
130 | ```tsx
131 | import { Storage } from '@quorini/core'
132 |
133 | export function App() {
134 | return (
135 | console.error(error)}
140 | />
141 | )
142 | }
143 | ```
144 |
145 | ## Operations
146 | - login
147 | - logout
148 | - signup
149 | - verifyEmail
150 | - refreshAuthToken
151 | - sendInvitation
152 | - acceptInvitation
153 | - query
154 | - mutate
155 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@quorini/core",
3 | "version": "1.1.65",
4 | "main": "dist/index.cjs.js",
5 | "module": "dist/index.esm.js",
6 | "types": "dist/index.d.ts",
7 | "exports": {
8 | "require": {
9 | "types": "./dist/index.d.ts",
10 | "default": "./dist/index.cjs.js"
11 | },
12 | "import": {
13 | "types": "./dist/index.d.ts",
14 | "default": "./dist/index.esm.js"
15 | }
16 | },
17 | "keywords": [
18 | "quorini",
19 | "react",
20 | "typescript",
21 | "graphQL",
22 | "core",
23 | "ui-react"
24 | ],
25 | "scripts": {
26 | "build": "rollup -c"
27 | },
28 | "dependencies": {
29 | "@apollo/client": "^3.11.10",
30 | "bson": "^6.10.1",
31 | "graphql": "^16.9.0"
32 | },
33 | "devDependencies": {
34 | "@types/axios": "^0.14.4",
35 | "@types/graphql": "^14.5.0",
36 | "rollup": "^2.60.0",
37 | "rollup-plugin-commonjs": "^10.1.0",
38 | "rollup-plugin-dts": "^6.1.1",
39 | "rollup-plugin-node-resolve": "^5.2.0",
40 | "rollup-plugin-typescript2": "^0.36.0",
41 | "typescript": "^4.5.0"
42 | },
43 | "publishConfig": {
44 | "access": "public"
45 | },
46 | "repository": {
47 | "type": "git",
48 | "url": "https://github.com/quorini/quorini-js-sdk"
49 | },
50 | "author": "ernst1202",
51 | "license": "MIT",
52 | "gitHead": "76424c713234e1e82c7c9605d9d7fc4015881659"
53 | }
54 |
--------------------------------------------------------------------------------
/packages/core/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2';
2 | import dts from 'rollup-plugin-dts';
3 |
4 | export default [
5 | {
6 | input: './src/index.ts',
7 | output: [
8 | {
9 | file: 'dist/index.cjs.js',
10 | format: 'cjs',
11 | sourcemap: true,
12 | },
13 | {
14 | file: 'dist/index.esm.js',
15 | format: 'esm',
16 | sourcemap: true,
17 | },
18 | ],
19 | plugins: [
20 | typescript({
21 | tsconfig: './tsconfig.json'
22 | })
23 | ],
24 | external: []
25 | },
26 | {
27 | input: './src/index.ts', // Path to your entry declaration file
28 | output: [{ file: 'dist/index.d.ts', format: 'esm' }],
29 | plugins: [dts()],
30 | },
31 | ];
32 |
--------------------------------------------------------------------------------
/packages/core/src/config/index.ts:
--------------------------------------------------------------------------------
1 | // This will allow the React project to configure the values based on their environment variables
2 | interface Config {
3 | projectId: string,
4 | env?: 'production' | 'development',
5 | gqlPaths?: {
6 | queries: Record,
7 | mutations: Record,
8 | },
9 | }
10 |
11 | const QClient = (() => {
12 | let config = {} as Config;
13 |
14 | const privateProdUrls = {
15 | apiUrl: "https://api.quorini.io",
16 | authApiUrl: "https://auth.quorini.io",
17 | };
18 |
19 | /**
20 | * privateDevUrls should be used in SDK developing or testing.
21 | */
22 |
23 | // const privateDevUrls = {
24 | // apiUrl: "https://h5ti6dtzyl.execute-api.us-west-2.amazonaws.com/development",
25 | // authApiUrl: "https://hth72i9z93.execute-api.us-west-2.amazonaws.com/development",
26 | // };
27 |
28 |
29 | return {
30 | // Public configuration method for React projects to pass their environment variables
31 | configure(externalConfig: Config) {
32 | config = { ...externalConfig };
33 | },
34 |
35 | // Retrieve public configuration
36 | getConfig() {
37 | return config;
38 | },
39 |
40 | // Internal method to retrieve private values based on mode
41 | getPrivate() {
42 | return privateProdUrls;
43 | // return privateDevUrls; // When enable privateDevUrls, this code line should be enabled as well.
44 | },
45 | };
46 | })();
47 |
48 | // Export QClient for use in other parts of the SDK
49 | export { QClient };
50 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './config';
2 | export * from './services';
3 | export * from './utils';
--------------------------------------------------------------------------------
/packages/core/src/services/authService.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { QClient } from '../config';
3 | import { SESSION_KEY } from '.';
4 |
5 | // Create an Axios instance with default config
6 | const apiClient = axios.create({
7 | baseURL: QClient.getPrivate().authApiUrl, // Default base URL; can override per request if needed
8 | headers: {
9 | 'Content-Type': 'application/json',
10 | },
11 | });
12 |
13 | // login function
14 | export const login = async (username: string, password: string) => {
15 | let result:any = undefined;
16 | try {
17 | const authApiUrl = QClient.getPrivate().authApiUrl;
18 | const projectId = QClient.getConfig().projectId;
19 | const projectEnvironment = QClient.getConfig().env;
20 | const response = await apiClient.post(`${authApiUrl}/${projectId}/log-in${projectEnvironment === 'development' ? `?env=dev` : ''}`, {
21 | authOption: { username, password },
22 | });
23 | if (response.status === 200 && response.data.accessToken) {
24 | result = response.data;
25 | }
26 | return result;
27 | } catch (error) {
28 | throw error;
29 | }
30 | };
31 |
32 | // signup function
33 | export const signup = async (username: string, password: string, code: string, signupFormData: any, usergroup: string) => {
34 | let result:any = undefined;
35 | try {
36 | const url = `${QClient.getPrivate().apiUrl}/${QClient.getConfig().projectId}/gql${QClient.getConfig().env === 'development' ? `?env=dev` : ''}`
37 | const response = await apiClient.post(url, {
38 | authOption: { username, password, invitationCode: code },
39 | query: `mutation create($input: create${usergroup}Input!) { create${usergroup}(input: $input) { id }}`,
40 | variables: {
41 | input: {
42 | ...signupFormData,
43 | },
44 | },
45 | });
46 | if (response.status === 200) {
47 | result = response.data;
48 | }
49 | return result;
50 | } catch (error) {
51 | throw error;
52 | }
53 | };
54 |
55 | // Verify Email
56 | export const verifyEmail = async (code: string, username: string) => {
57 | let result:any = undefined;
58 | try {
59 | const authApiUrl = QClient.getPrivate().authApiUrl;
60 | const projectId = QClient.getConfig().projectId;
61 | const projectEnvironment = QClient.getConfig().env;
62 | const response = await apiClient.get(`${authApiUrl}/${projectId}/verify-email?code=${code}&username=${username.replace("+", "%2B")}${projectEnvironment === 'development' ? `?env=dev` : ''}`);
63 | if (response.status === 200) result = response.data;
64 | return result;
65 | } catch (error) {
66 | throw error;
67 | }
68 | };
69 |
70 | // refresh auth token
71 | export const refreshAuthToken = async (refreshToken: any) => {
72 | let result:any = undefined;
73 | try {
74 | const authApiUrl = QClient.getPrivate().authApiUrl;
75 | const projectId = QClient.getConfig().projectId;
76 | const projectEnvironment = QClient.getConfig().env;
77 | const response = await apiClient.post(`${authApiUrl}/${projectId}/refresh-token/${projectEnvironment === 'development' ? `?env=dev` : ''}`, {refreshToken});
78 | result = response.data;
79 | return result;
80 | } catch (error) {
81 | throw error;
82 | }
83 | }
84 |
85 | export const sendInvitation = async (email: string, userGroup: string, accessToken: any) => {
86 | let result:any = undefined;
87 | try {
88 | const authApiUrl = QClient.getPrivate().authApiUrl;
89 | const projectId = QClient.getConfig().projectId;
90 | const projectEnvironment = QClient.getConfig().env;
91 | const response = await apiClient.post(`${authApiUrl}/${projectId}/send-invitation${projectEnvironment === 'development' ? `?env=dev` : ''}`,
92 | {
93 | email,
94 | userGroup,
95 | },
96 | {
97 | headers: {
98 | Authorization: accessToken,
99 | }
100 | }
101 | );
102 | result = response.data;
103 | return result;
104 | } catch (error) {
105 | throw error;
106 | }
107 | }
108 |
109 | export const acceptInvitation = async (email: string, password: string, code: string) => {
110 | let result:any = undefined;
111 | try {
112 | const authApiUrl = QClient.getPrivate().authApiUrl;
113 | const projectId = QClient.getConfig().projectId;
114 | const projectEnvironment = QClient.getConfig().env;
115 | const response = await apiClient.post(`${authApiUrl}/${projectId}/accept-invitation${projectEnvironment === 'development' ? `?env=dev` : ''}`,
116 | {
117 | authOption: {
118 | email,
119 | newPassword: password,
120 | invitationCode: code,
121 | }
122 | }
123 | );
124 | result = response.data;
125 | return result;
126 | } catch (error) {
127 | throw error;
128 | }
129 | }
130 |
131 | export const logout = () => {
132 | localStorage.removeItem(SESSION_KEY);
133 | }
--------------------------------------------------------------------------------
/packages/core/src/services/fileStorage.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { QClient } from '../config';
3 | import { SESSION_KEY } from '.';
4 |
5 | // Create an Axios instance with default config
6 | const apiClient = axios.create({
7 | baseURL: QClient.getPrivate().authApiUrl, // Default base URL; can override per request if needed
8 | headers: {
9 | 'Content-Type': 'application/json',
10 | },
11 | });
12 |
13 | const getPresignedUrl = async (accessToken: string) => {
14 | let result: any = undefined;
15 | try {
16 | const apiUrl = QClient.getPrivate().apiUrl;
17 | const projectId = QClient.getConfig().projectId;
18 | const projectEnvironment = QClient.getConfig().env;
19 | const url = `${apiUrl}/${projectId}/file-upload-url/${projectEnvironment === 'development' ? `?env=dev` : ''}`;
20 |
21 | const response = await apiClient.get(url, {
22 | headers: {
23 | Authorization: `${accessToken}`,
24 | },
25 | });
26 |
27 | if (response.status === 200) {
28 | result = response.data;
29 | }
30 | return result;
31 | } catch (error) {
32 | throw error;
33 | }
34 | }
35 |
36 | export const fileUpload = async (file: File, accessToken: string) => {
37 | let result: any = undefined;
38 | try {
39 | const presignedUrl = await getPresignedUrl(accessToken);
40 |
41 | // Set the Content-Type based on the file's MIME type
42 | const headers = {
43 | 'Content-Type': file.type || 'application/octet-stream', // Fallback to generic type if file.type is empty
44 | };
45 |
46 | const response = await apiClient.put(presignedUrl.uploadUrl, file, { headers });
47 |
48 | if (response.status === 200) {
49 | result = {
50 | id: presignedUrl.fileId,
51 | url: presignedUrl.uploadUrl
52 | };
53 | }
54 | return result;
55 | } catch (error) {
56 | console.error("Error uploading file:", error);
57 | throw error;
58 | }
59 | }
60 |
61 | export const create = async (file: File, formData: any, usergroup: string) => {
62 | let result: any = undefined;
63 | const session = JSON.parse(localStorage.getItem(SESSION_KEY)!);
64 | try {
65 | const apiUrl = QClient.getPrivate().apiUrl;
66 | const projectId = QClient.getConfig().projectId;
67 | const projectEnvironment = QClient.getConfig().env;
68 | const url = `${apiUrl}/${projectId}/gql${projectEnvironment === 'development' ? `?env=dev` : ''}`;
69 | const uploadedFileId = fileUpload(file, session?.accessToken);
70 | const response = await apiClient.post(
71 | url,
72 | {
73 | query: `mutation create($input: create${usergroup}Input!) { create${usergroup}(input: $input) { id }}`,
74 | variables: {
75 | input: {
76 | ...formData,
77 | fileField: `${uploadedFileId}`,
78 | },
79 | }
80 | },
81 | {
82 | headers: {
83 | Authorization: `${session?.accessToken}`
84 | }
85 | }
86 | );
87 | if (response.status === 200) {
88 | result = response.data;
89 | }
90 | return result;
91 | } catch (error) {
92 | throw error;
93 | }
94 | }
95 |
96 | export const update = async (file: File, formData: any, usergroup: string) => {
97 | let result: any = undefined;
98 | const session = JSON.parse(localStorage.getItem(SESSION_KEY)!);
99 | try {
100 | const apiUrl = QClient.getPrivate().apiUrl;
101 | const projectId = QClient.getConfig().projectId;
102 | const projectEnvironment = QClient.getConfig().env;
103 | const url = `${apiUrl}/${projectId}/gql${projectEnvironment === 'development' ? `?env=dev` : ''}`;
104 | const uploadedFileId = fileUpload(file, session?.accessToken);
105 | const response = await apiClient.post(
106 | url,
107 | {
108 | query: `mutation update($input: update${usergroup}Input!) { update${usergroup}(input: $input) { id }}`,
109 | variables: {
110 | input: {
111 | ...formData,
112 | fileField: `${uploadedFileId}`,
113 | },
114 | }
115 | },
116 | {
117 | headers: {
118 | Authorization: `${session?.accessToken}`
119 | }
120 | }
121 | );
122 | if (response.status === 200) {
123 | result = response.data;
124 | }
125 | return result;
126 | } catch (error) {
127 | throw error;
128 | }
129 | }
--------------------------------------------------------------------------------
/packages/core/src/services/gqlService.ts:
--------------------------------------------------------------------------------
1 | import { ApolloClient, InMemoryCache, HttpLink, gql, OperationVariables } from '@apollo/client';
2 | import { QClient } from '../config';
3 | import { SESSION_KEY } from '.';
4 | import { parse, print, FieldNode, SelectionSetNode, Kind, DocumentNode, OperationDefinitionNode } from 'graphql';
5 |
6 | // Function to extract allowed fields from selector input
7 | const parseSelectors = (selectors: string): Record => {
8 | const result: Record = {};
9 | const stack: Record[] = [result];
10 |
11 | selectors
12 | .trim()
13 | .split('\n')
14 | .map((line) => line.trim())
15 | .filter((line) => line.length > 0)
16 | .forEach((line) => {
17 | if (line.endsWith('{')) {
18 | const fieldName = line.replace('{', '').trim();
19 | stack[stack.length - 1][fieldName] = {};
20 | stack.push(stack[stack.length - 1][fieldName]);
21 | } else if (line === '}') {
22 | stack.pop();
23 | } else {
24 | stack[stack.length - 1][line] = true;
25 | }
26 | });
27 |
28 | return result;
29 | };
30 |
31 | // Function to filter or modify a SelectionSetNode based on allowed fields
32 | const filterSelectionSet = (selectionSet: SelectionSetNode, allowedFields: Record): SelectionSetNode => {
33 | const filteredSelections = selectionSet.selections
34 | .map((selection) => {
35 | if (selection.kind === Kind.FIELD) {
36 | const field = selection as FieldNode;
37 | const fieldName = field.name.value;
38 |
39 | if (allowedFields[fieldName]) {
40 | // If the field is allowed and has nested fields in allowedFields
41 | if (field.selectionSet && typeof allowedFields[fieldName] === 'object' && Object.keys(allowedFields[fieldName]).length > 0) {
42 | return {
43 | ...field,
44 | selectionSet: filterSelectionSet(field.selectionSet, allowedFields[fieldName]),
45 | };
46 | }
47 | // If the field is allowed and no nested fields are specified, keep it as is (leaf field)
48 | return field;
49 | }
50 | }
51 | return null; // Remove unallowed fields
52 | })
53 | .filter((selection) => selection !== null);
54 |
55 | return {
56 | kind: Kind.SELECTION_SET,
57 | selections: filteredSelections,
58 | } as SelectionSetNode;
59 | };
60 |
61 | // (auth) query function
62 | export const query = async (
63 | baseQuery: string,
64 | variables?: VarsType,
65 | selectors?: string
66 | ): Promise => {
67 | const session = JSON.parse(localStorage.getItem(SESSION_KEY)!);
68 |
69 | const client = new ApolloClient({
70 | link: new HttpLink({
71 | uri: `${QClient.getPrivate().apiUrl}/${QClient.getConfig().projectId}/gql${QClient.getConfig().env === 'development' ? `?env=dev` : ''}`,
72 | headers: {
73 | Authorization: `${session?.accessToken}`,
74 | },
75 | }),
76 | cache: new InMemoryCache({
77 | addTypename: false,
78 | }),
79 | });
80 |
81 | let gqlQueryString = baseQuery;
82 |
83 | if (selectors) {
84 | try {
85 | // Parse the base query into an AST
86 | const ast: DocumentNode = parse(baseQuery);
87 |
88 | // Parse the selectors into a nested object
89 | const allowedFields = parseSelectors(selectors);
90 |
91 | // Modify the AST based on the allowed fields
92 | const modifiedAst: DocumentNode = {
93 | ...ast,
94 | definitions: ast.definitions.map((definition) => {
95 | if (definition.kind === Kind.OPERATION_DEFINITION) {
96 | const opDef = definition as OperationDefinitionNode;
97 |
98 | // Assume the first selection is the main query field (e.g., "listListings")
99 | const mainSelection = opDef.selectionSet.selections[0] as FieldNode;
100 |
101 | if (mainSelection && mainSelection.selectionSet) {
102 | // Filter the selection set based on the allowed fields
103 | const filteredSelectionSet = filterSelectionSet(mainSelection.selectionSet, allowedFields);
104 |
105 | return {
106 | ...opDef,
107 | selectionSet: {
108 | ...opDef.selectionSet,
109 | selections: [
110 | {
111 | ...mainSelection,
112 | selectionSet: filteredSelectionSet,
113 | },
114 | ],
115 | },
116 | };
117 | }
118 | }
119 | return definition;
120 | }),
121 | };
122 |
123 | // Convert the modified AST back to a string
124 | gqlQueryString = print(modifiedAst);
125 | } catch (error) {
126 | console.error('Error parsing GraphQL query or selectors:', error);
127 | throw new Error('Invalid GraphQL query or selectors format.');
128 | }
129 | }
130 |
131 | // Parse the updated query string
132 | const gqlQuery = gql(gqlQueryString);
133 |
134 | // Ensure variables are never undefined
135 | const safeVariables = variables ?? ({} as VarsType);
136 |
137 | try {
138 | const response = await client.query({
139 | query: gqlQuery,
140 | variables: safeVariables,
141 | fetchPolicy: "no-cache",
142 | });
143 |
144 | if (!response.data) {
145 | throw new Error(`Query response data for "${baseQuery}" is null or undefined.`);
146 | }
147 |
148 | // Unwrap the first level of the response.data
149 | return Object.values(response.data)[0] as ResponseType;
150 | } catch (error) {
151 | console.error('Apollo Query Error:', error);
152 | throw error;
153 | }
154 | };
155 |
156 | // (auth) mutate function
157 | export const mutate = async (
158 | baseMutation: string,
159 | variables: VarsType,
160 | ): Promise => {
161 | const session = JSON.parse(localStorage.getItem(SESSION_KEY)!);
162 |
163 | const client = new ApolloClient({
164 | link: new HttpLink({
165 | uri: `${QClient.getPrivate().apiUrl}/${QClient.getConfig().projectId}/gql${QClient.getConfig().env === 'development' ? `?env=dev` : ''}`,
166 | headers: {
167 | Authorization: `${session?.accessToken}`,
168 | },
169 | }),
170 | cache: new InMemoryCache({
171 | addTypename: false,
172 | }),
173 | });
174 |
175 | const mutation = gql(baseMutation);
176 |
177 | try {
178 | const response = await client.mutate({
179 | mutation,
180 | variables,
181 | fetchPolicy: 'no-cache',
182 | });
183 |
184 | if (!response.data) {
185 | throw new Error(`Mutation response data for "${baseMutation}" is null or undefined.`);
186 | }
187 |
188 | // Unwrap the first level of the response.data
189 | return Object.values(response.data)[0] as ResponseType;
190 | } catch (error) {
191 | console.error(`Error during mutation "${baseMutation}":`, error);
192 | throw error;
193 | }
194 | };
195 |
196 | // Public (no-auth) query function
197 | export const publicQuery = async (
198 | baseQuery: string,
199 | variables?: VarsType,
200 | selectors?: string
201 | ): Promise => {
202 | const client = new ApolloClient({
203 | link: new HttpLink({
204 | uri: `${QClient.getPrivate().apiUrl}/${QClient.getConfig().projectId}/public/gql${QClient.getConfig().env === 'development' ? `?env=dev` : ''}`, // Adjust URI for public endpoint
205 | }),
206 | cache: new InMemoryCache({
207 | addTypename: false,
208 | }),
209 | });
210 |
211 | let gqlQueryString = baseQuery;
212 |
213 | if (selectors) {
214 | try {
215 | const ast: DocumentNode = parse(baseQuery);
216 | const allowedFields = parseSelectors(selectors);
217 |
218 | const modifiedAst: DocumentNode = {
219 | ...ast,
220 | definitions: ast.definitions.map((definition) => {
221 | if (definition.kind === Kind.OPERATION_DEFINITION) {
222 | const opDef = definition as OperationDefinitionNode;
223 | const mainSelection = opDef.selectionSet.selections[0] as FieldNode;
224 |
225 | if (mainSelection && mainSelection.selectionSet) {
226 | const filteredSelectionSet = filterSelectionSet(mainSelection.selectionSet, allowedFields);
227 |
228 | return {
229 | ...opDef,
230 | selectionSet: {
231 | ...opDef.selectionSet,
232 | selections: [
233 | {
234 | ...mainSelection,
235 | selectionSet: filteredSelectionSet,
236 | },
237 | ],
238 | },
239 | };
240 | }
241 | }
242 | return definition;
243 | }),
244 | };
245 |
246 | gqlQueryString = print(modifiedAst);
247 | } catch (error) {
248 | console.error('Error parsing GraphQL query or selectors:', error);
249 | throw new Error('Invalid GraphQL query or selectors format.');
250 | }
251 | }
252 |
253 | const gqlQuery = gql(gqlQueryString);
254 | const safeVariables = variables ?? ({} as VarsType);
255 |
256 | try {
257 | const response = await client.query({
258 | query: gqlQuery,
259 | variables: safeVariables,
260 | fetchPolicy: 'no-cache',
261 | });
262 |
263 | if (!response.data) {
264 | throw new Error(`Query response data for "${baseQuery}" is null or undefined.`);
265 | }
266 |
267 | // Unwrap the first level of the response.data
268 | return Object.values(response.data)[0] as ResponseType;
269 | } catch (error) {
270 | console.error('Apollo Public Query Error:', error);
271 | throw error;
272 | }
273 | };
274 |
275 | // Public (no-auth) mutation function
276 | export const publicMutate = async (
277 | baseMutation: string,
278 | variables: VarsType
279 | ): Promise => {
280 | const client = new ApolloClient({
281 | link: new HttpLink({
282 | uri: `${QClient.getPrivate().apiUrl}/${QClient.getConfig().projectId}/public/gql${QClient.getConfig().env === 'development' ? `?env=dev` : ''}`, // Adjust URI for public endpoint
283 | }),
284 | cache: new InMemoryCache({
285 | addTypename: false,
286 | }),
287 | });
288 |
289 | const mutation = gql(baseMutation);
290 |
291 | try {
292 | const response = await client.mutate({
293 | mutation,
294 | variables,
295 | });
296 |
297 | if (!response.data) {
298 | throw new Error(`Public mutation response data for "${baseMutation}" is null or undefined.`);
299 | }
300 |
301 | // Unwrap the first level of the response.data
302 | return Object.values(response.data)[0] as ResponseType;
303 | } catch (error) {
304 | console.error(`Error during public mutation "${baseMutation}":`, error);
305 | throw error;
306 | }
307 | };
--------------------------------------------------------------------------------
/packages/core/src/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './authService';
2 | export * from './gqlService';
3 | export * from './fileStorage';
4 |
5 | // export constant
6 | export const SESSION_KEY = 'qclient-session';
--------------------------------------------------------------------------------
/packages/core/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export type Metadata = {
2 | requiredFields: string[];
3 | optionalFields: string[];
4 | protectedFields: string[];
5 | deprecatedFields: string[];
6 | fieldTypes: Record;
7 | enumFields: Record;
8 | };
9 |
10 | export function extractMetadata>(schema: T): Metadata {
11 | const requiredFields: string[] = [];
12 | const optionalFields: string[] = [];
13 | const protectedFields: string[] = [];
14 | const deprecatedFields: string[] = [];
15 | const fieldTypes: Record = {};
16 | const enumFields: Record = {};
17 |
18 | for (const [key, value] of Object.entries(schema)) {
19 | const valueType = typeof value === "string" ? value : typeof value;
20 |
21 | // Handle protected and deprecated fields
22 | if (valueType.includes("protected")) {
23 | protectedFields.push(key);
24 | }
25 | if (valueType.includes("deprecated")) {
26 | deprecatedFields.push(key);
27 | }
28 |
29 | // Detect enum fields (like 'status')
30 | if (Array.isArray(value)) {
31 | enumFields[key] = value;
32 | }
33 |
34 | // Store the field type
35 | fieldTypes[key] = valueType;
36 |
37 | // Classify fields as required or optional
38 | if (key.endsWith("?")) {
39 | optionalFields.push(key.replace("?", ""));
40 | } else {
41 | requiredFields.push(key);
42 | }
43 | }
44 |
45 | return { requiredFields, optionalFields, protectedFields, deprecatedFields, fieldTypes, enumFields };
46 | }
47 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "types": ["node"],
6 | "paths": {
7 | "*": ["node_modules/*", "src/types/*"]
8 | }
9 | },
10 | "include": ["src"],
11 | "exclude": ["node_modules", "dist"]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/ui-react/README.md:
--------------------------------------------------------------------------------
1 | # @quorini/ui-react
2 |
3 | ## Description
4 | This package leverages a range of functions and React hooks designed to seamlessly integrate with your React application configured using the [@quorini/core](https://www.npmjs.com/package/@quorini/core) package in conjunction with the backend services provided by [quorini.app](https://quorini.app/).
5 |
6 | ## Installing
7 | To install this package, simply type add or install `@quorini/ui-react` using your favorite package manager:
8 |
9 | ```ts
10 | npm install @quorini/ui-react
11 |
12 | or
13 | yarn add @quorini/ui-react
14 |
15 | or
16 | pnpm add @quorini/ui-react
17 | ```
18 |
19 | ## Getting Started
20 |
21 |
22 | ```tsx
23 | // ES5 example
24 | const { QAuth, QGql } = require("@quorini/ui-react");
25 | ```
26 | ```tsx
27 | // ES6+ example
28 | import { QAuth, QGql } from "@quorini/ui-react";
29 | ```
30 |
31 | ## Usage
32 |
33 | For more information regarding configuration, visit [github repository readme](../../README.md)
34 |
35 | ### 1. Authenticator
36 | Before using the `@quorini/ui-react` package, ensure that your project is configured with the `@quorini/core` package.
37 |
38 | Additionally, all providers must wrap the root DOM of your React application, with `QAuth.Provider` being the first provider in the hierarchy.
39 |
40 | ```tsx
41 | /* src/index.tsx */
42 | ...
43 | import { QClient } from "@quorini/core";
44 | import { QAuth } from "@quorini/ui-react";
45 |
46 | QClient.configure(...)
47 |
48 | const root = ReactDOM.createRoot(
49 | document.getElementById('root') as HTMLElement
50 | )
51 |
52 | root.render(
53 |
54 | ...
55 |
56 |
57 |
58 | ...
59 |
60 | )
61 | ```
62 |
63 | `useAuth` Hook that can be used to access, modify, and update QAuth’s auth state.
64 |
65 | ```tsx
66 | /* src/app.tsx */
67 | ...
68 | import { useAuth } from '@quorini/ui-react'
69 |
70 | function App() {
71 | const { session, logout } = useAuth()
72 |
73 | return (
74 |
75 |
{session.username}
76 |
77 |
78 | )
79 | }
80 |
81 | ```
82 |
83 | ### **Option 1: Auth page as a single React UI component**
84 |
85 | - **Add Authenticator**\
86 | 👉 Replace `CreateUserSignUpSchema` with actual mutation from the generated file `./quorini-mutations.ts` for the user profile creation form.\
87 | 👉 Keep in mind that `CreateUserSignUpSchema` is only available for user groups with enabled sing-up. Change this in project configuration.
88 |
89 | The `QAuth.Provider` guarantees that the `useAuth` hook is available throughout your application.
90 |
91 | ```tsx
92 | // index.tsx
93 | ...
94 | import { QAuth } from '@quorini/ui-react'
95 | import '@quorini/ui-react/styles.css'
96 |
97 | import { CreateUserMetadata } from './quorini-mutations.ts'
98 |
99 | const root = ReactDOM.createRoot(
100 | document.getElementById('root') as HTMLElement
101 | )
102 |
103 | root.render(
104 |
105 | ...
106 |
110 |
111 |
112 | ...
113 |
114 | )
115 | ```
116 |
117 | ### **Option 2 (custom UI): Auth module (for custom auth components implementation)**
118 |
119 | - **Add Auth Api endpoints**\
120 | You can use the api endpoints directly.
121 | To use them, you need to import auth APIs from core package.
122 |
123 | ```tsx
124 | // auth.tsx
125 | ...
126 | import { login, signup, sendInvitation, refreshAuthToken } from '@quorini/core'
127 |
128 | const Auth = () => {
129 | const submitLogin = () => {
130 | const user = await login(username, password)
131 | }
132 |
133 | render() {
134 | return (
135 |
140 | )
141 | }
142 | }
143 | ```
144 |
145 | #### Objects & Operations built-in useAuth hook
146 | - session
147 | - login
148 | - logout
149 | - signup
150 | - verifyEmail
151 |
152 | In more detail refer to [this doc](#upcoming).
153 |
154 | ### 2. GraphQL API Handler
155 | The `QGql.Provider` should wrap the root of the React DOM and enable the usage of its associated hook functions.
156 |
157 | ```tsx
158 | /* src/index.tsx */
159 | ...
160 | import { QClient } from "@quorini/core";
161 | import { QAuth, QGql } from "@quorini/ui-react";
162 |
163 | QClient.configure(...)
164 |
165 | const root = ReactDOM.createRoot(
166 | document.getElementById('root') as HTMLElement
167 | )
168 |
169 | root.render(
170 |
171 | ...
172 |
173 |
174 |
175 |
176 |
177 | ...
178 |
179 | )
180 | ```
181 |
182 | ```tsx
183 | /* src/app.tsx */
184 | ...
185 | import { useQGql } from '@quorini/core'
186 | import { AdminFilterInput, listAdmins, listAdminsResponse, listAdminsVars } from './src/quorini-queries'
187 | import { createAdmin, createAdminResponse, createAdminVars } from './src/quorini-mutations'
188 |
189 | function App() {
190 | const { query, mutate } = useQGql()
191 |
192 | // queries examples
193 | const fetchAdmins = async () => {
194 | try {
195 | const response: listAdminsResponse = await query(listAdmins)
196 | console.log('Fetched Admins:', response)
197 | } catch (error) {
198 | console.error('Error fetching messages:', error)
199 | }
200 | }
201 | const fetchFilterAdmins = async () => {
202 | try {
203 | const filter = {
204 | fullName: { eq: 'SPECIFIC_NAME' }
205 | } as AdminFilterInput
206 |
207 | const response: listAdminsResponse = await query(listAdmins, { filter })
208 | console.log('Fetched filterd Admins:', response)
209 | } catch (error) {
210 | console.error('Error fetching messages:', error)
211 | }
212 | }
213 | const fetchFilterAdminsWithSelector = async () => {
214 | try {
215 | const filter = {
216 | fullName: { eq: 'SPECIFIC_NAME' }
217 | } as AdminFilterInput
218 |
219 | const response: listAdminsResponse = await query(listAdmins, { filter }, 'email fullName')
220 | console.log('Fetched specific Admin with selector:', response)
221 | } catch (error) {
222 | console.error('Error fetching messages:', error)
223 | }
224 | }
225 |
226 | // mutation example
227 | const createAdmin = async () => {
228 | try {
229 | const input = {
230 | fullName: "SPECIFIC_NAME",
231 | email: "EMAIL_ADDRESS"
232 | }
233 | const response: createAdminResponse = await mutate(createAdmin, { input })
234 | console.log("mutation response", response);
235 | } catch (error) {
236 | console.error('Error creation new admin:', error)
237 | }
238 | }
239 |
240 | ...
241 | }
242 | ```
243 |
244 | #### Operations built-in useQGql hook
245 | - query
246 | - mutate
247 | - subscriber (upcoming)
248 |
--------------------------------------------------------------------------------
/packages/ui-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@quorini/ui-react",
3 | "version": "1.1.65",
4 | "main": "dist/index.cjs.js",
5 | "module": "dist/index.esm.js",
6 | "types": "dist/index.d.ts",
7 | "exports": {
8 | "require": {
9 | "types": "./dist/index.d.ts",
10 | "default": "./dist/index.cjs.js"
11 | },
12 | "import": {
13 | "types": "./dist/index.d.ts",
14 | "default": "./dist/index.esm.js"
15 | }
16 | },
17 | "keywords": [
18 | "quorini",
19 | "react",
20 | "typescript",
21 | "graphQL",
22 | "core",
23 | "ui-react"
24 | ],
25 | "scripts": {
26 | "build": "rollup -c"
27 | },
28 | "peerDependencies": {
29 | "react": ">=17.0.0",
30 | "react-dom": ">=17.0.0"
31 | },
32 | "dependencies": {
33 | "@apollo/client": "^3.11.10",
34 | "@quorini/core": "^1.1.65",
35 | "antd": "^5.21.6",
36 | "axios": "^1.7.7",
37 | "graphql": "^16.9.0",
38 | "path-browserify": "^1.0.1",
39 | "react": ">=17.0.0",
40 | "styled-components": "^6.1.13"
41 | },
42 | "devDependencies": {
43 | "@types/axios": "^0.14.4",
44 | "@types/graphql": "^14.5.0",
45 | "@types/path-browserify": "^1.0.3",
46 | "@types/react": "^18.3.12",
47 | "@types/styled-components": "^5.1.34",
48 | "rollup": "^2.60.0",
49 | "rollup-plugin-dts": "^6.1.1",
50 | "typescript": "^4.5.0"
51 | },
52 | "publishConfig": {
53 | "access": "public"
54 | },
55 | "repository": {
56 | "type": "git",
57 | "url": "https://github.com/quorini/quorini-js-sdk"
58 | },
59 | "author": "ernst1202",
60 | "license": "MIT",
61 | "gitHead": "76424c713234e1e82c7c9605d9d7fc4015881659"
62 | }
63 |
--------------------------------------------------------------------------------
/packages/ui-react/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2';
2 | import dts from 'rollup-plugin-dts';
3 |
4 | export default [
5 | {
6 | input: './src/index.ts',
7 | output: [
8 | {
9 | file: 'dist/index.cjs.js',
10 | format: 'cjs',
11 | sourcemap: true,
12 | },
13 | {
14 | file: 'dist/index.esm.js',
15 | format: 'esm',
16 | sourcemap: true,
17 | },
18 | ],
19 | plugins: [
20 | typescript({
21 | tsconfig: './tsconfig.json'
22 | })
23 | ],
24 | external: ['react', 'react-dom', '@quorini/core']
25 | },
26 | {
27 | input: 'src/index.ts', // Path to your entry declaration file
28 | output: [{ file: 'dist/index.d.ts', format: 'esm' }],
29 | plugins: [dts()],
30 | },
31 | ];
32 |
--------------------------------------------------------------------------------
/packages/ui-react/src/components/Auth/Login.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import styled from "styled-components"
3 | import { Button, Input, Form, Alert, Flex, Checkbox } from "antd"
4 | import { LockOutlined, UserOutlined } from "@ant-design/icons"
5 | import { useAuth } from '../../hooks';
6 |
7 | interface LoginProps {
8 | onLoginSuccess: () => void;
9 | onSignupClick?: () => void;
10 | selfSignup?: boolean;
11 | }
12 |
13 | const Login: React.FC = ({ onLoginSuccess, onSignupClick, selfSignup }) => {
14 | const { login } = useAuth();
15 | const [username, setUsername] = useState('');
16 | const [password, setPassword] = useState('');
17 | const [error, setError] = useState(null);
18 | const [isLoading, setIsLoading] = useState(false);
19 |
20 | const handleLogin = () => {
21 | setIsLoading(true);
22 | login(username, password)
23 | .then(() => {
24 | setIsLoading(false);
25 | onLoginSuccess();
26 | })
27 | .catch((err) => {
28 | setIsLoading(false);
29 | setError("Invalid username or password");
30 | });
31 | };
32 |
33 | return (
34 |
35 |
37 | }
39 | placeholder="Email address..."
40 | title="Email address..."
41 | onChange={(e) => setUsername(e.target.value)}
42 | size="large"
43 | autoFocus
44 | />
45 |
46 |
47 |
61 | }
63 | placeholder="Password..."
64 | title="Password..."
65 | onChange={(e) => setPassword(e.target.value)}
66 | size="large"
67 | />
68 |
69 |
70 | {error && (
71 |
72 |
73 |
74 | )}
75 |
76 |
77 |
78 |
79 | Remember me
80 |
81 | Forgot password
82 |
83 |
84 |
85 |
86 |
89 | {selfSignup && (<>or Sign up now!>)}
90 |
91 |
92 |
93 | );
94 | };
95 |
96 | const FormWrapper = styled.div`
97 | display: flex;
98 | justify-content: center;
99 | height: 100vh;
100 | align-items: center;
101 | `;
102 |
103 | export default Login;
104 |
--------------------------------------------------------------------------------
/packages/ui-react/src/components/Auth/Signup.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useAuth } from '../../hooks';
3 | import styled from 'styled-components';
4 | import { Alert, Button, Form, Input } from 'antd';
5 | import { LockOutlined, UserOutlined, GiftOutlined } from "@ant-design/icons"
6 | import { MetaData } from '../../utils';
7 |
8 | interface SignupProps {
9 | onSignupSuccess: () => void;
10 | onLoginClick: () => void;
11 | onAcceptSuccess: () => void;
12 | formFields?: MetaData[];
13 | usergroup?: string;
14 | }
15 |
16 | const Signup: React.FC = ({ onSignupSuccess, onLoginClick, onAcceptSuccess, formFields, usergroup }) => {
17 | const { signup, acceptInvitation } = useAuth();
18 | const [error, setError] = useState(null);
19 | const [isLoading, setIsLoading] = useState(false);
20 | const [form] = Form.useForm();
21 |
22 | const pathname = window.location.pathname;
23 | if (pathname.includes("set-password")) {
24 | // Get the full URL
25 | const url = new URL(window.location.href);
26 |
27 | // Extract the query string
28 | const queryString = url.search;
29 |
30 | // Extract the fragment (part after #)
31 | const fragment = url.hash;
32 |
33 | // Parse the query string to get the parameters
34 | const params = new URLSearchParams(queryString);
35 |
36 | // Get the code and invitationEmail values
37 | let code = params.get("code") || ""; // Fallback to empty string if code is null
38 | const invitationEmail = params.get("email") || ""; // Fallback to empty string if email is null
39 |
40 | // Decode the code value to handle URL-encoded characters
41 | code = decodeURIComponent(code);
42 |
43 | // If there's a fragment, append it to the code
44 | if (fragment) {
45 | code += fragment;
46 | }
47 |
48 | const handleAcceptInvitation = (values: Record) => {
49 | setIsLoading(true);
50 | const { password } = values;
51 | if (!invitationEmail || invitationEmail.length === 0) {
52 | setError("email address is not valid!");
53 | setIsLoading(false);
54 | return;
55 | }
56 | if (!password || password.length === 0) {
57 | setError("password is not valid!");
58 | setIsLoading(false);
59 | return;
60 | }
61 | if (!code || code.length === 0) {
62 | setError("invitation code is not valid!");
63 | setIsLoading(false);
64 | return;
65 | }
66 | acceptInvitation(invitationEmail, password, code)
67 | .then(() => {
68 | setIsLoading(false);
69 | onAcceptSuccess();
70 | window.location.href = `${window.location.origin}/`;
71 | })
72 | .catch(() => {
73 | setIsLoading(false);
74 | setError("sign up err. try again");
75 | });
76 | }
77 |
78 | if (invitationEmail) {
79 | return (
80 |
81 |
83 | }
85 | size="large"
86 | style={{ width: "300px" }}
87 | defaultValue={invitationEmail}
88 | disabled
89 | />
90 |
91 |
92 |
96 | }
98 | placeholder="Inviation Code"
99 | size="large"
100 | defaultValue={code || ''}
101 | disabled
102 | />
103 |
104 |
105 |
123 | }
125 | placeholder="Password"
126 | size="large"
127 | />
128 |
129 |
130 | {error && (
131 |
132 |
133 |
134 | )}
135 |
136 |
137 |
140 | or Log in
141 |
142 |
143 |
144 | )
145 | }
146 | }
147 |
148 | const handleSignup = (values: Record) => {
149 | setIsLoading(true);
150 | const {username, password, confirmPassword, ...rest} = values;
151 |
152 | if (password !== confirmPassword) {
153 | setError("Password not matched!");
154 | setIsLoading(false);
155 | return;
156 | }
157 | signup(username, password, "", rest, usergroup || "")
158 | .then(() => {
159 | setIsLoading(false);
160 | onSignupSuccess();
161 | })
162 | .catch((err) => {
163 | setIsLoading(false);
164 | setError("sign up err. try again");
165 | });
166 | };
167 |
168 | return (
169 |
170 |
172 | }
174 | placeholder="Email address..."
175 | title="Email address..."
176 | size="large"
177 | style={{ width: "300px" }}
178 | autoFocus
179 | />
180 |
181 |
182 |
200 | }
202 | placeholder="Password..."
203 | title="Password..."
204 | size="large"
205 | />
206 |
207 |
208 |
226 | }
228 | placeholder="Confirm Password..."
229 | title="Confirm Password..."
230 | size="large"
231 | />
232 |
233 | {formFields?.map((field) => (
234 |
245 |
246 |
247 | ))}
248 |
249 | {error && (
250 |
251 |
252 |
253 | )}
254 |
255 |
256 |
259 | or Log in
260 |
261 |
262 |
263 | );
264 | };
265 |
266 | const FormWrapper = styled.div`
267 | display: flex;
268 | justify-content: center;
269 | height: 100vh;
270 | align-items: center;
271 | `;
272 |
273 | export default Signup;
274 |
--------------------------------------------------------------------------------
/packages/ui-react/src/components/Auth/VerifyEmail.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useAuth } from '../../hooks';
3 | import { Alert, Button, Flex, Form, Input, Typography } from 'antd';
4 | import styled from 'styled-components';
5 | interface VerifyEmailProps {
6 | onVerifySuccess: () => void;
7 | };
8 |
9 | const { Title } = Typography;
10 |
11 | const VerifyEmail: React.FC = ({ onVerifySuccess }) => {
12 | const { session, verifyEmail } = useAuth();
13 | const [verificationCode, setVerificationCode] = useState('');
14 | const [error, setError] = useState(null);
15 | const [isLoading, setIsLoading] = useState(false);
16 |
17 | const handleSubmit = async () => {
18 | setIsLoading(true);
19 | verifyEmail(verificationCode, session.username)
20 | .then(() => {
21 | setIsLoading(false);
22 | onVerifySuccess();
23 | })
24 | .catch((err) => {
25 | setIsLoading(false);
26 | setError("Verify Email Error. Try again later...");
27 | });
28 | };
29 |
30 | return (
31 |
32 |
43 | setVerificationCode(e.target.value)}
48 | size="large"
49 | autoFocus
50 | />
51 |
52 |
53 | {error && (
54 |
55 |
56 |
57 | )}
58 |
59 |
60 |
67 |
74 |
75 |
76 |
77 | );
78 | };
79 |
80 | const FormWrapper = styled.div`
81 | display: flex;
82 | justify-content: center;
83 | height: 100vh;
84 | align-items: center;
85 | `;
86 |
87 | export default VerifyEmail;
88 |
--------------------------------------------------------------------------------
/packages/ui-react/src/components/Auth/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Login } from './Login';
2 | export { default as Signup } from './Signup';
3 | export { default as VerifyEmail } from './VerifyEmail';
4 |
--------------------------------------------------------------------------------
/packages/ui-react/src/components/Theme/ThemeProvider.tsx:
--------------------------------------------------------------------------------
1 | // Theme Provider
--------------------------------------------------------------------------------
/packages/ui-react/src/components/Theme/ThemeProvider.types.ts:
--------------------------------------------------------------------------------
1 | // Theme Provider Types
--------------------------------------------------------------------------------
/packages/ui-react/src/components/Theme/index.ts:
--------------------------------------------------------------------------------
1 | // Theme Provider Index
--------------------------------------------------------------------------------
/packages/ui-react/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Auth';
2 | // ... export others
--------------------------------------------------------------------------------
/packages/ui-react/src/components/styles/index.ts:
--------------------------------------------------------------------------------
1 | import styled, { css } from "styled-components"
2 |
3 | const Colors = {
4 | // Main background color – Slightly blueish-greenish almost white color | Cold white very very light gray
5 | background: "#eef2f6",
6 | transparentBackground: "rgba(238, 242, 246, 0.7)",
7 | transparentWhiteBackground: "rgba(255, 255, 255, 0.9)",
8 | transparentDarkFrostyBackground: "rgba(0, 0, 0, 0.25)",
9 | // Primary color – Dark gray almosst black
10 | primary: "#2c2c2c",
11 | primaryRed: "#d20101",
12 | // Even darker on hover
13 | primaryHover: "#1c1c1c",
14 | //
15 | grayDark: "#494b4d",
16 | grayNormal: "rgba(0, 10, 20, 0.35)",
17 | grayLight: "rgba(0, 5, 10, 0.06)",
18 | }
19 |
20 | const StyleHelpers = {
21 | boldBoxShadow: `0px 0px 15px 0px ${Colors.transparentDarkFrostyBackground}`,
22 | lightBoxShadow: `0px 0px 5px 0px ${Colors.transparentDarkFrostyBackground}`,
23 | staticBoxShadow: `0px 0px 2px 0px ${Colors.transparentDarkFrostyBackground}`,
24 | whiteGlowBoxShadow: `0 0 10px 3px ${Colors.transparentWhiteBackground}`,
25 | accentGlowShadow: `0 0 18px -8px ${Colors.primaryHover}`,
26 | blur: "blur(2px)",
27 | //
28 | radiusSmall: "10px",
29 | radiusMedium: "20px",
30 | //
31 | iconSize: "15px",
32 | largeIconSize: "25px",
33 | }
34 |
35 | const Spaces = {
36 | small: "5px",
37 | normal: "10px",
38 | medium: "15px",
39 | large: "20px",
40 | xLarge: "25px",
41 | }
42 |
43 | const Centered = styled.div<{ vertical?: boolean }>`
44 | display: flex;
45 | justify-content: center;
46 | align-items: center;
47 | width: 100%;
48 | flex-direction: ${(props) => (props.vertical ? "column" : "unset")};
49 | gap: ${Spaces.large};
50 | `
51 |
52 | const PageWrapper = styled.div`
53 | display: flex;
54 | flex-direction: row;
55 | justify-content: center;
56 | width: 100%;
57 | min-height: 100vh;
58 | background-color: ${Colors.background};
59 | /* padding: 10px 5px; */
60 | `
61 |
62 | const HalfContainer = styled.div<{ fullHeight?: boolean }>`
63 | flex: none;
64 | width: 50%;
65 | height: ${(props) => (props.fullHeight ? "100vh" : "unset")};
66 | overflow-y: scroll;
67 |
68 | box-shadow: ${StyleHelpers.boldBoxShadow};
69 |
70 | &:last-child {
71 | box-shadow: unset;
72 | padding: 0 0 0 ${Spaces.large};
73 | }
74 |
75 | &:last-child:first-child {
76 | width: 100vw;
77 | padding: 0;
78 | }
79 |
80 | .ant-tabs-nav {
81 | background-color: white;
82 | box-shadow: ${StyleHelpers.lightBoxShadow};
83 | z-index: 2;
84 | }
85 | `
86 |
87 | const OneThirdContainer = styled(HalfContainer)`
88 | width: 30%;
89 | // min-width: 420px;
90 | `
91 |
92 | const TwoThirdContainer = styled(HalfContainer)`
93 | width: 70%;
94 | // min-width: 980px;
95 | `
96 |
97 | const ItemWithFadeInAnimation = styled.div<{ reversed?: boolean }>`
98 | opacity: 0; /* start with opacity set to 0 */
99 | animation-name: fade-in; /* specify the animation */
100 | animation-duration: 1s; /* specify the duration */
101 | animation-fill-mode: forwards; /* keep the last frame of the animation */
102 | @keyframes fade-in {
103 | from {
104 | opacity: 0;
105 | position: relative;
106 | // 40px is general for any list items
107 | // -20px is unique for NewAuthType component
108 | left: ${(props) => (props.reversed ? "-20px" : "40px")};
109 | }
110 | to {
111 | opacity: 1;
112 | position: relative;
113 | left: 0px;
114 | }
115 | }
116 | `
117 |
118 | // Define mixins
119 | const ScrollableBlock = css`
120 | overflow-y: scroll;
121 | overscroll-behavior: contain;
122 |
123 | &::before {
124 | content: "";
125 | position: absolute;
126 | top: 0;
127 | left: 0;
128 | z-index: -1;
129 | width: 100%;
130 | height: 30px;
131 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
132 | }
133 |
134 | &::after {
135 | content: "";
136 | position: absolute;
137 | bottom: 0;
138 | left: 0;
139 | z-index: -1;
140 | width: 100%;
141 | height: 30px;
142 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
143 | }
144 | `
145 |
146 | export {
147 | Colors,
148 | Centered,
149 | PageWrapper,
150 | HalfContainer,
151 | OneThirdContainer,
152 | TwoThirdContainer,
153 | ItemWithFadeInAnimation,
154 | StyleHelpers,
155 | Spaces,
156 | ScrollableBlock,
157 | }
158 |
--------------------------------------------------------------------------------
/packages/ui-react/src/context/Auth/QAuth.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useState, useEffect } from 'react';
2 | import { AuthContextType, AuthProviderProps, User } from './QAuth.types';
3 | import { SESSION_KEY } from '@quorini/core';
4 | import * as AuthService from '@quorini/core';
5 | import { Login, Signup, VerifyEmail } from '../../components/Auth';
6 | import { parseSchemaToFormFields } from '../../utils';
7 |
8 | const AuthContext = createContext(undefined);
9 |
10 | interface QAuthProviderProps extends AuthProviderProps {
11 | LoginComponent?: React.ComponentType<{ onLoginSuccess: () => void }>;
12 | SignupComponent?: React.ComponentType<{ onSignupSuccess: () => void, }>;
13 | VerifyEmailComponent?: React.ComponentType<{ onVerifySuccess: () => void }>;
14 | signUpFormInputType?: Record,
15 | usergroup?: string,
16 | noLayout?: boolean,
17 | }
18 |
19 | const QAuthProvider: React.FC = ({
20 | children,
21 | LoginComponent = Login,
22 | SignupComponent = Signup,
23 | VerifyEmailComponent = VerifyEmail,
24 | signUpFormInputType,
25 | usergroup,
26 | noLayout = false,
27 | }) => {
28 | const [authStep, setAuthStep] = useState<'login' | 'signup' | 'verifyEmail' | 'success'>('login');
29 | const [session, setSession] = useState(null);
30 |
31 | useEffect(() => {
32 | const pathname = window.location.pathname;
33 | if (pathname.includes("set-password")) {
34 | localStorage.removeItem(SESSION_KEY)
35 | setAuthStep('signup');
36 | } else {
37 | const session = JSON.parse(localStorage.getItem(SESSION_KEY)!);
38 | if (session) {
39 | setSession(session);
40 | }
41 | }
42 | }, []);
43 |
44 | const login = async (username: string, password: string) => {
45 | try {
46 | const sessionData = await AuthService.login(username, password);
47 | localStorage.setItem(SESSION_KEY, JSON.stringify({...sessionData, username}));
48 | setSession({...sessionData, username});
49 | } catch (error) {
50 | setAuthStep('login');
51 | throw error;
52 | }
53 | };
54 |
55 | const signup = async (username: string, password: string, code: string, signupFormData: any, usergroup: string) => {
56 | try {
57 | await AuthService.signup(username, password, code, signupFormData, usergroup);
58 | } catch (error) {
59 | setAuthStep('signup');
60 | throw error;
61 | }
62 | };
63 |
64 | const verifyEmail = async (verificationCode: string, username: string) => {
65 | try {
66 | await AuthService.verifyEmail(verificationCode, username);
67 | } catch (error) {
68 | setAuthStep('verifyEmail');
69 | throw error;
70 | }
71 | };
72 |
73 | const refreshAuthToken = async () => {
74 | try {
75 | const updatedSession = await AuthService.refreshAuthToken(session.refreshToken);
76 | localStorage.setItem(SESSION_KEY, JSON.stringify({...updatedSession, username: session.username}));
77 | } catch (error) {
78 | throw error;
79 | }
80 | }
81 |
82 | const sendInvitation = async (email: string, usergroup: string) => {
83 | try {
84 | const session = JSON.parse(localStorage.getItem(SESSION_KEY)!);
85 | await AuthService.sendInvitation(email, usergroup, session.accessToken);
86 | } catch (error) {
87 | throw error;
88 | }
89 | }
90 |
91 | const acceptInvitation = async (email: string, password: string, code: string) => {
92 | try {
93 | await AuthService.acceptInvitation(email, password, code);
94 | } catch (error) {
95 | throw error;
96 | }
97 | }
98 |
99 | const logout = () => {
100 | localStorage.removeItem(SESSION_KEY);
101 | setAuthStep('login');
102 | };
103 |
104 | const renderAuthComponent = () => {
105 | if (noLayout || (session && session.accessToken)) {
106 | return children;
107 | }
108 |
109 | const signupFormFields = signUpFormInputType ? parseSchemaToFormFields(signUpFormInputType) : undefined;
110 |
111 | switch (authStep) {
112 | case 'signup':
113 | return (
114 | setAuthStep('verifyEmail')}
118 | onAcceptSuccess={() => setAuthStep('login')}
119 | onLoginClick={() => setAuthStep('login')}
120 | />
121 | );
122 | case 'verifyEmail':
123 | return (
124 | setAuthStep('login')} />);
125 | default:
126 | return (
127 | setAuthStep('success')}
129 | onSignupClick={() => setAuthStep('signup')}
130 | selfSignup={!!signUpFormInputType}
131 | />
132 | );
133 | }
134 | };
135 |
136 | return (
137 |
149 | {renderAuthComponent()}
150 |
151 | );
152 | };
153 |
154 | // Export QAuth as an object with Provider property
155 | const QAuth = {
156 | Provider: QAuthProvider,
157 | };
158 |
159 | export { QAuth, AuthContext };
160 |
--------------------------------------------------------------------------------
/packages/ui-react/src/context/Auth/QAuth.types.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export interface AuthProviderProps {
4 | children: React.ReactNode;
5 | LoginComponent?: React.ComponentType<{ onLoginSuccess: () => void }>;
6 | SignupComponent?: React.ComponentType<{ onSignupSuccess: () => void }>;
7 | VerifyEmailComponent?: React.ComponentType<{ onVerifySuccess: () => void }>;
8 | }
9 |
10 | export interface AuthContextType {
11 | session: any | null;
12 | login: (username: string, password: string) => Promise;
13 | signup: (username: string, password: string, code: string, formData: any, usergroup: string) => Promise;
14 | logout: () => void;
15 | verifyEmail: (verificationCode: string, username: string) => Promise;
16 | sendInvitation: (email: string, usergroup: string) => Promise;
17 | acceptInvitation: (email: string, password: string, code: string) => Promise;
18 | refreshAuthToken: () => Promise;
19 | }
20 |
21 | export interface User {
22 | username: string;
23 | accessToken?: any;
24 | refreshToken?: any;
25 | }
--------------------------------------------------------------------------------
/packages/ui-react/src/context/Auth/index.ts:
--------------------------------------------------------------------------------
1 | export * from './QAuth';
2 | export * from './QAuth.types';
--------------------------------------------------------------------------------
/packages/ui-react/src/context/Gql/QGql.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, ReactNode } from 'react';
2 | import * as GqlService from '@quorini/core';
3 | import { QGqlContextType } from './QGql.types';
4 |
5 | // Create a context for GraphQL operations
6 | const QGqlContext = createContext(undefined);
7 |
8 | // Provider component to wrap your app
9 | export const QGqlProvider = ({ children }: { children: ReactNode }) => (
10 |
11 | {children}
12 |
13 | );
14 |
15 | const QGql = {
16 | Provider: QGqlProvider,
17 | };
18 |
19 | export { QGql, QGqlContext };
20 |
--------------------------------------------------------------------------------
/packages/ui-react/src/context/Gql/QGql.types.ts:
--------------------------------------------------------------------------------
1 | import { OperationVariables } from "@apollo/client";
2 |
3 | export interface ResponseType {
4 | [key: string]: any;
5 | }
6 |
7 | export interface QGqlContextType {
8 | query(
9 | operationName: string,
10 | variables?: VarsType,
11 | selectors?: string
12 | ): Promise;
13 | mutate(
14 | operationName: string,
15 | variables: VarsType,
16 | ): Promise;
17 | }
18 |
--------------------------------------------------------------------------------
/packages/ui-react/src/context/Gql/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./QGql";
2 | export * from "./QGql.types";
3 |
--------------------------------------------------------------------------------
/packages/ui-react/src/context/Theme/ThemeContext.tsx:
--------------------------------------------------------------------------------
1 | // Theme context
--------------------------------------------------------------------------------
/packages/ui-react/src/context/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Auth";
2 | export * from "./Gql";
3 | // ... export others
--------------------------------------------------------------------------------
/packages/ui-react/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { useAuth } from './useAuth';
2 | export { useQGql } from './useGql';
--------------------------------------------------------------------------------
/packages/ui-react/src/hooks/useAuth.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { AuthContext, AuthContextType } from '../context';
3 |
4 | export const useAuth = (): AuthContextType => {
5 | const context = useContext(AuthContext);
6 | if (!context) {
7 | throw new Error('useAuth must be used within a QAuth.Provider');
8 | }
9 | return context;
10 | };
11 |
--------------------------------------------------------------------------------
/packages/ui-react/src/hooks/useGql.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from "react";
2 | import { QGqlContextType, QGqlContext } from "../context";
3 |
4 | export const useQGql = (): QGqlContextType => {
5 | const context = useContext(QGqlContext);
6 | if (!context) {
7 | throw new Error('useQGql must be used within a QGqlProvider');
8 | }
9 | return context;
10 | };
--------------------------------------------------------------------------------
/packages/ui-react/src/hooks/useTheme.ts:
--------------------------------------------------------------------------------
1 | // theme hook
--------------------------------------------------------------------------------
/packages/ui-react/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './components';
2 | export * from './context';
3 | export * from './hooks';
4 | export * from './themes';
--------------------------------------------------------------------------------
/packages/ui-react/src/themes/defaultTheme.ts:
--------------------------------------------------------------------------------
1 | // default theme
--------------------------------------------------------------------------------
/packages/ui-react/src/themes/index.ts:
--------------------------------------------------------------------------------
1 | export {};
--------------------------------------------------------------------------------
/packages/ui-react/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export type MetaData = {
2 | name: string;
3 | required: boolean;
4 | label: string;
5 | placeholder: string;
6 | type: string;
7 | }
8 |
9 | export const parseSchemaToFormFields = (schema: Record): MetaData[] => {
10 | return Object.entries(schema).map(([key, value]) => {
11 | const isOptional = value.endsWith('?');
12 | return {
13 | name: key,
14 | required: !isOptional,
15 | label: key.charAt(0).toUpperCase() + key.slice(1),
16 | placeholder: `Enter your ${key.charAt(0).toUpperCase() + key.slice(1)}`,
17 | type: value.replace('?', ''), // Remove "?" to identify the base type
18 | };
19 | });
20 | };
21 |
22 |
--------------------------------------------------------------------------------
/packages/ui-react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist"
5 | },
6 | "include": ["src"],
7 | }
8 |
--------------------------------------------------------------------------------
/packages/ui-vue/README.md:
--------------------------------------------------------------------------------
1 | # @quorini/ui-vue
2 | Upcoming...
--------------------------------------------------------------------------------
/packages/ui-vue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@quorini/ui-vue",
3 | "version": "1.1.65",
4 | "main": "dist/index.js",
5 | "types": "dist/index.d.ts",
6 | "keywords": [
7 | "quorini",
8 | "react",
9 | "typescript",
10 | "graphQL",
11 | "core",
12 | "ui-react"
13 | ],
14 | "scripts": {
15 | "build": "rollup -c"
16 | },
17 | "peerDependencies": {
18 | "vue": "^3.0.0"
19 | },
20 | "dependencies": {
21 | "@quorini/core": "^1.1.65"
22 | },
23 | "devDependencies": {
24 | "rollup": "^2.60.0",
25 | "typescript": "^4.5.0"
26 | },
27 | "publishConfig": {
28 | "access": "public"
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "https://github.com/quorini/quorini-js-sdk"
33 | },
34 | "author": "ernst1202",
35 | "license": "MIT",
36 | "gitHead": "76424c713234e1e82c7c9605d9d7fc4015881659"
37 | }
38 |
--------------------------------------------------------------------------------
/packages/ui-vue/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2';
2 |
3 | export default {
4 | input: 'src/index.ts',
5 | output: {
6 | dir: 'dist',
7 | format: 'es',
8 | sourcemap: true,
9 | },
10 | plugins: [
11 | typescript({
12 | tsconfig: './tsconfig.json'
13 | })
14 | ],
15 | external: ['vue', '@quorini/core']
16 | };
17 |
--------------------------------------------------------------------------------
/packages/ui-vue/src/index.ts:
--------------------------------------------------------------------------------
1 | export {};
--------------------------------------------------------------------------------
/packages/ui-vue/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist"
5 | },
6 | "include": ["src"],
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "esnext",
5 | "strict": true,
6 | "declaration": true,
7 | "declarationDir": "./dist",
8 | "jsx": "react",
9 | "outDir": "./dist",
10 | "baseUrl": ".",
11 | "moduleResolution": "node",
12 | "typeRoots": ["node_modules/@types"],
13 | "types": [],
14 | "allowSyntheticDefaultImports": true,
15 | "esModuleInterop": true,
16 | "lib": ["ESNext", "DOM"],
17 | "paths": {
18 | "@quorini/core": ["packages/core/src"],
19 | "@quorini/ui-react": ["packages/ui-react/src"],
20 | "@quorini/ui-vue": ["packages/ui-vue/src"]
21 | }
22 | },
23 | "include": ["packages/*/src"],
24 | "exclude": ["node_modules", "dist"]
25 | }
26 |
--------------------------------------------------------------------------------